"""Tests for web API plumbing: settings, optimize forwarding, dashboard data endpoints."""

from __future__ import annotations

import json
import os
from pathlib import Path
from unittest.mock import patch, MagicMock

import pytest
from fastapi.testclient import TestClient

from clawbench.dashboard import create_app


@pytest.fixture
def output_dir(tmp_path):
    """Create a minimal output dir with results.json so the app boots."""
    results = {
        "iterations_run": 1,
        "best_iteration": 1,
        "best_avg_reward": 0.8,
        "total_metric_calls": 4,
        "history": [{"iteration": 1, "train_score": "1/1", "val_score": "1/1"}],
    }
    (tmp_path / "results.json").write_text(json.dumps(results))
    (tmp_path / "best_SKILL.md").write_text("# Best\n")

    iter_dir = tmp_path / "skill_iter_1"
    iter_dir.mkdir()
    (iter_dir / "SKILL.md").write_text("# Iter 1\n")
    (tmp_path / "iter_1_results.json").write_text(json.dumps({
        "train": {"avg_reward": 0.8, "pass_rate": 1.0, "summary": {"passed": 1, "total": 1}, "results": []},
        "val": {"avg_reward": 0.8, "pass_rate": 1.0, "summary": {"passed": 1, "total": 1}, "results": []},
    }))

    return tmp_path


@pytest.fixture
def client(output_dir):
    app = create_app(output_dir)
    return TestClient(app)


# ---------------------------------------------------------------------------
# /api/settings — engines
# ---------------------------------------------------------------------------

class TestSettingsShape:
    """Verify /api/settings returns the expected Phase 4 shape."""

    def test_has_models_and_agents(self, client):
        resp = client.get("/api/settings")
        assert resp.status_code == 200
        data = resp.json()
        assert "models" in data
        assert "agents" in data

    def test_no_removed_fields(self, client):
        resp = client.get("/api/settings")
        data = resp.json()
        for key in ("api_keys", "defaults", "engines", "skill_packs", "task_dirs"):
            assert key not in data, f"'{key}' should have been removed"

    def test_auth_tokens_present(self, client):
        resp = client.get("/api/settings")
        data = resp.json()
        assert "auth_tokens" in data
        assert "GOOGLE_API_KEY" in data["auth_tokens"]

    def test_auth_token_detected(self, client):
        with patch.dict(os.environ, {"GOOGLE_API_KEY": "test-key"}):
            resp = client.get("/api/settings")
            data = resp.json()
            assert data["auth_tokens"]["GOOGLE_API_KEY"]["set"] is True

    def test_auth_token_missing(self, client):
        env = {k: v for k, v in os.environ.items() if k != "GOOGLE_API_KEY"}
        with patch.dict(os.environ, env, clear=True):
            resp = client.get("/api/settings")
            data = resp.json()
            assert data["auth_tokens"]["GOOGLE_API_KEY"]["set"] is False

    def test_dir_counts_present(self, client):
        resp = client.get("/api/settings")
        data = resp.json()
        assert "skills_dir" in data
        assert "skills_count" in data
        assert "tasks_dir" in data
        assert "tasks_count" in data
        assert "prompts_dir" in data
        assert "prompts_count" in data


# ---------------------------------------------------------------------------
# /api/optimize — engine forwarding
# ---------------------------------------------------------------------------

