# tests/unit/services/memory/scoping/test_scope.py
"""Unit tests for scope management."""

from uuid import uuid4

import pytest

from app.services.memory.scoping.scope import (
    ScopeManager,
    ScopePolicy,
    get_scope_manager,
)
from app.services.memory.types import ScopeLevel


class TestScopePolicy:
    """Tests for ScopePolicy dataclass."""

    def test_default_values(self) -> None:
        """Test default policy values."""
        policy = ScopePolicy(
            scope_type=ScopeLevel.PROJECT,
            scope_id="test-project",
        )

        assert policy.can_read is True
        assert policy.can_write is True
        assert policy.can_inherit is True
        assert policy.allowed_memory_types == ["all"]

    def test_allows_read(self) -> None:
        """Test allows_read method."""
        policy = ScopePolicy(
            scope_type=ScopeLevel.PROJECT,
            scope_id="test",
            can_read=True,
        )
        assert policy.allows_read() is True

        policy.can_read = False
        assert policy.allows_read() is False

    def test_allows_write(self) -> None:
        """Test allows_write method."""
        policy = ScopePolicy(
            scope_type=ScopeLevel.PROJECT,
            scope_id="test",
            can_write=True,
        )
        assert policy.allows_write() is True

        policy.can_write = False
        assert policy.allows_write() is False

    def test_allows_inherit(self) -> None:
        """Test allows_inherit method."""
        policy = ScopePolicy(
            scope_type=ScopeLevel.PROJECT,
            scope_id="test",
            can_inherit=True,
        )
        assert policy.allows_inherit() is True

        policy.can_inherit = False
        assert policy.allows_inherit() is False

    def test_allows_memory_type(self) -> None:
        """Test allows_memory_type method."""
        policy = ScopePolicy(
            scope_type=ScopeLevel.PROJECT,
            scope_id="test",
            allowed_memory_types=["all"],
        )
        assert policy.allows_memory_type("working") is True
        assert policy.allows_memory_type("episodic") is True

        policy.allowed_memory_types = ["working", "episodic"]
        assert policy.allows_memory_type("working") is True
        assert policy.allows_memory_type("episodic") is True
        assert policy.allows_memory_type("semantic") is False


