# tests/crud/syndarix/test_agent_instance_crud.py
"""
Tests for AgentInstance CRUD operations.
"""

import uuid
from decimal import Decimal

import pytest

from app.crud.syndarix import agent_instance as agent_instance_crud
from app.models.syndarix import AgentStatus
from app.schemas.syndarix import AgentInstanceCreate, AgentInstanceUpdate


class TestAgentInstanceCreate:
    """Tests for agent instance creation."""

    @pytest.mark.asyncio
    async def test_create_agent_instance_success(self, async_test_db, test_project_crud, test_agent_type_crud):
        """Test successfully creating an agent instance."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        async with AsyncTestingSessionLocal() as session:
            instance_data = AgentInstanceCreate(
                agent_type_id=test_agent_type_crud.id,
                project_id=test_project_crud.id,
                name="TestBot",
                status=AgentStatus.IDLE,
                current_task=None,
                short_term_memory={"context": "initial"},
                long_term_memory_ref="project-123/agent-456",
                session_id="session-abc",
            )
            result = await agent_instance_crud.create(session, obj_in=instance_data)

            assert result.id is not None
            assert result.agent_type_id == test_agent_type_crud.id
            assert result.project_id == test_project_crud.id
            assert result.status == AgentStatus.IDLE
            assert result.short_term_memory == {"context": "initial"}

    @pytest.mark.asyncio
    async def test_create_agent_instance_minimal(self, async_test_db, test_project_crud, test_agent_type_crud):
        """Test creating agent instance with minimal fields."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        async with AsyncTestingSessionLocal() as session:
            instance_data = AgentInstanceCreate(
                agent_type_id=test_agent_type_crud.id,
                project_id=test_project_crud.id,
                name="MinimalBot",
            )
            result = await agent_instance_crud.create(session, obj_in=instance_data)

            assert result.status == AgentStatus.IDLE  # Default
            assert result.tasks_completed == 0
            assert result.tokens_used == 0


