# tests/models/syndarix/test_agent_instance.py
"""
Unit tests for the AgentInstance model.
"""

import uuid
from datetime import UTC, datetime
from decimal import Decimal

from app.models.syndarix import (
    AgentInstance,
    AgentStatus,
    AgentType,
    Project,
)


class TestAgentInstanceModel:
    """Tests for AgentInstance model creation and fields."""

    def test_create_agent_instance_with_required_fields(self, db_session):
        """Test creating an agent instance with only required fields."""
        # First create dependencies
        project = Project(
            id=uuid.uuid4(),
            name="Test Project",
            slug="test-project-instance",
        )
        db_session.add(project)

        agent_type = AgentType(
            id=uuid.uuid4(),
            name="Test Agent",
            slug="test-agent-instance",
            personality_prompt="Test",
            primary_model="claude-opus-4-5-20251101",
        )
        db_session.add(agent_type)
        db_session.commit()

        # Create agent instance
        instance = AgentInstance(
            id=uuid.uuid4(),
            agent_type_id=agent_type.id,
            project_id=project.id,
            name="Alice",
        )
        db_session.add(instance)
        db_session.commit()

        retrieved = db_session.query(AgentInstance).filter_by(project_id=project.id).first()

        assert retrieved is not None
        assert retrieved.agent_type_id == agent_type.id
        assert retrieved.project_id == project.id
        assert retrieved.status == AgentStatus.IDLE  # Default
        assert retrieved.current_task is None
        assert retrieved.short_term_memory == {}
        assert retrieved.long_term_memory_ref is None
        assert retrieved.session_id is None
        assert retrieved.tasks_completed == 0
        assert retrieved.tokens_used == 0
        assert retrieved.cost_incurred == Decimal("0")

    def test_create_agent_instance_with_all_fields(self, db_session):
        """Test creating an agent instance with all optional fields."""
        # First create dependencies
        project = Project(
            id=uuid.uuid4(),
            name="Full Project",
            slug="full-project-instance",
        )
        db_session.add(project)

        agent_type = AgentType(
            id=uuid.uuid4(),
            name="Full Agent",
            slug="full-agent-instance",
            personality_prompt="Test",
            primary_model="claude-opus-4-5-20251101",
        )
        db_session.add(agent_type)
        db_session.commit()

        instance_id = uuid.uuid4()
        now = datetime.now(UTC)

        instance = AgentInstance(
            id=instance_id,
            agent_type_id=agent_type.id,
            project_id=project.id,
            name="Bob",
            status=AgentStatus.WORKING,
            current_task="Implementing user authentication",
            short_term_memory={"context": "Working on auth", "recent_files": ["auth.py"]},
            long_term_memory_ref="project-123/agent-456",
            session_id="session-abc-123",
            last_activity_at=now,
            tasks_completed=5,
            tokens_used=10000,
            cost_incurred=Decimal("0.5000"),
        )
        db_session.add(instance)
        db_session.commit()

        retrieved = db_session.query(AgentInstance).filter_by(id=instance_id).first()

        assert retrieved.status == AgentStatus.WORKING
        assert retrieved.current_task == "Implementing user authentication"
        assert retrieved.short_term_memory == {"context": "Working on auth", "recent_files": ["auth.py"]}
        assert retrieved.long_term_memory_ref == "project-123/agent-456"
        assert retrieved.session_id == "session-abc-123"
        assert retrieved.tasks_completed == 5
        assert retrieved.tokens_used == 10000
        assert retrieved.cost_incurred == Decimal("0.5000")

    def test_agent_instance_timestamps(self, db_session):
        """Test that timestamps are automatically set."""
        project = Project(id=uuid.uuid4(), name="Timestamp Project", slug="timestamp-project-ai")
        agent_type = AgentType(
            id=uuid.uuid4(),
            name="Timestamp Agent",
            slug="timestamp-agent-ai",
            personality_prompt="Test",
            primary_model="claude-opus-4-5-20251101",
        )
        db_session.add(project)
        db_session.add(agent_type)
        db_session.commit()

        instance = AgentInstance(
            id=uuid.uuid4(),
            agent_type_id=agent_type.id,
            project_id=project.id,
            name="Charlie",
        )
        db_session.add(instance)
        db_session.commit()

        assert isinstance(instance.created_at, datetime)
        assert isinstance(instance.updated_at, datetime)

    def test_agent_instance_string_representation(self, db_session):
        """Test the string representation of an agent instance."""
        project = Project(id=uuid.uuid4(), name="Repr Project", slug="repr-project-ai")
        agent_type = AgentType(
            id=uuid.uuid4(),
            name="Repr Agent",
            slug="repr-agent-ai",
            personality_prompt="Test",
            primary_model="claude-opus-4-5-20251101",
        )
        db_session.add(project)
        db_session.add(agent_type)
        db_session.commit()

        instance_id = uuid.uuid4()
        instance = AgentInstance(
            id=instance_id,
            agent_type_id=agent_type.id,
            project_id=project.id,
            name="Dave",
            status=AgentStatus.IDLE,
        )

        repr_str = repr(instance)
        assert "Dave" in repr_str
        assert str(instance_id) in repr_str
        assert str(agent_type.id) in repr_str
        assert str(project.id) in repr_str
        assert "idle" in repr_str


