"""Tests for AI & Analysis endpoints (test generation, root cause, PBT,
contract drift analysis)."""

from unittest.mock import AsyncMock, MagicMock, patch

_DB = "api.routers.ai.db"
_STORAGE = "api.routers.ai.storage"


class TestGenerateTest:
    """Tests for POST /v1/runs/{run_id}/generate-test."""

    def test_generate_test_success(self, client, auth_header, sample_run):
        run_id = sample_run["id"]
        with patch(f"{_DB}.get_run") as mock_run, \
             patch(f"{_DB}.get_project") as mock_project, \
             patch("autodebug.agent.test_generator.generate_tests_from_run") as mock_gen, \
             patch("autodebug.agent.test_generator.format_test_file", return_value="# test"):
            mock_run.return_value = sample_run
            mock_project.return_value = {
                "id": sample_run["project_id"],
                "org_id": sample_run["org_id"],
            }

            mock_test = MagicMock()
            mock_test.name = "test_login"
            mock_test.code = "def test_login(): pass"
            mock_test.fixtures = ["client"]
            mock_test.imports = ["import pytest"]
            mock_test.description = "Test login"
            mock_test.confidence = 0.9

            mock_result = MagicMock(
                tests=[mock_test],
                fixture_code="",
                conftest_additions="",
                errors=[],
            )
            mock_gen.return_value = mock_result

            response = client.post(
                f"/v1/runs/{run_id}/generate-test",
                headers=auth_header,
            )
            assert response.status_code == 200
            data = response.json()
            assert "tests" in data
            assert "full_file" in data

    def test_generate_test_run_not_found(self, client, auth_header):
        with patch(f"{_DB}.get_run") as mock_run:
            mock_run.return_value = None
            response = client.post(
                "/v1/runs/550e8400-e29b-41d4-a716-446655440000/generate-test",
                headers=auth_header,
            )
            assert response.status_code == 404

    def test_generate_test_unauthorized(self, client):
        response = client.post(
            "/v1/runs/550e8400-e29b-41d4-a716-446655440000/generate-test",
        )
        assert response.status_code == 401


class TestGeneratePBT:
    """Tests for POST /v1/runs/{run_id}/generate-pbt."""

    def test_generate_pbt_success(self, client, auth_header, sample_run):
        run_id = sample_run["id"]
        with patch(f"{_DB}.get_run") as mock_run, \
             patch(f"{_DB}.get_project") as mock_project, \
             patch("autodebug.agent.pbt_generator.generate_pbt_from_replay") as mock_gen, \
             patch("autodebug.agent.pbt_generator.format_pbt_file", return_value="# pbt"):
            mock_run.return_value = sample_run
            mock_project.return_value = {
                "id": sample_run["project_id"],
                "org_id": sample_run["org_id"],
            }

            mock_result = MagicMock(
                tests=[], properties=[], strategies=[],
                conftest_code="", errors=[],
            )
            mock_gen.return_value = mock_result

            response = client.post(
                f"/v1/runs/{run_id}/generate-pbt",
                headers=auth_header,
            )
            assert response.status_code == 200
            data = response.json()
            assert "tests" in data
            assert "properties" in data
            assert "strategies" in data

    def test_generate_pbt_run_not_found(self, client, auth_header):
        with patch(f"{_DB}.get_run") as mock_run:
            mock_run.return_value = None
            response = client.post(
                "/v1/runs/550e8400-e29b-41d4-a716-446655440000/generate-pbt",
                headers=auth_header,
            )
            assert response.status_code == 404

    def test_generate_pbt_unauthorized(self, client):
        response = client.post(
            "/v1/runs/550e8400-e29b-41d4-a716-446655440000/generate-pbt",
        )
        assert response.status_code == 401


