# tests/crud/syndarix/test_agent_type.py
"""Tests for AgentType CRUD operations."""

import uuid
from unittest.mock import MagicMock, patch

import pytest
import pytest_asyncio
from sqlalchemy.exc import IntegrityError, OperationalError

from app.crud.syndarix.agent_type import agent_type
from app.models.syndarix import AgentInstance, AgentType, Project
from app.models.syndarix.enums import AgentStatus, ProjectStatus
from app.schemas.syndarix import AgentTypeCreate


@pytest_asyncio.fixture
async def db_session(async_test_db):
    """Create a database session for tests."""
    _, AsyncTestingSessionLocal = async_test_db
    async with AsyncTestingSessionLocal() as session:
        yield session


@pytest_asyncio.fixture
async def test_agent_type(db_session):
    """Create a test agent type."""
    at = AgentType(
        id=uuid.uuid4(),
        name="Test Agent Type",
        slug=f"test-agent-type-{uuid.uuid4().hex[:8]}",
        primary_model="claude-3-opus",
        personality_prompt="You are a helpful test agent.",
        expertise=["python", "testing"],
        is_active=True,
    )
    db_session.add(at)
    await db_session.commit()
    await db_session.refresh(at)
    return at


class TestAgentTypeGetBySlug:
    """Tests for getting agent type by slug."""

    @pytest.mark.asyncio
    async def test_get_by_slug_not_found(self, db_session):
        """Test getting non-existent agent type by slug."""
        result = await agent_type.get_by_slug(db_session, slug="nonexistent")
        assert result is None

    @pytest.mark.asyncio
    async def test_get_by_slug_success(self, db_session, test_agent_type):
        """Test successfully getting agent type by slug."""
        result = await agent_type.get_by_slug(db_session, slug=test_agent_type.slug)
        assert result is not None
        assert result.id == test_agent_type.id

    @pytest.mark.asyncio
    async def test_get_by_slug_db_error(self, db_session):
        """Test getting agent type by slug when DB error occurs."""
        with patch.object(
            db_session,
            "execute",
            side_effect=OperationalError("Connection lost", {}, Exception()),
        ):
            with pytest.raises(OperationalError):
                await agent_type.get_by_slug(db_session, slug="test")


class TestAgentTypeCreate:
    """Tests for agent type creation."""

    @pytest.mark.asyncio
    async def test_create_agent_type_success(self, db_session):
        """Test successful agent type creation."""
        agent_type_data = AgentTypeCreate(
            name="New Agent Type",
            slug=f"new-agent-type-{uuid.uuid4().hex[:8]}",
            primary_model="claude-3-opus",
            personality_prompt="You are a new agent.",
        )
        created = await agent_type.create(db_session, obj_in=agent_type_data)
        assert created.name == "New Agent Type"

    @pytest.mark.asyncio
    async def test_create_agent_type_duplicate_slug(self, db_session, test_agent_type):
        """Test agent type creation with duplicate slug."""
        agent_type_data = AgentTypeCreate(
            name="Another Agent Type",
            slug=test_agent_type.slug,  # Use existing slug
            primary_model="claude-3-opus",
            personality_prompt="You are another agent.",
        )

        # Mock IntegrityError with slug in the message
        mock_orig = MagicMock()
        mock_orig.__str__ = lambda self: "duplicate key value violates unique constraint on slug"

        with patch.object(
            db_session,
            "commit",
            side_effect=IntegrityError("", {}, mock_orig),
        ):
            with pytest.raises(ValueError, match="already exists"):
                await agent_type.create(db_session, obj_in=agent_type_data)

    @pytest.mark.asyncio
    async def test_create_agent_type_integrity_error(self, db_session):
        """Test agent type creation with general integrity error."""
        agent_type_data = AgentTypeCreate(
            name="Test Agent Type",
            slug=f"test-{uuid.uuid4().hex[:8]}",
            primary_model="claude-3-opus",
            personality_prompt="You are a test agent.",
        )

        # Mock IntegrityError without slug in the message
        mock_orig = MagicMock()
        mock_orig.__str__ = lambda self: "foreign key constraint violation"

        with patch.object(
            db_session,
            "commit",
            side_effect=IntegrityError("", {}, mock_orig),
        ):
            with pytest.raises(ValueError, match="Database integrity error"):
                await agent_type.create(db_session, obj_in=agent_type_data)

    @pytest.mark.asyncio
    async def test_create_agent_type_unexpected_error(self, db_session):
        """Test agent type creation with unexpected error."""
        agent_type_data = AgentTypeCreate(
            name="Test Agent Type",
            slug=f"test-{uuid.uuid4().hex[:8]}",
            primary_model="claude-3-opus",
            personality_prompt="You are a test agent.",
        )

        with patch.object(
            db_session,
            "commit",
            side_effect=RuntimeError("Unexpected error"),
        ):
            with pytest.raises(RuntimeError, match="Unexpected error"):
                await agent_type.create(db_session, obj_in=agent_type_data)


