# tests/api/routes/syndarix/test_agent_types.py
"""
Comprehensive tests for the AgentTypes API endpoints.

Tests cover:
- CRUD operations (create, read, update, deactivate)
- Authorization (superuser vs regular user)
- Pagination and filtering
- Error handling (not found, validation, duplicates)
- Slug lookup functionality
"""

import uuid

import pytest
import pytest_asyncio
from fastapi import status


@pytest_asyncio.fixture
async def test_agent_type(client, superuser_token):
    """Create a test agent type for tests."""
    unique_slug = f"test-type-{uuid.uuid4().hex[:8]}"
    response = await client.post(
        "/api/v1/agent-types",
        json={
            "name": "Test Agent Type",
            "slug": unique_slug,
            "description": "A test agent type for testing",
            "expertise": ["python", "testing"],
            "personality_prompt": "You are a helpful test agent.",
            "primary_model": "claude-3-opus",
            "fallback_models": ["claude-3-sonnet"],
            "model_params": {"temperature": 0.7},
            "mcp_servers": [],
            "tool_permissions": {"read": True, "write": False},
        },
        headers={"Authorization": f"Bearer {superuser_token}"},
    )
    assert response.status_code == status.HTTP_201_CREATED
    return response.json()


@pytest_asyncio.fixture
async def multiple_agent_types(client, superuser_token):
    """Create multiple agent types for pagination tests."""
    types = []
    for i in range(5):
        unique_slug = f"multi-type-{i}-{uuid.uuid4().hex[:8]}"
        response = await client.post(
            "/api/v1/agent-types",
            json={
                "name": f"Agent Type {i}",
                "slug": unique_slug,
                "description": f"Description for type {i}",
                "expertise": ["python"],
                "personality_prompt": f"Personality prompt {i}",
                "primary_model": "claude-3-opus",
            },
            headers={"Authorization": f"Bearer {superuser_token}"},
        )
        assert response.status_code == status.HTTP_201_CREATED
        types.append(response.json())
    return types


