"""
Tests for Memory System Exceptions.
"""

from uuid import uuid4

import pytest

from app.services.memory.exceptions import (
    CheckpointError,
    EmbeddingError,
    MemoryCapacityError,
    MemoryConflictError,
    MemoryConsolidationError,
    MemoryError,
    MemoryExpiredError,
    MemoryNotFoundError,
    MemoryRetrievalError,
    MemoryScopeError,
    MemorySerializationError,
    MemoryStorageError,
)


class TestMemoryError:
    """Tests for base MemoryError class."""

    def test_basic_error(self) -> None:
        """Test creating a basic memory error."""
        error = MemoryError("Something went wrong")

        assert str(error) == "Something went wrong"
        assert error.message == "Something went wrong"
        assert error.memory_type is None
        assert error.scope_type is None
        assert error.scope_id is None
        assert error.details == {}

    def test_error_with_context(self) -> None:
        """Test creating an error with context."""
        error = MemoryError(
            "Operation failed",
            memory_type="episodic",
            scope_type="project",
            scope_id="proj-123",
            details={"operation": "search"},
        )

        assert error.memory_type == "episodic"
        assert error.scope_type == "project"
        assert error.scope_id == "proj-123"
        assert error.details == {"operation": "search"}

    def test_error_inheritance(self) -> None:
        """Test that MemoryError inherits from Exception."""
        error = MemoryError("test")
        assert isinstance(error, Exception)


class TestMemoryNotFoundError:
    """Tests for MemoryNotFoundError class."""

    def test_default_message(self) -> None:
        """Test default error message."""
        error = MemoryNotFoundError()
        assert error.message == "Memory not found"

    def test_with_memory_id(self) -> None:
        """Test error with memory ID."""
        memory_id = uuid4()
        error = MemoryNotFoundError(
            f"Memory {memory_id} not found",
            memory_id=memory_id,
        )

        assert error.memory_id == memory_id

    def test_with_key(self) -> None:
        """Test error with key."""
        error = MemoryNotFoundError(
            "Key not found",
            key="my_key",
        )

        assert error.key == "my_key"


class TestMemoryCapacityError:
    """Tests for MemoryCapacityError class."""

    def test_default_message(self) -> None:
        """Test default error message."""
        error = MemoryCapacityError()
        assert error.message == "Memory capacity exceeded"

    def test_with_sizes(self) -> None:
        """Test error with size information."""
        error = MemoryCapacityError(
            "Working memory full",
            current_size=1048576,
            max_size=1000000,
            item_count=500,
        )

        assert error.current_size == 1048576
        assert error.max_size == 1000000
        assert error.item_count == 500


class TestMemoryExpiredError:
    """Tests for MemoryExpiredError class."""

    def test_default_message(self) -> None:
        """Test default error message."""
        error = MemoryExpiredError()
        assert error.message == "Memory has expired"

    def test_with_expiry_info(self) -> None:
        """Test error with expiry information."""
        error = MemoryExpiredError(
            "Key expired",
            key="session_data",
            expired_at="2025-01-05T00:00:00Z",
        )

        assert error.key == "session_data"
        assert error.expired_at == "2025-01-05T00:00:00Z"


class TestMemoryStorageError:
    """Tests for MemoryStorageError class."""

    def test_default_message(self) -> None:
        """Test default error message."""
        error = MemoryStorageError()
        assert error.message == "Memory storage operation failed"

    def test_with_operation_info(self) -> None:
        """Test error with operation information."""
        error = MemoryStorageError(
            "Redis write failed",
            operation="set",
            backend="redis",
        )

        assert error.operation == "set"
        assert error.backend == "redis"


class TestMemorySerializationError:
    """Tests for MemorySerializationError class."""

    def test_default_message(self) -> None:
        """Test default error message."""
        error = MemorySerializationError()
        assert error.message == "Memory serialization failed"

    def test_with_content_type(self) -> None:
        """Test error with content type."""
        error = MemorySerializationError(
            "Cannot serialize function",
            content_type="function",
        )

        assert error.content_type == "function"