class TestAgentInstanceRead:
    """Tests for agent instance read operations."""

    @pytest.mark.asyncio
    async def test_get_agent_instance_by_id(self, async_test_db, test_agent_instance_crud):
        """Test getting agent instance by ID."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        async with AsyncTestingSessionLocal() as session:
            result = await agent_instance_crud.get(session, id=str(test_agent_instance_crud.id))

            assert result is not None
            assert result.id == test_agent_instance_crud.id

    @pytest.mark.asyncio
    async def test_get_agent_instance_by_id_not_found(self, async_test_db):
        """Test getting non-existent agent instance returns None."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        async with AsyncTestingSessionLocal() as session:
            result = await agent_instance_crud.get(session, id=str(uuid.uuid4()))
            assert result is None

    @pytest.mark.asyncio
    async def test_get_with_details(self, async_test_db, test_agent_instance_crud):
        """Test getting agent instance with related details."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        async with AsyncTestingSessionLocal() as session:
            result = await agent_instance_crud.get_with_details(
                session,
                instance_id=test_agent_instance_crud.id,
            )

            assert result is not None
            assert result["instance"].id == test_agent_instance_crud.id
            assert result["agent_type_name"] is not None
            assert result["project_name"] is not None


class TestAgentInstanceUpdate:
    """Tests for agent instance update operations."""

    @pytest.mark.asyncio
    async def test_update_agent_instance_status(self, async_test_db, test_agent_instance_crud):
        """Test updating agent instance status."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        async with AsyncTestingSessionLocal() as session:
            instance = await agent_instance_crud.get(session, id=str(test_agent_instance_crud.id))

            update_data = AgentInstanceUpdate(
                status=AgentStatus.WORKING,
                current_task="Processing feature request",
            )
            result = await agent_instance_crud.update(session, db_obj=instance, obj_in=update_data)

            assert result.status == AgentStatus.WORKING
            assert result.current_task == "Processing feature request"

    @pytest.mark.asyncio
    async def test_update_agent_instance_memory(self, async_test_db, test_agent_instance_crud):
        """Test updating agent instance short-term memory."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        async with AsyncTestingSessionLocal() as session:
            instance = await agent_instance_crud.get(session, id=str(test_agent_instance_crud.id))

            new_memory = {"conversation": ["msg1", "msg2"], "decisions": {"key": "value"}}
            update_data = AgentInstanceUpdate(short_term_memory=new_memory)
            result = await agent_instance_crud.update(session, db_obj=instance, obj_in=update_data)

            assert result.short_term_memory == new_memory


class TestAgentInstanceStatusUpdate:
    """Tests for agent instance status update method."""

    @pytest.mark.asyncio
    async def test_update_status(self, async_test_db, test_agent_instance_crud):
        """Test updating agent instance status via dedicated method."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        async with AsyncTestingSessionLocal() as session:
            result = await agent_instance_crud.update_status(
                session,
                instance_id=test_agent_instance_crud.id,
                status=AgentStatus.WORKING,
                current_task="Working on feature X",
            )

            assert result is not None
            assert result.status == AgentStatus.WORKING
            assert result.current_task == "Working on feature X"
            assert result.last_activity_at is not None

    @pytest.mark.asyncio
    async def test_update_status_nonexistent(self, async_test_db):
        """Test updating status of non-existent instance returns None."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        async with AsyncTestingSessionLocal() as session:
            result = await agent_instance_crud.update_status(
                session,
                instance_id=uuid.uuid4(),
                status=AgentStatus.WORKING,
            )
            assert result is None


class TestAgentInstanceTerminate:
    """Tests for agent instance termination."""

    @pytest.mark.asyncio
    async def test_terminate_agent_instance(self, async_test_db, test_project_crud, test_agent_type_crud):
        """Test terminating an agent instance."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        # Create an instance to terminate
        async with AsyncTestingSessionLocal() as session:
            instance_data = AgentInstanceCreate(
                agent_type_id=test_agent_type_crud.id,
                project_id=test_project_crud.id,
                name="TerminateBot",
                status=AgentStatus.WORKING,
            )
            created = await agent_instance_crud.create(session, obj_in=instance_data)
            instance_id = created.id

        # Terminate
        async with AsyncTestingSessionLocal() as session:
            result = await agent_instance_crud.terminate(session, instance_id=instance_id)

            assert result is not None
            assert result.status == AgentStatus.TERMINATED
            assert result.terminated_at is not None
            assert result.current_task is None
            assert result.session_id is None

    @pytest.mark.asyncio
    async def test_terminate_nonexistent_instance(self, async_test_db):
        """Test terminating non-existent instance returns None."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        async with AsyncTestingSessionLocal() as session:
            result = await agent_instance_crud.terminate(session, instance_id=uuid.uuid4())
            assert result is None


class TestAgentInstanceMetrics:
    """Tests for agent instance metrics operations."""

    @pytest.mark.asyncio
    async def test_record_task_completion(self, async_test_db, test_agent_instance_crud):
        """Test recording task completion with metrics."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        async with AsyncTestingSessionLocal() as session:
            result = await agent_instance_crud.record_task_completion(
                session,
                instance_id=test_agent_instance_crud.id,
                tokens_used=1500,
                cost_incurred=Decimal("0.0150"),
            )

            assert result is not None
            assert result.tasks_completed == 1
            assert result.tokens_used == 1500
            assert result.cost_incurred == Decimal("0.0150")
            assert result.last_activity_at is not None

    @pytest.mark.asyncio
    async def test_record_multiple_task_completions(self, async_test_db, test_project_crud, test_agent_type_crud):
        """Test recording multiple task completions accumulates metrics."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        # Create fresh instance
        async with AsyncTestingSessionLocal() as session:
            instance_data = AgentInstanceCreate(
                agent_type_id=test_agent_type_crud.id,
                project_id=test_project_crud.id,
                name="MetricsBot",
            )
            created = await agent_instance_crud.create(session, obj_in=instance_data)
            instance_id = created.id

        # Record first task
        async with AsyncTestingSessionLocal() as session:
            await agent_instance_crud.record_task_completion(
                session,
                instance_id=instance_id,
                tokens_used=1000,
                cost_incurred=Decimal("0.0100"),
            )

        # Record second task
        async with AsyncTestingSessionLocal() as session:
            result = await agent_instance_crud.record_task_completion(
                session,
                instance_id=instance_id,
                tokens_used=2000,
                cost_incurred=Decimal("0.0200"),
            )

            assert result.tasks_completed == 2
            assert result.tokens_used == 3000
            assert result.cost_incurred == Decimal("0.0300")

    @pytest.mark.asyncio
    async def test_get_project_metrics(self, async_test_db, test_project_crud, test_agent_instance_crud):
        """Test getting aggregated metrics for a project."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        async with AsyncTestingSessionLocal() as session:
            result = await agent_instance_crud.get_project_metrics(
                session,
                project_id=test_project_crud.id,
            )

            assert result is not None
            assert "total_instances" in result
            assert "active_instances" in result
            assert "idle_instances" in result
            assert "total_tasks_completed" in result
            assert "total_tokens_used" in result
            assert "total_cost_incurred" in result


