"""Integration tests — full server evaluation lifecycle without an LLM.

Uses the answer_similarity suite (deterministic metrics) so evaluations
actually complete end-to-end against an in-memory SQLite database.
No API keys required.
"""

from __future__ import annotations

import time

import pytest

# -- Helpers -------------------------------------------------------------------

TEST_CASES = [
    {
        "question": "What is the capital of France?",
        "answer": "The capital of France is Paris.",
        "expected_answer": "Paris",
    },
    {
        "question": "Who created Python?",
        "answer": "Guido van Rossum created Python.",
        "expected_answer": "Guido van Rossum",
    },
    {
        "question": "What year was Linux created?",
        "answer": "Linux was created in 1991.",
        "expected_answer": "1991",
    },
]


def _create_project(client, name="Test Project"):
    resp = client.post("/api/v1/projects", json={"name": name})
    assert resp.status_code == 201
    return resp.json()


def _submit_evaluation(client, project_id, test_cases=None, suite="answer_similarity"):
    resp = client.post(
        "/api/v1/evaluate",
        json={
            "project_id": project_id,
            "suite": suite,
            "test_cases": test_cases or TEST_CASES,
        },
    )
    return resp


def _wait_for_evaluation(client, evaluation_id, timeout=10):
    """Poll until evaluation completes or times out."""
    start = time.monotonic()
    while time.monotonic() - start < timeout:
        resp = client.get(f"/api/v1/evaluations/{evaluation_id}")
        data = resp.json()
        if data["status"] in ("complete", "failed"):
            return data
        time.sleep(0.1)
    raise TimeoutError(f"Evaluation {evaluation_id} did not complete within {timeout}s")


# -- Full lifecycle tests ------------------------------------------------------


class TestEvaluationLifecycle:
    """End-to-end: create project → submit evaluation → poll → verify results."""

    def test_full_evaluation_completes(self, client):
        project = _create_project(client, "lifecycle-project")
        resp = _submit_evaluation(client, project["id"])
        assert resp.status_code == 202
        evaluation_id = resp.json()["evaluation_id"]

        result = _wait_for_evaluation(client, evaluation_id)
        assert result["status"] == "complete"
        assert result["suite_name"] == "answer_similarity"
        assert result["average_score"] is not None
        assert 0.0 <= result["average_score"] <= 1.0

    def test_evaluation_scores_are_reasonable(self, client):
        project = _create_project(client, "scores-project")
        resp = _submit_evaluation(client, project["id"])
        result = _wait_for_evaluation(client, resp.json()["evaluation_id"])

        # string_containment should find "Paris" in "The capital of France is Paris."
        assert result["average_score"] > 0

    def test_evaluation_has_tags(self, client):
        project = _create_project(client, "tags-lifecycle-project")
        resp = client.post(
            "/api/v1/evaluate",
            json={
                "project_id": project["id"],
                "suite": "answer_similarity",
                "test_cases": TEST_CASES[:1],
                "tags": {"env": "test"},
            },
        )
        result = _wait_for_evaluation(client, resp.json()["evaluation_id"])
        assert result["tags"]["suite"] == "answer_similarity"
        assert result["tags"]["env"] == "test"

    def test_multiple_evaluations_on_same_project(self, client):
        project = _create_project(client, "multi-eval-project")

        ids = []
        for _ in range(3):
            resp = _submit_evaluation(client, project["id"], test_cases=TEST_CASES[:1])
            ids.append(resp.json()["evaluation_id"])

        for eid in ids:
            result = _wait_for_evaluation(client, eid)
            assert result["status"] == "complete"


# -- Upload results (pre-computed) ---------------------------------------------


class TestUploadResults:
    """Upload locally-computed results and verify they're stored correctly."""

    def test_upload_and_retrieve(self, client):
        project = _create_project(client, "upload-project")
        resp = client.post(
            "/api/v1/results",
            json={
                "project_id": project["id"],
                "suite_name": "answer_similarity",
                "results": [
                    {
                        "test_case": TEST_CASES[0],
                        "scores": [
                            {"metric_name": "exact_match", "score": 0.0, "passed": False, "reason": "No match"},
                            {"metric_name": "string_containment", "score": 1.0, "passed": True, "reason": "Found"},
                            {"metric_name": "string_similarity", "score": 0.45, "passed": False, "reason": "Low"},
                        ],
                        "overall_score": 0.48,
                    },
                ],
                "average_score": 0.48,
            },
        )
        assert resp.status_code == 201
        evaluation_id = resp.json()["evaluation_id"]

        status = client.get(f"/api/v1/evaluations/{evaluation_id}")
        assert status.json()["status"] == "complete"
        assert status.json()["average_score"] == pytest.approx(0.48)

    def test_upload_report_downloadable(self, client):
        project = _create_project(client, "download-project")
        resp = client.post(
            "/api/v1/results",
            json={
                "project_id": project["id"],
                "suite_name": "answer_similarity",
                "results": [
                    {
                        "test_case": TEST_CASES[0],
                        "scores": [
                            {"metric_name": "exact_match", "score": 1.0, "passed": True, "reason": "Match"},
                        ],
                        "overall_score": 1.0,
                    },
                ],
            },
        )
        evaluation_id = resp.json()["evaluation_id"]

        report = client.get(f"/evaluations/{evaluation_id}/report")
        assert report.status_code == 200
        data = report.json()
        assert data["suite_name"] == "answer_similarity"
        assert len(data["results"]) == 1
        assert data["results"][0]["metric_scores"][0]["metric_name"] == "exact_match"


