"""
Integration tests for portal agent CRUD endpoints (PRTA-02).

Tests: create (all fields / minimal fields), list (tenant isolation), get, update,
delete. Proves all Agent Designer fields are stored and retrievable.
"""

from __future__ import annotations

import uuid

import pytest
import pytest_asyncio
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession

from shared.api.portal import portal_router
from shared.db import get_session


def make_app(session: AsyncSession) -> FastAPI:
    """Build a FastAPI test app with the portal router and session override."""
    app = FastAPI()
    app.include_router(portal_router)

    async def override_get_session():  # type: ignore[return]
        yield session

    app.dependency_overrides[get_session] = override_get_session
    return app


@pytest_asyncio.fixture
async def client(db_session: AsyncSession):  # type: ignore[no-untyped-def]
    """HTTP client wired to the portal API with the test DB session."""
    app = make_app(db_session)
    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
        yield c


@pytest_asyncio.fixture
async def tenant_id(client: AsyncClient) -> str:
    """Create a tenant and return its ID for agent tests."""
    slug = f"agent-test-{uuid.uuid4().hex[:8]}"
    resp = await client.post(
        "/api/portal/tenants",
        json={"name": f"Agent Test Tenant {slug}", "slug": slug},
    )
    assert resp.status_code == 201
    return resp.json()["id"]


@pytest_asyncio.fixture
async def second_tenant_id(client: AsyncClient) -> str:
    """Create a second tenant for isolation tests."""
    slug = f"agent-test2-{uuid.uuid4().hex[:8]}"
    resp = await client.post(
        "/api/portal/tenants",
        json={"name": f"Agent Test Tenant 2 {slug}", "slug": slug},
    )
    assert resp.status_code == 201
    return resp.json()["id"]