@pytest.mark.asyncio
class TestCreateAgentType:
    """Tests for POST /api/v1/agent-types endpoint."""

    async def test_create_agent_type_success(self, client, superuser_token):
        """Test successful agent type creation by superuser."""
        unique_slug = f"created-type-{uuid.uuid4().hex[:8]}"
        response = await client.post(
            "/api/v1/agent-types",
            json={
                "name": "New Agent Type",
                "slug": unique_slug,
                "description": "A newly created agent type",
                "expertise": ["python", "fastapi"],
                "personality_prompt": "You are a backend developer.",
                "primary_model": "claude-3-opus",
                "fallback_models": ["claude-3-sonnet"],
                "model_params": {"temperature": 0.5},
            },
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_201_CREATED
        data = response.json()

        assert data["name"] == "New Agent Type"
        assert data["slug"] == unique_slug
        assert data["description"] == "A newly created agent type"
        assert data["expertise"] == ["python", "fastapi"]
        assert data["personality_prompt"] == "You are a backend developer."
        assert data["primary_model"] == "claude-3-opus"
        assert data["fallback_models"] == ["claude-3-sonnet"]
        assert data["model_params"]["temperature"] == 0.5
        assert data["is_active"] is True
        assert data["instance_count"] == 0
        assert "id" in data
        assert "created_at" in data
        assert "updated_at" in data

    async def test_create_agent_type_minimal_fields(self, client, superuser_token):
        """Test creating agent type with only required fields."""
        unique_slug = f"minimal-type-{uuid.uuid4().hex[:8]}"
        response = await client.post(
            "/api/v1/agent-types",
            json={
                "name": "Minimal Agent Type",
                "slug": unique_slug,
                "expertise": ["general"],
                "personality_prompt": "You are a general assistant.",
                "primary_model": "claude-3-sonnet",
            },
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_201_CREATED
        data = response.json()
        assert data["name"] == "Minimal Agent Type"
        assert data["slug"] == unique_slug
        assert data["is_active"] is True

    async def test_create_agent_type_duplicate_slug(
        self, client, superuser_token, test_agent_type
    ):
        """Test that duplicate slugs are rejected."""
        existing_slug = test_agent_type["slug"]

        response = await client.post(
            "/api/v1/agent-types",
            json={
                "name": "Another Type",
                "slug": existing_slug,  # Duplicate slug
                "expertise": ["python"],
                "personality_prompt": "Prompt",
                "primary_model": "claude-3-opus",
            },
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_409_CONFLICT
        data = response.json()
        assert data["errors"][0]["code"] == "SYS_005"  # ALREADY_EXISTS
        assert data["errors"][0]["field"] == "slug"

    async def test_create_agent_type_regular_user_forbidden(self, client, user_token):
        """Test that regular users cannot create agent types."""
        unique_slug = f"forbidden-type-{uuid.uuid4().hex[:8]}"
        response = await client.post(
            "/api/v1/agent-types",
            json={
                "name": "Forbidden Type",
                "slug": unique_slug,
                "expertise": ["python"],
                "personality_prompt": "Prompt",
                "primary_model": "claude-3-opus",
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_403_FORBIDDEN

    async def test_create_agent_type_unauthenticated(self, client):
        """Test that unauthenticated users cannot create agent types."""
        response = await client.post(
            "/api/v1/agent-types",
            json={
                "name": "Unauth Type",
                "slug": "unauth-type",
                "expertise": ["python"],
                "personality_prompt": "Prompt",
                "primary_model": "claude-3-opus",
            },
        )

        assert response.status_code == status.HTTP_401_UNAUTHORIZED

    async def test_create_agent_type_validation_missing_name(
        self, client, superuser_token
    ):
        """Test validation error when name is missing."""
        response = await client.post(
            "/api/v1/agent-types",
            json={
                "slug": "no-name-type",
                "expertise": ["python"],
                "personality_prompt": "Prompt",
                "primary_model": "claude-3-opus",
            },
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY

    async def test_create_agent_type_validation_missing_primary_model(
        self, client, superuser_token
    ):
        """Test validation error when primary_model is missing."""
        unique_slug = f"no-model-type-{uuid.uuid4().hex[:8]}"
        response = await client.post(
            "/api/v1/agent-types",
            json={
                "name": "No Model Type",
                "slug": unique_slug,
                "expertise": ["python"],
                "personality_prompt": "Prompt",
                # Missing primary_model
            },
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY


@pytest.mark.asyncio
class TestListAgentTypes:
    """Tests for GET /api/v1/agent-types endpoint."""

    async def test_list_agent_types_success(
        self, client, user_token, multiple_agent_types
    ):
        """Test successful listing of agent types."""
        response = await client.get(
            "/api/v1/agent-types",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()

        assert "data" in data
        assert "pagination" in data
        assert len(data["data"]) >= 5
        assert data["pagination"]["total"] >= 5
        assert data["pagination"]["page"] == 1

    async def test_list_agent_types_pagination(
        self, client, user_token, multiple_agent_types
    ):
        """Test pagination of agent types."""
        response = await client.get(
            "/api/v1/agent-types",
            params={"page": 1, "limit": 2},
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()

        assert len(data["data"]) <= 2
        assert data["pagination"]["page_size"] <= 2
        assert data["pagination"]["page"] == 1

    async def test_list_agent_types_filter_active(
        self, client, user_token, test_agent_type
    ):
        """Test filtering by active status."""
        # Default: only active types
        response = await client.get(
            "/api/v1/agent-types",
            params={"is_active": True},
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()

        # All returned types should be active
        for agent_type in data["data"]:
            assert agent_type["is_active"] is True

    async def test_list_agent_types_search(
        self, client, user_token, multiple_agent_types
    ):
        """Test search functionality."""
        # Search for a specific type
        search_term = multiple_agent_types[0]["name"]
        response = await client.get(
            "/api/v1/agent-types",
            params={"search": search_term},
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert len(data["data"]) >= 1

    async def test_list_agent_types_unauthenticated(self, client):
        """Test that unauthenticated users cannot list agent types."""
        response = await client.get("/api/v1/agent-types")
        assert response.status_code == status.HTTP_401_UNAUTHORIZED


@pytest.mark.asyncio
class TestGetAgentType:
    """Tests for GET /api/v1/agent-types/{agent_type_id} endpoint."""

    async def test_get_agent_type_success(
        self, client, user_token, test_agent_type
    ):
        """Test successful retrieval of agent type by ID."""
        agent_type_id = test_agent_type["id"]

        response = await client.get(
            f"/api/v1/agent-types/{agent_type_id}",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()

        assert data["id"] == agent_type_id
        assert data["name"] == test_agent_type["name"]
        assert data["slug"] == test_agent_type["slug"]
        assert "instance_count" in data

    async def test_get_agent_type_not_found(self, client, user_token):
        """Test retrieval of non-existent agent type."""
        fake_id = str(uuid.uuid4())

        response = await client.get(
            f"/api/v1/agent-types/{fake_id}",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_404_NOT_FOUND
        data = response.json()
        assert data["errors"][0]["code"] == "SYS_002"  # NOT_FOUND

    async def test_get_agent_type_invalid_uuid(self, client, user_token):
        """Test retrieval with invalid UUID format."""
        response = await client.get(
            "/api/v1/agent-types/not-a-uuid",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY

    async def test_get_agent_type_unauthenticated(self, client, test_agent_type):
        """Test that unauthenticated users cannot get agent types."""
        agent_type_id = test_agent_type["id"]

        response = await client.get(f"/api/v1/agent-types/{agent_type_id}")

        assert response.status_code == status.HTTP_401_UNAUTHORIZED


@pytest.mark.asyncio
class TestGetAgentTypeBySlug:
    """Tests for GET /api/v1/agent-types/slug/{slug} endpoint."""

    async def test_get_agent_type_by_slug_success(
        self, client, user_token, test_agent_type
    ):
        """Test successful retrieval of agent type by slug."""
        slug = test_agent_type["slug"]

        response = await client.get(
            f"/api/v1/agent-types/slug/{slug}",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()

        assert data["slug"] == slug
        assert data["id"] == test_agent_type["id"]
        assert data["name"] == test_agent_type["name"]

    async def test_get_agent_type_by_slug_not_found(self, client, user_token):
        """Test retrieval of non-existent slug."""
        response = await client.get(
            "/api/v1/agent-types/slug/non-existent-slug",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_404_NOT_FOUND
        data = response.json()
        assert data["errors"][0]["code"] == "SYS_002"  # NOT_FOUND
        assert "non-existent-slug" in data["errors"][0]["message"]

    async def test_get_agent_type_by_slug_unauthenticated(self, client, test_agent_type):
        """Test that unauthenticated users cannot get agent types by slug."""
        slug = test_agent_type["slug"]

        response = await client.get(f"/api/v1/agent-types/slug/{slug}")

        assert response.status_code == status.HTTP_401_UNAUTHORIZED


@pytest.mark.asyncio
class TestUpdateAgentType:
    """Tests for PATCH /api/v1/agent-types/{agent_type_id} endpoint."""

    async def test_update_agent_type_success(
        self, client, superuser_token, test_agent_type
    ):
        """Test successful update of agent type."""
        agent_type_id = test_agent_type["id"]

        response = await client.patch(
            f"/api/v1/agent-types/{agent_type_id}",
            json={
                "name": "Updated Agent Type",
                "description": "Updated description",
                "expertise": ["python", "fastapi", "testing"],
            },
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()

        assert data["id"] == agent_type_id
        assert data["name"] == "Updated Agent Type"
        assert data["description"] == "Updated description"
        assert data["expertise"] == ["python", "fastapi", "testing"]
        # Slug should remain unchanged
        assert data["slug"] == test_agent_type["slug"]

    async def test_update_agent_type_partial(
        self, client, superuser_token, test_agent_type
    ):
        """Test partial update of agent type."""
        agent_type_id = test_agent_type["id"]

        response = await client.patch(
            f"/api/v1/agent-types/{agent_type_id}",
            json={"description": "Only description updated"},
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()

        assert data["description"] == "Only description updated"
        # Other fields remain unchanged
        assert data["name"] == test_agent_type["name"]

    async def test_update_agent_type_slug(
        self, client, superuser_token, test_agent_type
    ):
        """Test updating agent type slug."""
        agent_type_id = test_agent_type["id"]
        new_slug = f"updated-slug-{uuid.uuid4().hex[:8]}"

        response = await client.patch(
            f"/api/v1/agent-types/{agent_type_id}",
            json={"slug": new_slug},
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert data["slug"] == new_slug

    async def test_update_agent_type_duplicate_slug(
        self, client, superuser_token, multiple_agent_types
    ):
        """Test that updating to an existing slug fails."""
        # Try to update first type's slug to second type's slug
        first_type_id = multiple_agent_types[0]["id"]
        second_type_slug = multiple_agent_types[1]["slug"]

        response = await client.patch(
            f"/api/v1/agent-types/{first_type_id}",
            json={"slug": second_type_slug},
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_409_CONFLICT
        data = response.json()
        assert data["errors"][0]["code"] == "SYS_005"  # ALREADY_EXISTS

    async def test_update_agent_type_not_found(self, client, superuser_token):
        """Test updating non-existent agent type."""
        fake_id = str(uuid.uuid4())

        response = await client.patch(
            f"/api/v1/agent-types/{fake_id}",
            json={"name": "Updated Name"},
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_404_NOT_FOUND
        data = response.json()
        assert data["errors"][0]["code"] == "SYS_002"  # NOT_FOUND

    async def test_update_agent_type_regular_user_forbidden(
        self, client, user_token, test_agent_type
    ):
        """Test that regular users cannot update agent types."""
        agent_type_id = test_agent_type["id"]

        response = await client.patch(
            f"/api/v1/agent-types/{agent_type_id}",
            json={"name": "Forbidden Update"},
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_403_FORBIDDEN

    async def test_update_agent_type_unauthenticated(self, client, test_agent_type):
        """Test that unauthenticated users cannot update agent types."""
        agent_type_id = test_agent_type["id"]

        response = await client.patch(
            f"/api/v1/agent-types/{agent_type_id}",
            json={"name": "Unauth Update"},
        )

        assert response.status_code == status.HTTP_401_UNAUTHORIZED


@pytest.mark.asyncio
class TestDeactivateAgentType:
    """Tests for DELETE /api/v1/agent-types/{agent_type_id} endpoint."""

    async def test_deactivate_agent_type_success(self, client, superuser_token):
        """Test successful deactivation of agent type."""
        # Create a type to deactivate
        unique_slug = f"deactivate-type-{uuid.uuid4().hex[:8]}"
        create_response = await client.post(
            "/api/v1/agent-types",
            json={
                "name": "Type to Deactivate",
                "slug": unique_slug,
                "expertise": ["python"],
                "personality_prompt": "Prompt",
                "primary_model": "claude-3-opus",
            },
            headers={"Authorization": f"Bearer {superuser_token}"},
        )
        assert create_response.status_code == status.HTTP_201_CREATED
        agent_type_id = create_response.json()["id"]

        # Deactivate it
        response = await client.delete(
            f"/api/v1/agent-types/{agent_type_id}",
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert data["success"] is True
        assert "deactivated" in data["message"].lower()

        # Verify it's deactivated by checking is_active filter
        get_response = await client.get(
            f"/api/v1/agent-types/{agent_type_id}",
            headers={"Authorization": f"Bearer {superuser_token}"},
        )
        assert get_response.status_code == status.HTTP_200_OK
        assert get_response.json()["is_active"] is False

    async def test_deactivate_agent_type_not_found(self, client, superuser_token):
        """Test deactivating non-existent agent type."""
        fake_id = str(uuid.uuid4())

        response = await client.delete(
            f"/api/v1/agent-types/{fake_id}",
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_404_NOT_FOUND
        data = response.json()
        assert data["errors"][0]["code"] == "SYS_002"  # NOT_FOUND

    async def test_deactivate_agent_type_regular_user_forbidden(
        self, client, user_token, test_agent_type
    ):
        """Test that regular users cannot deactivate agent types."""
        agent_type_id = test_agent_type["id"]

        response = await client.delete(
            f"/api/v1/agent-types/{agent_type_id}",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_403_FORBIDDEN

    async def test_deactivate_agent_type_unauthenticated(self, client, test_agent_type):
        """Test that unauthenticated users cannot deactivate agent types."""
        agent_type_id = test_agent_type["id"]

        response = await client.delete(f"/api/v1/agent-types/{agent_type_id}")

        assert response.status_code == status.HTTP_401_UNAUTHORIZED

    async def test_deactivate_agent_type_idempotent(self, client, superuser_token):
        """Test that deactivating an already deactivated type returns 404."""
        # Create and deactivate a type
        unique_slug = f"idempotent-type-{uuid.uuid4().hex[:8]}"
        create_response = await client.post(
            "/api/v1/agent-types",
            json={
                "name": "Type to Deactivate Twice",
                "slug": unique_slug,
                "expertise": ["python"],
                "personality_prompt": "Prompt",
                "primary_model": "claude-3-opus",
            },
            headers={"Authorization": f"Bearer {superuser_token}"},
        )
        agent_type_id = create_response.json()["id"]

        # First deactivation
        await client.delete(
            f"/api/v1/agent-types/{agent_type_id}",
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        # Second deactivation should fail (already deactivated)
        response = await client.delete(
            f"/api/v1/agent-types/{agent_type_id}",
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        # Depending on implementation, this might return 404 or 200
        # Check implementation for expected behavior
        assert response.status_code in [
            status.HTTP_200_OK,
            status.HTTP_404_NOT_FOUND,
        ]


@pytest.mark.asyncio
class TestAgentTypeModelParams:
    """Tests for model configuration fields."""

    async def test_create_with_full_model_config(self, client, superuser_token):
        """Test creating agent type with complete model configuration."""
        unique_slug = f"full-config-{uuid.uuid4().hex[:8]}"
        response = await client.post(
            "/api/v1/agent-types",
            json={
                "name": "Full Config Type",
                "slug": unique_slug,
                "description": "Type with full model config",
                "expertise": ["coding", "architecture"],
                "personality_prompt": "You are an expert architect.",
                "primary_model": "claude-3-opus",
                "fallback_models": ["claude-3-sonnet", "claude-3-haiku"],
                "model_params": {
                    "temperature": 0.3,
                    "max_tokens": 4096,
                    "top_p": 0.9,
                },
                "mcp_servers": ["filesystem", "git"],  # List of strings, not objects
                "tool_permissions": {
                    "read_files": True,
                    "write_files": True,
                    "execute_code": False,
                },
            },
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_201_CREATED
        data = response.json()

        assert data["primary_model"] == "claude-3-opus"
        assert data["fallback_models"] == ["claude-3-sonnet", "claude-3-haiku"]
        assert data["model_params"]["temperature"] == 0.3
        assert data["model_params"]["max_tokens"] == 4096
        assert len(data["mcp_servers"]) == 2
        assert data["tool_permissions"]["read_files"] is True
        assert data["tool_permissions"]["execute_code"] is False

    async def test_update_model_params(
        self, client, superuser_token, test_agent_type
    ):
        """Test updating model parameters."""
        agent_type_id = test_agent_type["id"]

        response = await client.patch(
            f"/api/v1/agent-types/{agent_type_id}",
            json={
                "model_params": {"temperature": 0.9, "max_tokens": 2048},
                "fallback_models": ["claude-3-haiku"],
            },
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()

        assert data["model_params"]["temperature"] == 0.9
        assert data["fallback_models"] == ["claude-3-haiku"]


@pytest.mark.asyncio
class TestAgentTypeInstanceCount:
    """Tests for instance count tracking."""

    async def test_new_agent_type_has_zero_instances(
        self, client, superuser_token
    ):
        """Test that newly created agent types have zero instances."""
        unique_slug = f"zero-instances-{uuid.uuid4().hex[:8]}"
        response = await client.post(
            "/api/v1/agent-types",
            json={
                "name": "Zero Instances Type",
                "slug": unique_slug,
                "expertise": ["python"],
                "personality_prompt": "Prompt",
                "primary_model": "claude-3-opus",
            },
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_201_CREATED
        data = response.json()
        assert data["instance_count"] == 0

    async def test_get_agent_type_includes_instance_count(
        self, client, user_token, test_agent_type
    ):
        """Test that getting an agent type includes instance count."""
        agent_type_id = test_agent_type["id"]

        response = await client.get(
            f"/api/v1/agent-types/{agent_type_id}",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert "instance_count" in data
        assert isinstance(data["instance_count"], int)

    async def test_list_agent_types_includes_instance_counts(
        self, client, user_token, test_agent_type
    ):
        """Test that listing agent types includes instance counts."""
        response = await client.get(
            "/api/v1/agent-types",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()

        for agent_type in data["data"]:
            assert "instance_count" in agent_type
            assert isinstance(agent_type["instance_count"], int)