class TestAgentInstanceStatus:
    """Tests for AgentInstance status transitions."""

    def test_all_agent_statuses(self, db_session):
        """Test that all agent statuses can be stored."""
        project = Project(id=uuid.uuid4(), name="Status Project", slug="status-project-ai")
        agent_type = AgentType(
            id=uuid.uuid4(),
            name="Status Agent",
            slug="status-agent-ai",
            personality_prompt="Test",
            primary_model="claude-opus-4-5-20251101",
        )
        db_session.add(project)
        db_session.add(agent_type)
        db_session.commit()

        for idx, status in enumerate(AgentStatus):
            instance = AgentInstance(
                id=uuid.uuid4(),
                agent_type_id=agent_type.id,
                project_id=project.id,
                name=f"Agent-{idx}",
                status=status,
            )
            db_session.add(instance)
            db_session.commit()

            retrieved = db_session.query(AgentInstance).filter_by(id=instance.id).first()
            assert retrieved.status == status

    def test_status_update(self, db_session):
        """Test updating agent instance status."""
        project = Project(id=uuid.uuid4(), name="Update Status Project", slug="update-status-project-ai")
        agent_type = AgentType(
            id=uuid.uuid4(),
            name="Update Status Agent",
            slug="update-status-agent-ai",
            personality_prompt="Test",
            primary_model="claude-opus-4-5-20251101",
        )
        db_session.add(project)
        db_session.add(agent_type)
        db_session.commit()

        instance = AgentInstance(
            id=uuid.uuid4(),
            agent_type_id=agent_type.id,
            project_id=project.id,
            name="Eve",
            status=AgentStatus.IDLE,
        )
        db_session.add(instance)
        db_session.commit()

        # Update to WORKING
        instance.status = AgentStatus.WORKING
        instance.current_task = "Processing feature request"
        db_session.commit()

        retrieved = db_session.query(AgentInstance).filter_by(id=instance.id).first()
        assert retrieved.status == AgentStatus.WORKING
        assert retrieved.current_task == "Processing feature request"

    def test_terminate_agent_instance(self, db_session):
        """Test terminating an agent instance."""
        project = Project(id=uuid.uuid4(), name="Terminate Project", slug="terminate-project-ai")
        agent_type = AgentType(
            id=uuid.uuid4(),
            name="Terminate Agent",
            slug="terminate-agent-ai",
            personality_prompt="Test",
            primary_model="claude-opus-4-5-20251101",
        )
        db_session.add(project)
        db_session.add(agent_type)
        db_session.commit()

        instance = AgentInstance(
            id=uuid.uuid4(),
            agent_type_id=agent_type.id,
            project_id=project.id,
            name="Frank",
            status=AgentStatus.WORKING,
            current_task="Working on something",
            session_id="active-session",
        )
        db_session.add(instance)
        db_session.commit()

        # Terminate
        now = datetime.now(UTC)
        instance.status = AgentStatus.TERMINATED
        instance.terminated_at = now
        instance.current_task = None
        instance.session_id = None
        db_session.commit()

        retrieved = db_session.query(AgentInstance).filter_by(id=instance.id).first()
        assert retrieved.status == AgentStatus.TERMINATED
        assert retrieved.terminated_at is not None
        assert retrieved.current_task is None
        assert retrieved.session_id is None