class TestScopeManager:
    """Tests for ScopeManager class."""

    @pytest.fixture
    def manager(self) -> ScopeManager:
        """Create a scope manager."""
        return ScopeManager()

    def test_create_global_scope(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test creating a global scope."""
        scope = manager.create_scope(
            scope_type=ScopeLevel.GLOBAL,
            scope_id="global",
        )

        assert scope.scope_type == ScopeLevel.GLOBAL
        assert scope.scope_id == "global"
        assert scope.parent is None

    def test_create_project_scope(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test creating a project scope."""
        global_scope = manager.create_scope(
            scope_type=ScopeLevel.GLOBAL,
            scope_id="global",
        )

        project_scope = manager.create_scope(
            scope_type=ScopeLevel.PROJECT,
            scope_id="project-1",
            parent=global_scope,
        )

        assert project_scope.scope_type == ScopeLevel.PROJECT
        assert project_scope.scope_id == "project-1"
        assert project_scope.parent is global_scope

    def test_create_scope_auto_parent(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test that non-global scopes auto-create parent chain."""
        scope = manager.create_scope(
            scope_type=ScopeLevel.PROJECT,
            scope_id="test-project",
        )

        assert scope.scope_type == ScopeLevel.PROJECT
        assert scope.parent is not None
        assert scope.parent.scope_type == ScopeLevel.GLOBAL

    def test_create_scope_invalid_hierarchy(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test that invalid hierarchy raises error."""
        project_scope = manager.create_scope(
            scope_type=ScopeLevel.PROJECT,
            scope_id="project-1",
        )

        with pytest.raises(ValueError, match="Invalid scope hierarchy"):
            manager.create_scope(
                scope_type=ScopeLevel.GLOBAL,
                scope_id="global",
                parent=project_scope,
            )

    def test_create_scope_from_ids(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test creating scope from individual IDs."""
        project_id = uuid4()
        agent_type_id = uuid4()

        scope = manager.create_scope_from_ids(
            project_id=project_id,
            agent_type_id=agent_type_id,
        )

        assert scope.scope_type == ScopeLevel.AGENT_TYPE
        assert scope.scope_id == str(agent_type_id)
        assert scope.parent is not None
        assert scope.parent.scope_type == ScopeLevel.PROJECT

    def test_create_scope_from_ids_with_session(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test creating scope with session ID."""
        project_id = uuid4()
        session_id = "session-123"

        scope = manager.create_scope_from_ids(
            project_id=project_id,
            session_id=session_id,
        )

        assert scope.scope_type == ScopeLevel.SESSION
        assert scope.scope_id == session_id

    def test_get_default_policy(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test getting default policy."""
        scope = manager.create_scope(
            scope_type=ScopeLevel.PROJECT,
            scope_id="test-project",
        )

        policy = manager.get_policy(scope)

        assert policy.can_read is True
        assert policy.can_write is True

    def test_set_and_get_policy(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test setting and retrieving a policy."""
        scope = manager.create_scope(
            scope_type=ScopeLevel.PROJECT,
            scope_id="test-project",
        )

        custom_policy = ScopePolicy(
            scope_type=ScopeLevel.PROJECT,
            scope_id="test-project",
            can_write=False,
        )

        manager.set_policy(scope, custom_policy)
        retrieved = manager.get_policy(scope)

        assert retrieved.can_write is False

    def test_get_scope_depth(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test getting scope depth."""
        assert manager.get_scope_depth(ScopeLevel.GLOBAL) == 0
        assert manager.get_scope_depth(ScopeLevel.PROJECT) == 1
        assert manager.get_scope_depth(ScopeLevel.AGENT_TYPE) == 2
        assert manager.get_scope_depth(ScopeLevel.AGENT_INSTANCE) == 3
        assert manager.get_scope_depth(ScopeLevel.SESSION) == 4

    def test_get_parent_level(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test getting parent level."""
        assert manager.get_parent_level(ScopeLevel.GLOBAL) is None
        assert manager.get_parent_level(ScopeLevel.PROJECT) == ScopeLevel.GLOBAL
        assert manager.get_parent_level(ScopeLevel.AGENT_TYPE) == ScopeLevel.PROJECT
        assert manager.get_parent_level(ScopeLevel.SESSION) == ScopeLevel.AGENT_INSTANCE

    def test_get_child_level(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test getting child level."""
        assert manager.get_child_level(ScopeLevel.GLOBAL) == ScopeLevel.PROJECT
        assert manager.get_child_level(ScopeLevel.PROJECT) == ScopeLevel.AGENT_TYPE
        assert manager.get_child_level(ScopeLevel.SESSION) is None

    def test_is_ancestor(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test ancestor checking."""
        global_scope = manager.create_scope(ScopeLevel.GLOBAL, "global")
        project_scope = manager.create_scope(
            ScopeLevel.PROJECT, "project", parent=global_scope
        )
        agent_scope = manager.create_scope(
            ScopeLevel.AGENT_TYPE, "agent", parent=project_scope
        )

        assert manager.is_ancestor(global_scope, agent_scope) is True
        assert manager.is_ancestor(project_scope, agent_scope) is True
        assert manager.is_ancestor(agent_scope, global_scope) is False
        assert manager.is_ancestor(agent_scope, project_scope) is False

    def test_get_common_ancestor(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test finding common ancestor."""
        global_scope = manager.create_scope(ScopeLevel.GLOBAL, "global")
        project_scope = manager.create_scope(
            ScopeLevel.PROJECT, "project", parent=global_scope
        )
        agent1 = manager.create_scope(
            ScopeLevel.AGENT_TYPE, "agent1", parent=project_scope
        )
        agent2 = manager.create_scope(
            ScopeLevel.AGENT_TYPE, "agent2", parent=project_scope
        )

        common = manager.get_common_ancestor(agent1, agent2)

        assert common is not None
        assert common.scope_type == ScopeLevel.PROJECT

    def test_can_access_same_scope(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test access to same scope."""
        scope = manager.create_scope(ScopeLevel.PROJECT, "project")

        assert manager.can_access(scope, scope) is True
        assert manager.can_access(scope, scope, "write") is True

    def test_can_access_ancestor(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test access to ancestor scope."""
        global_scope = manager.create_scope(ScopeLevel.GLOBAL, "global")
        project_scope = manager.create_scope(
            ScopeLevel.PROJECT, "project", parent=global_scope
        )

        # Child can read from parent
        assert manager.can_access(project_scope, global_scope, "read") is True

    def test_cannot_access_descendant(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test that parent cannot access child scope."""
        global_scope = manager.create_scope(ScopeLevel.GLOBAL, "global")
        project_scope = manager.create_scope(
            ScopeLevel.PROJECT, "project", parent=global_scope
        )

        # Parent cannot access child
        assert manager.can_access(global_scope, project_scope) is False

    def test_cannot_access_sibling(
        self,
        manager: ScopeManager,
    ) -> None:
        """Test that sibling scopes cannot access each other."""
        global_scope = manager.create_scope(ScopeLevel.GLOBAL, "global")
        project1 = manager.create_scope(
            ScopeLevel.PROJECT, "project1", parent=global_scope
        )
        project2 = manager.create_scope(
            ScopeLevel.PROJECT, "project2", parent=global_scope
        )

        assert manager.can_access(project1, project2) is False
        assert manager.can_access(project2, project1) is False


class TestGetScopeManager:
    """Tests for singleton getter."""

    def test_returns_instance(self) -> None:
        """Test that getter returns instance."""
        manager = get_scope_manager()
        assert manager is not None
        assert isinstance(manager, ScopeManager)

    def test_returns_same_instance(self) -> None:
        """Test that getter returns same instance."""
        manager1 = get_scope_manager()
        manager2 = get_scope_manager()
        assert manager1 is manager2