class TestMemoryScopeError:
    """Tests for MemoryScopeError class."""

    def test_default_message(self) -> None:
        """Test default error message."""
        error = MemoryScopeError()
        assert error.message == "Memory scope error"

    def test_with_scope_info(self) -> None:
        """Test error with scope information."""
        error = MemoryScopeError(
            "Scope access denied",
            requested_scope="global",
            allowed_scopes=["project", "session"],
        )

        assert error.requested_scope == "global"
        assert error.allowed_scopes == ["project", "session"]


class TestMemoryConsolidationError:
    """Tests for MemoryConsolidationError class."""

    def test_default_message(self) -> None:
        """Test default error message."""
        error = MemoryConsolidationError()
        assert error.message == "Memory consolidation failed"

    def test_with_consolidation_info(self) -> None:
        """Test error with consolidation information."""
        error = MemoryConsolidationError(
            "Transfer failed",
            source_type="working",
            target_type="episodic",
            items_processed=50,
        )

        assert error.source_type == "working"
        assert error.target_type == "episodic"
        assert error.items_processed == 50


class TestMemoryRetrievalError:
    """Tests for MemoryRetrievalError class."""

    def test_default_message(self) -> None:
        """Test default error message."""
        error = MemoryRetrievalError()
        assert error.message == "Memory retrieval failed"

    def test_with_query_info(self) -> None:
        """Test error with query information."""
        error = MemoryRetrievalError(
            "Search timeout",
            query="complex search query",
            retrieval_type="semantic",
        )

        assert error.query == "complex search query"
        assert error.retrieval_type == "semantic"


class TestEmbeddingError:
    """Tests for EmbeddingError class."""

    def test_default_message(self) -> None:
        """Test default error message."""
        error = EmbeddingError()
        assert error.message == "Embedding generation failed"

    def test_with_embedding_info(self) -> None:
        """Test error with embedding information."""
        error = EmbeddingError(
            "Content too long",
            content_length=100000,
            model="text-embedding-3-small",
        )

        assert error.content_length == 100000
        assert error.model == "text-embedding-3-small"


class TestCheckpointError:
    """Tests for CheckpointError class."""

    def test_default_message(self) -> None:
        """Test default error message."""
        error = CheckpointError()
        assert error.message == "Checkpoint operation failed"

    def test_with_checkpoint_info(self) -> None:
        """Test error with checkpoint information."""
        error = CheckpointError(
            "Restore failed",
            checkpoint_id="chk-123",
            operation="restore",
        )

        assert error.checkpoint_id == "chk-123"
        assert error.operation == "restore"


class TestMemoryConflictError:
    """Tests for MemoryConflictError class."""

    def test_default_message(self) -> None:
        """Test default error message."""
        error = MemoryConflictError()
        assert error.message == "Memory conflict detected"

    def test_with_conflict_info(self) -> None:
        """Test error with conflict information."""
        id1 = uuid4()
        id2 = uuid4()
        error = MemoryConflictError(
            "Contradictory facts detected",
            conflicting_ids=[id1, id2],
            conflict_type="semantic",
        )

        assert len(error.conflicting_ids) == 2
        assert error.conflict_type == "semantic"


class TestExceptionHierarchy:
    """Tests for exception inheritance hierarchy."""

    def test_all_exceptions_inherit_from_memory_error(self) -> None:
        """Test that all exceptions inherit from MemoryError."""
        exceptions = [
            MemoryNotFoundError(),
            MemoryCapacityError(),
            MemoryExpiredError(),
            MemoryStorageError(),
            MemorySerializationError(),
            MemoryScopeError(),
            MemoryConsolidationError(),
            MemoryRetrievalError(),
            EmbeddingError(),
            CheckpointError(),
            MemoryConflictError(),
        ]

        for exc in exceptions:
            assert isinstance(exc, MemoryError)
            assert isinstance(exc, Exception)

    def test_can_catch_base_error(self) -> None:
        """Test that catching MemoryError catches all subclasses."""
        exceptions = [
            MemoryNotFoundError("not found"),
            MemoryCapacityError("capacity"),
            MemoryStorageError("storage"),
        ]

        for exc in exceptions:
            with pytest.raises(MemoryError):
                raise exc