class TestAgentTypeGetMultiWithFilters:
    """Tests for getting agent types with filters."""

    @pytest.mark.asyncio
    async def test_get_multi_with_filters_success(self, db_session, test_agent_type):
        """Test successfully getting agent types with filters."""
        results, total = await agent_type.get_multi_with_filters(db_session)
        assert total >= 1

    @pytest.mark.asyncio
    async def test_get_multi_with_filters_sort_asc(self, db_session, test_agent_type):
        """Test getting agent types with ascending sort order."""
        results, total = await agent_type.get_multi_with_filters(
            db_session,
            sort_by="created_at",
            sort_order="asc",
        )
        assert total >= 1

    @pytest.mark.asyncio
    async def test_get_multi_with_filters_db_error(self, db_session):
        """Test getting agent types when DB error occurs."""
        with patch.object(
            db_session,
            "execute",
            side_effect=OperationalError("Connection lost", {}, Exception()),
        ):
            with pytest.raises(OperationalError):
                await agent_type.get_multi_with_filters(db_session)


class TestAgentTypeGetWithInstanceCount:
    """Tests for getting agent type with instance count."""

    @pytest.mark.asyncio
    async def test_get_with_instance_count_not_found(self, db_session):
        """Test getting non-existent agent type with instance count."""
        result = await agent_type.get_with_instance_count(
            db_session, agent_type_id=uuid.uuid4()
        )
        assert result is None

    @pytest.mark.asyncio
    async def test_get_with_instance_count_success(self, db_session, test_agent_type):
        """Test successfully getting agent type with instance count."""
        result = await agent_type.get_with_instance_count(
            db_session, agent_type_id=test_agent_type.id
        )
        assert result is not None
        assert result["agent_type"].id == test_agent_type.id
        assert result["instance_count"] == 0

    @pytest.mark.asyncio
    async def test_get_with_instance_count_db_error(self, db_session, test_agent_type):
        """Test getting agent type with instance count when DB error occurs."""
        with patch.object(
            db_session,
            "execute",
            side_effect=OperationalError("Connection lost", {}, Exception()),
        ):
            with pytest.raises(OperationalError):
                await agent_type.get_with_instance_count(
                    db_session, agent_type_id=test_agent_type.id
                )


class TestAgentTypeGetMultiWithInstanceCounts:
    """Tests for getting agent types with instance counts."""

    @pytest.mark.asyncio
    async def test_get_multi_with_instance_counts_empty(self, db_session):
        """Test getting agent types with instance counts when none exist."""
        # Create a separate project to ensure isolation
        results, total = await agent_type.get_multi_with_instance_counts(
            db_session,
            is_active=None,
            search="nonexistent-xyz-query",
        )
        assert results == []
        assert total == 0

    @pytest.mark.asyncio
    async def test_get_multi_with_instance_counts_success(
        self, db_session, test_agent_type
    ):
        """Test successfully getting agent types with instance counts."""
        results, total = await agent_type.get_multi_with_instance_counts(db_session)
        assert total >= 1
        assert len(results) >= 1
        assert "agent_type" in results[0]
        assert "instance_count" in results[0]

    @pytest.mark.asyncio
    async def test_get_multi_with_instance_counts_db_error(
        self, db_session, test_agent_type
    ):
        """Test getting agent types with instance counts when DB error occurs."""
        with patch.object(
            db_session,
            "execute",
            side_effect=OperationalError("Connection lost", {}, Exception()),
        ):
            with pytest.raises(OperationalError):
                await agent_type.get_multi_with_instance_counts(db_session)


class TestAgentTypeGetByExpertise:
    """Tests for getting agent types by expertise."""

    @pytest.mark.asyncio
    @pytest.mark.skip(reason="Uses PostgreSQL JSONB contains operator, not available in SQLite")
    async def test_get_by_expertise_success(self, db_session, test_agent_type):
        """Test successfully getting agent types by expertise."""
        results = await agent_type.get_by_expertise(db_session, expertise="python")
        assert len(results) >= 1

    @pytest.mark.asyncio
    @pytest.mark.skip(reason="Uses PostgreSQL JSONB contains operator, not available in SQLite")
    async def test_get_by_expertise_db_error(self, db_session):
        """Test getting agent types by expertise when DB error occurs."""
        with patch.object(
            db_session,
            "execute",
            side_effect=OperationalError("Connection lost", {}, Exception()),
        ):
            with pytest.raises(OperationalError):
                await agent_type.get_by_expertise(db_session, expertise="python")


class TestAgentTypeDeactivate:
    """Tests for deactivating agent types."""

    @pytest.mark.asyncio
    async def test_deactivate_not_found(self, db_session):
        """Test deactivating non-existent agent type."""
        result = await agent_type.deactivate(db_session, agent_type_id=uuid.uuid4())
        assert result is None

    @pytest.mark.asyncio
    async def test_deactivate_success(self, db_session, test_agent_type):
        """Test successfully deactivating agent type."""
        result = await agent_type.deactivate(
            db_session, agent_type_id=test_agent_type.id
        )
        assert result is not None
        assert result.is_active is False

    @pytest.mark.asyncio
    async def test_deactivate_db_error(self, db_session, test_agent_type):
        """Test deactivating agent type when DB error occurs."""
        with patch.object(
            db_session,
            "commit",
            side_effect=OperationalError("Connection lost", {}, Exception()),
        ):
            with pytest.raises(OperationalError):
                await agent_type.deactivate(
                    db_session, agent_type_id=test_agent_type.id
                )