class TestRootCauseAnalysis:
    """Tests for GET /v1/runs/{run_id}/root-cause."""

    def test_root_cause_cached(self, client, auth_header, sample_run):
        run_id = sample_run["id"]
        with patch(f"{_DB}.get_run") as mock_run, \
             patch(f"{_DB}.get_root_cause_analysis") as mock_cached, \
             patch("api.storage.get_artifact_content", return_value=""):
            mock_run.return_value = sample_run
            mock_cached.return_value = {
                "analyzer_version": "2.1.0",
                "summary": "Race condition in async handler",
                "category": "concurrency",
                "confidence": 0.85,
                "detailed_explanation": "The handler reads shared state without a lock.",
                "evidence": [
                    {"label": "Timing", "value": "Timing-dependent failure"},
                ],
                "recommended_fixes": [
                    {
                        "description": "Add async lock",
                        "diff": "--- a/handler.py\n+++ b/handler.py",
                        "confidence": 0.85,
                        "reasoning": "Prevents concurrent access",
                    },
                ],
            }
            response = client.get(
                f"/v1/runs/{run_id}/root-cause",
                headers=auth_header,
            )
            assert response.status_code == 200
            data = response.json()
            assert data["cached"] is True
            assert "summary" in data

    def test_root_cause_no_comparable(self, client, auth_header, sample_run):
        run_id = sample_run["id"]
        with patch(f"{_DB}.get_run") as mock_run, \
             patch(f"{_DB}.get_root_cause_analysis") as mock_cached, \
             patch(f"{_DB}.get_comparable_runs") as mock_comparable, \
             patch("api.storage.get_artifact_content", return_value=""), \
             patch("api.ai.analysis_cache.get_analysis_cache") as mock_l1:
            mock_l1.return_value.get.return_value = __import__("api.ai.analysis_cache", fromlist=["CACHE_MISS"]).CACHE_MISS
            mock_run.return_value = sample_run
            mock_cached.return_value = None
            mock_comparable.return_value = []
            response = client.get(
                f"/v1/runs/{run_id}/root-cause",
                headers=auth_header,
            )
            assert response.status_code == 400

    def test_root_cause_run_not_found(self, client, auth_header):
        with patch(f"{_DB}.get_run") as mock_run:
            mock_run.return_value = None
            response = client.get(
                "/v1/runs/550e8400-e29b-41d4-a716-446655440000/root-cause",
                headers=auth_header,
            )
            assert response.status_code == 404

    def test_root_cause_unauthorized(self, client):
        response = client.get(
            "/v1/runs/550e8400-e29b-41d4-a716-446655440000/root-cause",
        )
        assert response.status_code == 401


class TestContractDriftAnalyze:
    """Tests for POST /v1/contract-drift/analyze."""

    def test_analyze_run_not_found(self, client, auth_header):
        with patch(f"{_DB}.get_run") as mock_run:
            mock_run.return_value = None
            response = client.post(
                "/v1/contract-drift/analyze",
                json={
                    "run_id": "550e8400-e29b-41d4-a716-446655440000",
                },
                headers=auth_header,
            )
            assert response.status_code == 404

    def test_analyze_unauthorized(self, client):
        response = client.post(
            "/v1/contract-drift/analyze",
            json={"run_id": "550e8400-e29b-41d4-a716-446655440000"},
        )
        assert response.status_code == 401


class TestRunAccessDenied:
    """Tests for access control on AI endpoints."""

    def test_generate_test_wrong_org(self, client, auth_header):
        with patch(f"{_DB}.get_run") as mock_run, \
             patch(f"{_DB}.get_project") as mock_project:
            mock_run.return_value = {
                "id": "550e8400-e29b-41d4-a716-446655440000",
                "org_id": "different_org",
                "project_id": "660e8400-e29b-41d4-a716-446655440000",
            }
            mock_project.return_value = {
                "id": "660e8400-e29b-41d4-a716-446655440000",
                "org_id": "different_org",
            }
            response = client.post(
                "/v1/runs/550e8400-e29b-41d4-a716-446655440000/generate-test",
                headers=auth_header,
            )
            assert response.status_code == 404

    def test_root_cause_wrong_org(self, client, auth_header):
        with patch(f"{_DB}.get_run") as mock_run:
            mock_run.return_value = {
                "id": "550e8400-e29b-41d4-a716-446655440000",
                "org_id": "different_org",
            }
            response = client.get(
                "/v1/runs/550e8400-e29b-41d4-a716-446655440000/root-cause",
                headers=auth_header,
            )
            assert response.status_code == 404
