"""Tests for Web UI Enhanced Metrics & Eval Display."""

import os
from unittest.mock import patch

import pytest


def _auth_headers():
    """Return auth headers with the current UI token."""
    from soup_cli.ui.app import get_auth_token
    return {"Authorization": f"Bearer {get_auth_token()}"}


class TestMetricsFullFields:
    """Test that metrics response includes all fields."""

    def test_metrics_includes_grad_norm(self, tmp_path):
        """Metrics should include grad_norm field."""
        try:
            from fastapi.testclient import TestClient
        except ImportError:
            pytest.skip("FastAPI not installed")

        db_path = tmp_path / "test.db"
        with patch.dict(os.environ, {"SOUP_DB_PATH": str(db_path)}):
            from soup_cli.experiment.tracker import ExperimentTracker
            from soup_cli.ui.app import create_app

            tracker = ExperimentTracker(db_path=db_path)
            run_id = tracker.start_run(
                config_dict={"base": "test", "task": "sft"},
                device="cpu",
                device_name="CPU",
                gpu_info={"memory_total": "N/A"},
            )
            tracker.log_metrics(
                run_id, step=10, loss=2.5, lr=1e-5,
                grad_norm=1.23, speed=100.5, gpu_mem="4.2GB",
            )
            tracker.close()

            client = TestClient(create_app())
            response = client.get(f"/api/runs/{run_id}/metrics")
            assert response.status_code == 200
            metrics = response.json()["metrics"]
            assert len(metrics) == 1
            m = metrics[0]
            assert m["grad_norm"] == 1.23
            assert m["speed"] == 100.5
            assert m["gpu_mem"] == "4.2GB"

    def test_metrics_includes_epoch(self, tmp_path):
        """Metrics should include epoch field."""
        try:
            from fastapi.testclient import TestClient
        except ImportError:
            pytest.skip("FastAPI not installed")

        db_path = tmp_path / "test.db"
        with patch.dict(os.environ, {"SOUP_DB_PATH": str(db_path)}):
            from soup_cli.experiment.tracker import ExperimentTracker
            from soup_cli.ui.app import create_app

            tracker = ExperimentTracker(db_path=db_path)
            run_id = tracker.start_run(
                config_dict={"base": "test", "task": "sft"},
                device="cpu",
                device_name="CPU",
                gpu_info={"memory_total": "N/A"},
            )
            tracker.log_metrics(run_id, step=10, epoch=1.5, loss=2.0, lr=1e-5)
            tracker.close()

            client = TestClient(create_app())
            response = client.get(f"/api/runs/{run_id}/metrics")
            metrics = response.json()["metrics"]
            assert metrics[0]["epoch"] == 1.5