class TestOptimizeEngineForwarding:
    def test_optimize_forwards_engine_param(self, client, output_dir):
        """POST /api/optimize includes --engine in the subprocess command."""
        with patch("subprocess.Popen") as mock_popen:
            mock_proc = MagicMock()
            mock_proc.pid = 12345
            mock_proc.poll.return_value = None
            mock_popen.return_value = mock_proc

            resp = client.post("/api/optimize", json={
                "experiment": "test-fwd",
                "engine": "gepa",
                "skill": str(output_dir / "best_SKILL.md"),
                "tasks_dir": str(output_dir),
                "max_calls": 2,
            })
            assert resp.status_code == 200
            data = resp.json()
            assert data["status"] == "started"

            # Verify --engine gepa is in the command
            cmd = mock_popen.call_args[0][0]
            assert "--engine" in cmd
            engine_idx = cmd.index("--engine")
            assert cmd[engine_idx + 1] == "gepa"

    def test_optimize_defaults_engine_gepa(self, client, output_dir):
        """POST /api/optimize without engine defaults to gepa."""
        with patch("subprocess.Popen") as mock_popen:
            mock_proc = MagicMock()
            mock_proc.pid = 12345
            mock_proc.poll.return_value = None
            mock_popen.return_value = mock_proc

            resp = client.post("/api/optimize", json={
                "experiment": "test-default",
                "skill": str(output_dir / "best_SKILL.md"),
                "tasks_dir": str(output_dir),
            })
            assert resp.status_code == 200

            cmd = mock_popen.call_args[0][0]
            engine_idx = cmd.index("--engine")
            assert cmd[engine_idx + 1] == "gepa"

    def test_optimize_forwards_val_tasks(self, client, output_dir):
        """POST /api/optimize with val_tasks includes --val-tasks in the subprocess command."""
        with patch("subprocess.Popen") as mock_popen:
            mock_proc = MagicMock()
            mock_proc.pid = 12345
            mock_proc.poll.return_value = None
            mock_popen.return_value = mock_proc

            resp = client.post("/api/optimize", json={
                "experiment": "test-val",
                "skill": str(output_dir / "best_SKILL.md"),
                "tasks_dir": str(output_dir),
                "val_tasks": "task-a,task-b",
                "test_tasks": "task-c",
            })
            assert resp.status_code == 200

            cmd = mock_popen.call_args[0][0]
            assert "--val-tasks" in cmd
            val_idx = cmd.index("--val-tasks")
            assert cmd[val_idx + 1] == "task-a,task-b"
            assert "--test-tasks" in cmd
            test_idx = cmd.index("--test-tasks")
            assert cmd[test_idx + 1] == "task-c"

    def test_optimize_already_running_rejected(self, client, output_dir):
        """Second start for same experiment is rejected while first is running."""
        with patch("subprocess.Popen") as mock_popen:
            mock_proc = MagicMock()
            mock_proc.pid = 12345
            mock_proc.poll.return_value = None  # still running
            mock_popen.return_value = mock_proc

            # First call starts
            resp1 = client.post("/api/optimize", json={
                "experiment": "dup-test",
                "skill": str(output_dir / "best_SKILL.md"),
                "tasks_dir": str(output_dir),
            })
            assert resp1.json()["status"] == "started"

            # Second call rejected
            resp2 = client.post("/api/optimize", json={
                "experiment": "dup-test",
                "skill": str(output_dir / "best_SKILL.md"),
                "tasks_dir": str(output_dir),
            })
            assert resp2.json()["status"] == "already_running"


# ---------------------------------------------------------------------------
# Dashboard data endpoints (automated from Dashboard Test Procedure)
# ---------------------------------------------------------------------------

class TestDashboardRunEndpoint:
    def test_api_run_returns_payload(self, client):
        resp = client.get("/api/run")
        assert resp.status_code == 200
        data = resp.json()
        assert data["iterations_run"] == 1
        assert data["best_iteration"] == 1
        assert "iterations" in data
        assert len(data["iterations"]) == 1

    def test_api_run_includes_best_skill(self, client):
        resp = client.get("/api/run")
        data = resp.json()
        assert data["best_skill_content"] == "# Best\n"


class TestDashboardIterationEndpoint:
    def test_api_iteration_1_returns_data(self, client):
        resp = client.get("/api/iteration/1")
        assert resp.status_code == 200
        data = resp.json()
        assert data["iteration"] == 1
        assert "skill_content" in data
        assert data["skill_content"] == "# Iter 1\n"

    def test_api_iteration_invalid_returns_404(self, client):
        resp = client.get("/api/iteration/999")
        assert resp.status_code == 404


class TestDashboardExperimentsEndpoint:
    def test_api_experiments_returns_entries(self, client):
        resp = client.get("/api/experiments")
        assert resp.status_code == 200
        data = resp.json()
        assert len(data["experiments"]) >= 1
        assert data["experiments"][0]["name"] == "default"