@pytest.mark.asyncio
class TestAgentCreate:
    async def test_create_agent_all_fields_returns_201(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        """All Agent Designer fields are accepted and returned."""
        resp = await client.post(
            f"/api/portal/tenants/{tenant_id}/agents",
            json={
                "name": "Mara",
                "role": "Customer Support Lead",
                "persona": "Professional, empathetic, solution-oriented.",
                "system_prompt": "You are a helpful customer support agent named Mara.",
                "model_preference": "quality",
                "tool_assignments": ["zendesk_ticket_create", "knowledge_base_search"],
                "escalation_rules": [
                    {"condition": "billing_dispute AND attempts > 2", "action": "handoff_human"},
                    {"condition": "sentiment < -0.7", "action": "handoff_human"},
                ],
                "is_active": True,
            },
        )
        assert resp.status_code == 201
        data = resp.json()
        assert data["name"] == "Mara"
        assert data["role"] == "Customer Support Lead"
        assert data["persona"] == "Professional, empathetic, solution-oriented."
        assert data["system_prompt"] == "You are a helpful customer support agent named Mara."
        assert data["model_preference"] == "quality"
        assert data["tool_assignments"] == ["zendesk_ticket_create", "knowledge_base_search"]
        assert len(data["escalation_rules"]) == 2
        assert data["is_active"] is True
        assert data["tenant_id"] == tenant_id
        assert "id" in data
        assert "created_at" in data

    async def test_create_agent_minimal_fields_returns_201_with_defaults(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        """Creating with only name + role uses sensible defaults."""
        resp = await client.post(
            f"/api/portal/tenants/{tenant_id}/agents",
            json={"name": "Sam", "role": "Sales Representative"},
        )
        assert resp.status_code == 201
        data = resp.json()
        assert data["name"] == "Sam"
        assert data["role"] == "Sales Representative"
        assert data["persona"] == ""
        assert data["system_prompt"] == ""
        assert data["model_preference"] == "quality"
        assert data["tool_assignments"] == []
        assert data["escalation_rules"] == []
        assert data["is_active"] is True

    async def test_create_agent_invalid_tenant_returns_404(
        self, client: AsyncClient
    ) -> None:
        resp = await client.post(
            f"/api/portal/tenants/{uuid.uuid4()}/agents",
            json={"name": "Ghost", "role": "Nobody"},
        )
        assert resp.status_code == 404

    async def test_create_agent_missing_name_returns_422(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        resp = await client.post(
            f"/api/portal/tenants/{tenant_id}/agents",
            json={"role": "Some Role"},
        )
        assert resp.status_code == 422

    async def test_create_agent_missing_role_returns_422(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        resp = await client.post(
            f"/api/portal/tenants/{tenant_id}/agents",
            json={"name": "Some Name"},
        )
        assert resp.status_code == 422

    async def test_create_agent_invalid_model_preference_returns_422(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        resp = await client.post(
            f"/api/portal/tenants/{tenant_id}/agents",
            json={"name": "Bad Model", "role": "Tester", "model_preference": "super-mega-mode"},
        )
        assert resp.status_code == 422


@pytest.mark.asyncio
class TestAgentList:
    async def test_list_agents_returns_only_that_tenants_agents(
        self, client: AsyncClient, tenant_id: str, second_tenant_id: str
    ) -> None:
        """Agents from one tenant must NOT appear in another tenant's list."""
        # Create agent for tenant 1
        await client.post(
            f"/api/portal/tenants/{tenant_id}/agents",
            json={"name": "Tenant1 Agent", "role": "Worker"},
        )
        # Create agent for tenant 2
        await client.post(
            f"/api/portal/tenants/{second_tenant_id}/agents",
            json={"name": "Tenant2 Agent", "role": "Worker"},
        )

        resp = await client.get(f"/api/portal/tenants/{tenant_id}/agents")
        assert resp.status_code == 200
        agents = resp.json()
        names = [a["name"] for a in agents]
        assert "Tenant1 Agent" in names
        assert "Tenant2 Agent" not in names
        for agent in agents:
            assert agent["tenant_id"] == tenant_id

    async def test_list_agents_empty_for_new_tenant(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        resp = await client.get(f"/api/portal/tenants/{tenant_id}/agents")
        assert resp.status_code == 200
        assert resp.json() == []

    async def test_list_agents_invalid_tenant_returns_404(
        self, client: AsyncClient
    ) -> None:
        resp = await client.get(f"/api/portal/tenants/{uuid.uuid4()}/agents")
        assert resp.status_code == 404


@pytest.mark.asyncio
class TestAgentGet:
    async def test_get_agent_by_id(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        create_resp = await client.post(
            f"/api/portal/tenants/{tenant_id}/agents",
            json={"name": "Get Me", "role": "Fetcher"},
        )
        agent_id = create_resp.json()["id"]

        resp = await client.get(f"/api/portal/tenants/{tenant_id}/agents/{agent_id}")
        assert resp.status_code == 200
        assert resp.json()["id"] == agent_id
        assert resp.json()["name"] == "Get Me"

    async def test_get_agent_not_found_returns_404(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        resp = await client.get(f"/api/portal/tenants/{tenant_id}/agents/{uuid.uuid4()}")
        assert resp.status_code == 404

    async def test_get_agent_wrong_tenant_returns_404(
        self, client: AsyncClient, tenant_id: str, second_tenant_id: str
    ) -> None:
        """Agent from tenant1 should not be accessible via tenant2's path."""
        create_resp = await client.post(
            f"/api/portal/tenants/{tenant_id}/agents",
            json={"name": "Secret Agent", "role": "Spy"},
        )
        agent_id = create_resp.json()["id"]

        resp = await client.get(f"/api/portal/tenants/{second_tenant_id}/agents/{agent_id}")
        assert resp.status_code == 404


@pytest.mark.asyncio
class TestAgentUpdate:
    async def test_update_agent_persona_and_system_prompt(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        create_resp = await client.post(
            f"/api/portal/tenants/{tenant_id}/agents",
            json={"name": "Updatable", "role": "Updater"},
        )
        agent_id = create_resp.json()["id"]

        resp = await client.put(
            f"/api/portal/tenants/{tenant_id}/agents/{agent_id}",
            json={
                "persona": "New persona description",
                "system_prompt": "New system prompt content",
            },
        )
        assert resp.status_code == 200
        data = resp.json()
        assert data["persona"] == "New persona description"
        assert data["system_prompt"] == "New system prompt content"
        assert data["name"] == "Updatable"  # unchanged

    async def test_update_agent_tool_assignments(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        create_resp = await client.post(
            f"/api/portal/tenants/{tenant_id}/agents",
            json={"name": "Tool Updater", "role": "Tester", "tool_assignments": ["tool_a"]},
        )
        agent_id = create_resp.json()["id"]

        resp = await client.put(
            f"/api/portal/tenants/{tenant_id}/agents/{agent_id}",
            json={"tool_assignments": ["tool_a", "tool_b", "tool_c"]},
        )
        assert resp.status_code == 200
        assert resp.json()["tool_assignments"] == ["tool_a", "tool_b", "tool_c"]

    async def test_update_agent_escalation_rules(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        create_resp = await client.post(
            f"/api/portal/tenants/{tenant_id}/agents",
            json={"name": "Escalation Test", "role": "Support"},
        )
        agent_id = create_resp.json()["id"]

        rules = [{"condition": "urgent", "action": "escalate_to_manager"}]
        resp = await client.put(
            f"/api/portal/tenants/{tenant_id}/agents/{agent_id}",
            json={"escalation_rules": rules},
        )
        assert resp.status_code == 200
        assert resp.json()["escalation_rules"] == rules

    async def test_update_agent_not_found_returns_404(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        resp = await client.put(
            f"/api/portal/tenants/{tenant_id}/agents/{uuid.uuid4()}",
            json={"name": "Ghost"},
        )
        assert resp.status_code == 404


@pytest.mark.asyncio
class TestAgentDelete:
    async def test_delete_agent_returns_204(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        create_resp = await client.post(
            f"/api/portal/tenants/{tenant_id}/agents",
            json={"name": "Delete Me", "role": "Doomed"},
        )
        agent_id = create_resp.json()["id"]

        resp = await client.delete(f"/api/portal/tenants/{tenant_id}/agents/{agent_id}")
        assert resp.status_code == 204

    async def test_deleted_agent_is_gone(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        create_resp = await client.post(
            f"/api/portal/tenants/{tenant_id}/agents",
            json={"name": "Gone Soon", "role": "Temporary"},
        )
        agent_id = create_resp.json()["id"]

        await client.delete(f"/api/portal/tenants/{tenant_id}/agents/{agent_id}")
        get_resp = await client.get(f"/api/portal/tenants/{tenant_id}/agents/{agent_id}")
        assert get_resp.status_code == 404

    async def test_delete_agent_not_found_returns_404(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        resp = await client.delete(f"/api/portal/tenants/{tenant_id}/agents/{uuid.uuid4()}")
        assert resp.status_code == 404


@pytest.mark.asyncio
class TestAgentDesignerFields:
    """
    Explicitly prove all Agent Designer fields are stored and retrievable.
    This is the end-to-end round-trip test for PRTA-02.
    """

    async def test_all_agent_designer_fields_stored_and_retrievable(
        self, client: AsyncClient, tenant_id: str
    ) -> None:
        payload = {
            "name": "Mara Chen",
            "role": "Head of Customer Experience",
            "persona": (
                "Empathetic, data-driven, and relentlessly focused on customer success. "
                "Fluent in English, Spanish, and Mandarin. "
                "Never escalates without first attempting resolution."
            ),
            "system_prompt": (
                "You are Mara Chen, Head of Customer Experience at Acme Corp. "
                "Your goal is to resolve customer issues efficiently and with empathy."
            ),
            "model_preference": "quality",
            "tool_assignments": [
                "zendesk_ticket_create",
                "zendesk_ticket_update",
                "knowledge_base_search",
                "calendar_book",
                "send_email",
            ],
            "escalation_rules": [
                {"condition": "billing_dispute AND resolution_attempts > 2", "action": "handoff_human"},
                {"condition": "customer_sentiment < -0.7", "action": "handoff_human"},
                {"condition": "legal_threat_detected", "action": "handoff_legal_team"},
            ],
            "is_active": True,
        }
        create_resp = await client.post(f"/api/portal/tenants/{tenant_id}/agents", json=payload)
        assert create_resp.status_code == 201
        agent_id = create_resp.json()["id"]

        # Fetch and verify all fields round-trip correctly
        get_resp = await client.get(f"/api/portal/tenants/{tenant_id}/agents/{agent_id}")
        assert get_resp.status_code == 200
        data = get_resp.json()

        assert data["name"] == payload["name"]
        assert data["role"] == payload["role"]
        assert data["persona"] == payload["persona"]
        assert data["system_prompt"] == payload["system_prompt"]
        assert data["model_preference"] == payload["model_preference"]
        assert data["tool_assignments"] == payload["tool_assignments"]
        assert data["escalation_rules"] == payload["escalation_rules"]
        assert data["is_active"] == payload["is_active"]
        assert data["tenant_id"] == tenant_id