class TestRunsCompareEndpoint:
    """Test GET /api/runs/compare endpoint."""

    def test_compare_endpoint_exists(self):
        """Route /api/runs/compare should be registered."""
        try:
            import fastapi  # noqa: F401
        except ImportError:
            pytest.skip("FastAPI not installed")

        from soup_cli.ui.app import create_app

        app = create_app()
        routes = [route.path for route in app.routes]
        assert "/api/runs/compare" in routes

    def test_compare_returns_metrics(self, tmp_path):
        """Compare endpoint should return metrics for multiple runs."""
        try:
            from fastapi.testclient import TestClient
        except ImportError:
            pytest.skip("FastAPI not installed")

        db_path = tmp_path / "test.db"
        with patch.dict(os.environ, {"SOUP_DB_PATH": str(db_path)}):
            from soup_cli.experiment.tracker import ExperimentTracker
            from soup_cli.ui.app import create_app

            tracker = ExperimentTracker(db_path=db_path)
            run1 = tracker.start_run(
                config_dict={"base": "model-a", "task": "sft"},
                device="cpu", device_name="CPU",
                gpu_info={"memory_total": "N/A"},
            )
            tracker.log_metrics(run1, step=10, loss=2.5, lr=1e-5)
            run2 = tracker.start_run(
                config_dict={"base": "model-b", "task": "dpo"},
                device="cpu", device_name="CPU",
                gpu_info={"memory_total": "N/A"},
            )
            tracker.log_metrics(run2, step=10, loss=1.8, lr=5e-6)
            tracker.close()

            client = TestClient(create_app())
            response = client.get(f"/api/runs/compare?ids={run1},{run2}")
            assert response.status_code == 200
            data = response.json()
            assert "runs" in data
            assert len(data["runs"]) == 2
            assert data["runs"][0]["run_id"] == run1
            assert data["runs"][1]["run_id"] == run2
            assert len(data["runs"][0]["metrics"]) == 1
            assert len(data["runs"][1]["metrics"]) == 1

    def test_compare_rejects_too_many_runs(self, tmp_path):
        """Compare endpoint should reject >5 runs."""
        try:
            from fastapi.testclient import TestClient
        except ImportError:
            pytest.skip("FastAPI not installed")

        db_path = tmp_path / "test.db"
        with patch.dict(os.environ, {"SOUP_DB_PATH": str(db_path)}):
            from soup_cli.ui.app import create_app

            client = TestClient(create_app())
            ids = ",".join([f"run_{i}" for i in range(6)])
            response = client.get(f"/api/runs/compare?ids={ids}")
            assert response.status_code == 400

    def test_compare_validates_run_ids(self, tmp_path):
        """Compare endpoint returns entries for nonexistent runs (empty metrics)."""
        try:
            from fastapi.testclient import TestClient
        except ImportError:
            pytest.skip("FastAPI not installed")

        db_path = tmp_path / "test_validate.db"
        with patch.dict(os.environ, {"SOUP_DB_PATH": str(db_path)}):
            from soup_cli.ui.app import create_app

            client = TestClient(create_app())
            response = client.get("/api/runs/compare?ids=nonexistent1,nonexistent2")
            assert response.status_code == 200
            data = response.json()
            assert len(data["runs"]) == 2

    def test_compare_empty_ids(self, tmp_path):
        """Compare endpoint should reject empty ids."""
        try:
            from fastapi.testclient import TestClient
        except ImportError:
            pytest.skip("FastAPI not installed")

        db_path = tmp_path / "test_empty.db"
        with patch.dict(os.environ, {"SOUP_DB_PATH": str(db_path)}):
            from soup_cli.ui.app import create_app

            client = TestClient(create_app())
            response = client.get("/api/runs/compare?ids=")
            assert response.status_code == 400

    def test_compare_no_auth_required(self, tmp_path):
        """Compare is GET (read-only) — no auth needed."""
        try:
            from fastapi.testclient import TestClient
        except ImportError:
            pytest.skip("FastAPI not installed")

        db_path = tmp_path / "test_noauth.db"
        with patch.dict(os.environ, {"SOUP_DB_PATH": str(db_path)}):
            from soup_cli.ui.app import create_app

            client = TestClient(create_app())
            response = client.get("/api/runs/compare?ids=run1,run2")
            assert response.status_code == 200