class TestAgentInstanceByProject:
    """Tests for getting instances by project."""

    @pytest.mark.asyncio
    async def test_get_by_project(self, async_test_db, test_project_crud, test_agent_instance_crud):
        """Test getting instances by project."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        async with AsyncTestingSessionLocal() as session:
            instances, total = await agent_instance_crud.get_by_project(
                session,
                project_id=test_project_crud.id,
            )

            assert total >= 1
            assert all(i.project_id == test_project_crud.id for i in instances)

    @pytest.mark.asyncio
    async def test_get_by_project_with_status(self, async_test_db, test_project_crud, test_agent_type_crud):
        """Test getting instances by project filtered by status."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        # Create instances with different statuses
        async with AsyncTestingSessionLocal() as session:
            idle_instance = AgentInstanceCreate(
                agent_type_id=test_agent_type_crud.id,
                project_id=test_project_crud.id,
                name="IdleBot",
                status=AgentStatus.IDLE,
            )
            await agent_instance_crud.create(session, obj_in=idle_instance)

            working_instance = AgentInstanceCreate(
                agent_type_id=test_agent_type_crud.id,
                project_id=test_project_crud.id,
                name="WorkerBot",
                status=AgentStatus.WORKING,
            )
            await agent_instance_crud.create(session, obj_in=working_instance)

        async with AsyncTestingSessionLocal() as session:
            instances, _total = await agent_instance_crud.get_by_project(
                session,
                project_id=test_project_crud.id,
                status=AgentStatus.WORKING,
            )

            assert all(i.status == AgentStatus.WORKING for i in instances)


class TestAgentInstanceByAgentType:
    """Tests for getting instances by agent type."""

    @pytest.mark.asyncio
    async def test_get_by_agent_type(self, async_test_db, test_agent_type_crud, test_agent_instance_crud):
        """Test getting instances by agent type."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        async with AsyncTestingSessionLocal() as session:
            instances = await agent_instance_crud.get_by_agent_type(
                session,
                agent_type_id=test_agent_type_crud.id,
            )

            assert len(instances) >= 1
            assert all(i.agent_type_id == test_agent_type_crud.id for i in instances)


class TestBulkTerminate:
    """Tests for bulk termination of instances."""

    @pytest.mark.asyncio
    async def test_bulk_terminate_by_project(self, async_test_db, test_project_crud, test_agent_type_crud):
        """Test bulk terminating all instances in a project."""
        _test_engine, AsyncTestingSessionLocal = async_test_db

        # Create multiple instances
        async with AsyncTestingSessionLocal() as session:
            for i in range(3):
                instance_data = AgentInstanceCreate(
                    agent_type_id=test_agent_type_crud.id,
                    project_id=test_project_crud.id,
                    name=f"BulkBot-{i}",
                    status=AgentStatus.WORKING if i < 2 else AgentStatus.IDLE,
                )
                await agent_instance_crud.create(session, obj_in=instance_data)

        # Bulk terminate
        async with AsyncTestingSessionLocal() as session:
            count = await agent_instance_crud.bulk_terminate_by_project(
                session,
                project_id=test_project_crud.id,
            )

            assert count >= 3

        # Verify all are terminated
        async with AsyncTestingSessionLocal() as session:
            instances, _ = await agent_instance_crud.get_by_project(
                session,
                project_id=test_project_crud.id,
            )

            for instance in instances:
                assert instance.status == AgentStatus.TERMINATED