# -- Dashboard HTML endpoints -------------------------------------------------


class TestDashboardPages:
    """Verify HTML pages render correctly with evaluation data."""

    def test_dashboard_lists_projects(self, client):
        _create_project(client, "dashboard-project")
        resp = client.get("/")
        assert resp.status_code == 200
        assert "dashboard-project" in resp.text

    def test_project_page_shows_evaluations(self, client):
        project = _create_project(client, "page-project")
        resp = _submit_evaluation(client, project["id"], test_cases=TEST_CASES[:1])
        _wait_for_evaluation(client, resp.json()["evaluation_id"])

        page = client.get(f"/projects/{project['id']}")
        assert page.status_code == 200
        assert "answer_similarity" in page.text
        assert "complete" in page.text

    def test_project_page_pagination(self, client):
        project = _create_project(client, "pagination-project")
        # Create 12 evaluations (page size is 10)
        for _ in range(12):
            client.post(
                "/api/v1/results",
                json={
                    "project_id": project["id"],
                    "suite_name": "answer_similarity",
                    "results": [
                        {
                            "test_case": TEST_CASES[0],
                            "scores": [{"metric_name": "exact_match", "score": 1.0, "passed": True, "reason": "ok"}],
                            "overall_score": 1.0,
                        }
                    ],
                },
            )

        # Page 1 should have pagination controls
        page1 = client.get(f"/projects/{project['id']}")
        assert page1.status_code == 200
        assert "page 1 of 2" in page1.text

        # Page 2 should have remaining evaluations
        page2 = client.get(f"/projects/{project['id']}?page=2")
        assert page2.status_code == 200
        assert "page 2 of 2" in page2.text

    def test_evaluation_detail_page(self, client):
        project = _create_project(client, "detail-project")
        resp = client.post(
            "/api/v1/results",
            json={
                "project_id": project["id"],
                "suite_name": "answer_similarity",
                "results": [
                    {
                        "test_case": TEST_CASES[0],
                        "scores": [
                            {"metric_name": "exact_match", "score": 0.0, "passed": False, "reason": "No match"},
                            {"metric_name": "string_containment", "score": 1.0, "passed": True, "reason": "Found"},
                        ],
                        "overall_score": 0.5,
                    },
                ],
            },
        )
        evaluation_id = resp.json()["evaluation_id"]

        page = client.get(f"/evaluations/{evaluation_id}")
        assert page.status_code == 200
        assert "exact_match" in page.text
        assert "string_containment" in page.text


# -- Comparison ----------------------------------------------------------------


class TestComparison:
    """Compare two completed evaluations."""

    def _upload(self, client, project_id, score):
        resp = client.post(
            "/api/v1/results",
            json={
                "project_id": project_id,
                "suite_name": "answer_similarity",
                "results": [
                    {
                        "test_case": TEST_CASES[0],
                        "scores": [
                            {"metric_name": "exact_match", "score": score, "passed": score >= 0.5, "reason": "ok"},
                        ],
                        "overall_score": score,
                    },
                ],
            },
        )
        return resp.json()["evaluation_id"]

    def test_compare_two_evaluations(self, client):
        project = _create_project(client, "compare-project")
        id1 = self._upload(client, project["id"], 0.6)
        id2 = self._upload(client, project["id"], 0.9)

        resp = client.get(f"/api/v1/projects/{project['id']}/compare?ids={id1},{id2}")
        assert resp.status_code == 200
        data = resp.json()
        assert data["baseline"]["id"] == id1
        assert len(data["comparisons"]) == 1

    def test_compare_html_page(self, client):
        project = _create_project(client, "compare-html-project")
        id1 = self._upload(client, project["id"], 0.5)
        id2 = self._upload(client, project["id"], 0.8)

        resp = client.get(f"/projects/{project['id']}/compare?ids={id1},{id2}")
        assert resp.status_code == 200
        assert "compare" in resp.text.lower() or "baseline" in resp.text.lower()


# -- Tag management ------------------------------------------------------------


class TestTagManagement:
    def test_add_update_delete_tags(self, client):
        project = _create_project(client, "tag-mgmt-project")
        resp = client.post(
            "/api/v1/results",
            json={
                "project_id": project["id"],
                "suite_name": "answer_similarity",
                "results": [],
                "tags": {"env": "dev", "branch": "main"},
            },
        )
        eid = resp.json()["evaluation_id"]

        # Add a new tag
        resp = client.put(f"/api/v1/evaluations/{eid}/tags", json={"tags": {"version": "v2"}})
        assert resp.status_code == 200
        assert resp.json()["tags"]["version"] == "v2"
        assert resp.json()["tags"]["env"] == "dev"

        # Update existing tag
        resp = client.put(f"/api/v1/evaluations/{eid}/tags", json={"tags": {"env": "staging"}})
        assert resp.json()["tags"]["env"] == "staging"

        # Delete a tag with null
        resp = client.put(f"/api/v1/evaluations/{eid}/tags", json={"tags": {"branch": None}})
        assert "branch" not in resp.json()["tags"]


# -- Config endpoint -----------------------------------------------------------


class TestServerConfig:
    def test_config_lists_all_suites(self, client):
        resp = client.get("/api/v1/config")
        assert resp.status_code == 200
        data = resp.json()
        assert "answer_similarity" in data["available_suites"]
        assert "rag_qa" in data["available_suites"]

    def test_config_lists_providers(self, client):
        resp = client.get("/api/v1/config")
        data = resp.json()
        assert set(data["available_providers"]) == {"openai", "azure", "anthropic", "ollama"}