class TestAgentInstanceMetrics:
    """Tests for AgentInstance usage metrics."""

    def test_increment_metrics(self, db_session):
        """Test incrementing usage metrics."""
        project = Project(id=uuid.uuid4(), name="Metrics Project", slug="metrics-project-ai")
        agent_type = AgentType(
            id=uuid.uuid4(),
            name="Metrics Agent",
            slug="metrics-agent-ai",
            personality_prompt="Test",
            primary_model="claude-opus-4-5-20251101",
        )
        db_session.add(project)
        db_session.add(agent_type)
        db_session.commit()

        instance = AgentInstance(
            id=uuid.uuid4(),
            agent_type_id=agent_type.id,
            project_id=project.id,
            name="Grace",
        )
        db_session.add(instance)
        db_session.commit()

        # Record task completion
        instance.tasks_completed += 1
        instance.tokens_used += 1500
        instance.cost_incurred += Decimal("0.0150")
        db_session.commit()

        retrieved = db_session.query(AgentInstance).filter_by(id=instance.id).first()
        assert retrieved.tasks_completed == 1
        assert retrieved.tokens_used == 1500
        assert retrieved.cost_incurred == Decimal("0.0150")

        # Record another task
        retrieved.tasks_completed += 1
        retrieved.tokens_used += 2500
        retrieved.cost_incurred += Decimal("0.0250")
        db_session.commit()

        updated = db_session.query(AgentInstance).filter_by(id=instance.id).first()
        assert updated.tasks_completed == 2
        assert updated.tokens_used == 4000
        assert updated.cost_incurred == Decimal("0.0400")

    def test_large_token_count(self, db_session):
        """Test handling large token counts."""
        project = Project(id=uuid.uuid4(), name="Large Tokens Project", slug="large-tokens-project-ai")
        agent_type = AgentType(
            id=uuid.uuid4(),
            name="Large Tokens Agent",
            slug="large-tokens-agent-ai",
            personality_prompt="Test",
            primary_model="claude-opus-4-5-20251101",
        )
        db_session.add(project)
        db_session.add(agent_type)
        db_session.commit()

        instance = AgentInstance(
            id=uuid.uuid4(),
            agent_type_id=agent_type.id,
            project_id=project.id,
            name="Henry",
            tokens_used=10_000_000_000,  # 10 billion tokens
            cost_incurred=Decimal("100000.0000"),  # $100,000
        )
        db_session.add(instance)
        db_session.commit()

        retrieved = db_session.query(AgentInstance).filter_by(id=instance.id).first()
        assert retrieved.tokens_used == 10_000_000_000
        assert retrieved.cost_incurred == Decimal("100000.0000")


class TestAgentInstanceShortTermMemory:
    """Tests for AgentInstance short-term memory JSON field."""

    def test_store_complex_memory(self, db_session):
        """Test storing complex short-term memory."""
        project = Project(id=uuid.uuid4(), name="Memory Project", slug="memory-project-ai")
        agent_type = AgentType(
            id=uuid.uuid4(),
            name="Memory Agent",
            slug="memory-agent-ai",
            personality_prompt="Test",
            primary_model="claude-opus-4-5-20251101",
        )
        db_session.add(project)
        db_session.add(agent_type)
        db_session.commit()

        memory = {
            "conversation_history": [
                {"role": "user", "content": "Implement feature X"},
                {"role": "assistant", "content": "I'll start by..."},
            ],
            "recent_files": ["auth.py", "models.py", "test_auth.py"],
            "decisions": {
                "architecture": "Use repository pattern",
                "testing": "TDD approach",
            },
            "blockers": [],
            "context_tokens": 2048,
        }

        instance = AgentInstance(
            id=uuid.uuid4(),
            agent_type_id=agent_type.id,
            project_id=project.id,
            name="Ivy",
            short_term_memory=memory,
        )
        db_session.add(instance)
        db_session.commit()

        retrieved = db_session.query(AgentInstance).filter_by(id=instance.id).first()
        assert retrieved.short_term_memory == memory
        assert len(retrieved.short_term_memory["conversation_history"]) == 2
        assert "auth.py" in retrieved.short_term_memory["recent_files"]

    def test_update_memory(self, db_session):
        """Test updating short-term memory."""
        project = Project(id=uuid.uuid4(), name="Update Memory Project", slug="update-memory-project-ai")
        agent_type = AgentType(
            id=uuid.uuid4(),
            name="Update Memory Agent",
            slug="update-memory-agent-ai",
            personality_prompt="Test",
            primary_model="claude-opus-4-5-20251101",
        )
        db_session.add(project)
        db_session.add(agent_type)
        db_session.commit()

        instance = AgentInstance(
            id=uuid.uuid4(),
            agent_type_id=agent_type.id,
            project_id=project.id,
            name="Jack",
            short_term_memory={"initial": "state"},
        )
        db_session.add(instance)
        db_session.commit()

        # Update memory
        instance.short_term_memory = {"updated": "state", "new_key": "new_value"}
        db_session.commit()

        retrieved = db_session.query(AgentInstance).filter_by(id=instance.id).first()
        assert "initial" not in retrieved.short_term_memory
        assert retrieved.short_term_memory["updated"] == "state"
        assert retrieved.short_term_memory["new_key"] == "new_value"