class TestEvalResultsEndpoint:
    """Test eval results display with parsed details."""

    def test_eval_results_include_details(self, tmp_path):
        """Eval results should include parsed details_json."""
        try:
            from fastapi.testclient import TestClient
        except ImportError:
            pytest.skip("FastAPI not installed")

        db_path = tmp_path / "test.db"
        with patch.dict(os.environ, {"SOUP_DB_PATH": str(db_path)}):
            from soup_cli.experiment.tracker import ExperimentTracker
            from soup_cli.ui.app import create_app

            tracker = ExperimentTracker(db_path=db_path)
            run_id = tracker.start_run(
                config_dict={"base": "test"},
                device="cpu", device_name="CPU",
                gpu_info={"memory_total": "N/A"},
            )
            tracker.save_eval_result(
                model_path="./output",
                benchmark="mmlu",
                score=0.75,
                details={"subjects": {"math": 0.8, "science": 0.7}},
                run_id=run_id,
            )
            tracker.close()

            client = TestClient(create_app())
            response = client.get(f"/api/runs/{run_id}/eval")
            data = response.json()
            assert len(data["eval_results"]) == 1
            result = data["eval_results"][0]
            assert result["benchmark"] == "mmlu"
            assert result["score"] == 0.75
            # details_json should be present as a string (from SQLite)
            assert "details_json" in result

    def test_eval_results_empty(self, tmp_path):
        """Empty eval results should return empty array, not error."""
        try:
            from fastapi.testclient import TestClient
        except ImportError:
            pytest.skip("FastAPI not installed")

        db_path = tmp_path / "test.db"
        with patch.dict(os.environ, {"SOUP_DB_PATH": str(db_path)}):
            from soup_cli.experiment.tracker import ExperimentTracker
            from soup_cli.ui.app import create_app

            tracker = ExperimentTracker(db_path=db_path)
            run_id = tracker.start_run(
                config_dict={"base": "test"},
                device="cpu", device_name="CPU",
                gpu_info={"memory_total": "N/A"},
            )
            tracker.close()

            client = TestClient(create_app())
            response = client.get(f"/api/runs/{run_id}/eval")
            assert response.status_code == 200
            data = response.json()
            assert data["eval_results"] == []

    def test_compare_max_five_runs(self, tmp_path):
        """Compare endpoint allows exactly 5 runs."""
        try:
            from fastapi.testclient import TestClient
        except ImportError:
            pytest.skip("FastAPI not installed")

        db_path = tmp_path / "test.db"
        with patch.dict(os.environ, {"SOUP_DB_PATH": str(db_path)}):
            from soup_cli.experiment.tracker import ExperimentTracker
            from soup_cli.ui.app import create_app

            tracker = ExperimentTracker(db_path=db_path)
            run_ids = []
            for idx in range(5):
                rid = tracker.start_run(
                    config_dict={"base": f"model-{idx}"},
                    device="cpu", device_name="CPU",
                    gpu_info={"memory_total": "N/A"},
                )
                run_ids.append(rid)
            tracker.close()

            client = TestClient(create_app())
            ids_str = ",".join(run_ids)
            response = client.get(f"/api/runs/compare?ids={ids_str}")
            assert response.status_code == 200
            assert len(response.json()["runs"]) == 5


class TestCompareMetricsContent:
    """Test compare endpoint returns full metric data."""

    def test_compare_includes_all_metric_fields(self, tmp_path):
        """Compare should return metrics with all fields."""
        try:
            from fastapi.testclient import TestClient
        except ImportError:
            pytest.skip("FastAPI not installed")

        db_path = tmp_path / "test.db"
        with patch.dict(os.environ, {"SOUP_DB_PATH": str(db_path)}):
            from soup_cli.experiment.tracker import ExperimentTracker
            from soup_cli.ui.app import create_app

            tracker = ExperimentTracker(db_path=db_path)
            run_id = tracker.start_run(
                config_dict={"base": "test", "task": "sft"},
                device="cpu", device_name="CPU",
                gpu_info={"memory_total": "N/A"},
            )
            tracker.log_metrics(
                run_id, step=10, epoch=1.0, loss=2.5, lr=1e-5,
                grad_norm=0.5, speed=200.0, gpu_mem="8GB",
            )
            tracker.close()

            client = TestClient(create_app())
            response = client.get(f"/api/runs/compare?ids={run_id}")
            data = response.json()
            m = data["runs"][0]["metrics"][0]
            assert m["step"] == 10
            assert m["loss"] == 2.5
            assert m["grad_norm"] == 0.5
            assert m["speed"] == 200.0

    def test_compare_includes_config(self, tmp_path):
        """Compare should include run config for diff display."""
        try:
            from fastapi.testclient import TestClient
        except ImportError:
            pytest.skip("FastAPI not installed")

        db_path = tmp_path / "test.db"
        with patch.dict(os.environ, {"SOUP_DB_PATH": str(db_path)}):
            from soup_cli.experiment.tracker import ExperimentTracker
            from soup_cli.ui.app import create_app

            tracker = ExperimentTracker(db_path=db_path)
            run_id = tracker.start_run(
                config_dict={"base": "llama-8b", "task": "sft"},
                device="cpu", device_name="CPU",
                gpu_info={"memory_total": "N/A"},
            )
            tracker.close()

            client = TestClient(create_app())
            response = client.get(f"/api/runs/compare?ids={run_id}")
            data = response.json()
            assert "config" in data["runs"][0]
