"""Tests for orxaq learn and orxaq cost CLI subcommands."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from orxaq.experiences.cli import main

# ── Fixtures ──────────────────────────────────────────────────


@pytest.fixture()
def metrics_store(tmp_path: Path) -> Path:
    """Create a metrics JSON store with sample data."""
    data = [
        {
            "run_id": "run-001",
            "timestamp": 1700000000.0,
            "algorithm": "pc",
            "dataset_size": 1000,
            "num_variables": 5,
            "shd": 3,
            "edge_precision": 0.8,
            "edge_recall": 0.75,
            "edge_f1": 0.7742,
            "orientation_accuracy": 0.6,
            "execution_time_ms": 120.5,
            "config": {"alpha": 0.05},
        },
        {
            "run_id": "run-002",
            "timestamp": 1700001000.0,
            "algorithm": "consensus",
            "dataset_size": 1000,
            "num_variables": 5,
            "shd": 1,
            "edge_precision": 0.9,
            "edge_recall": 0.85,
            "edge_f1": 0.8743,
            "orientation_accuracy": 0.8,
            "execution_time_ms": 350.2,
            "config": {"alpha": 0.01},
        },
    ]
    store = tmp_path / "metrics.json"
    store.write_text(json.dumps(data, indent=2))
    return store


@pytest.fixture()
def experiments_store(tmp_path: Path) -> Path:
    """Create an experiments JSON store with sample data."""
    data = {
        "exp-001": {
            "experiment_id": "exp-001",
            "name": "PC alpha sweep",
            "algorithm": "pc",
            "algorithm_config": {"alpha": 0.05},
            "data_config": {"rows": 1000},
            "results": {"shd": 3},
            "start_time": 1700000000.0,
            "end_time": 1700000010.0,
            "status": "completed",
            "tags": ["sweep", "pc"],
        },
        "exp-002": {
            "experiment_id": "exp-002",
            "name": "Consensus baseline",
            "algorithm": "consensus",
            "algorithm_config": {},
            "data_config": {"rows": 2000},
            "results": {},
            "start_time": 1700002000.0,
            "end_time": 0.0,
            "status": "running",
            "tags": ["baseline"],
        },
    }
    store = tmp_path / "experiments.json"
    store.write_text(json.dumps(data, indent=2))
    return store


@pytest.fixture()
def empty_store(tmp_path: Path) -> Path:
    """Return a path that does not exist (empty store)."""
    return tmp_path / "nonexistent.json"


# ── Learn summary ─────────────────────────────────────────────


class TestLearnSummary:
    def test_summary_human(self, metrics_store: Path, capsys: pytest.CaptureFixture) -> None:
        rc = main(["learn", "summary", "--store", str(metrics_store)])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Learning Metrics Summary" in out
        assert "Total runs:" in out
        assert "2" in out

    def test_summary_json(self, metrics_store: Path, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "learn", "summary", "--store", str(metrics_store)])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["total_runs"] == 2
        assert "algorithms" in data
        assert "best_f1" in data

    def test_summary_empty(self, empty_store: Path, capsys: pytest.CaptureFixture) -> None:
        rc = main(["learn", "summary", "--store", str(empty_store)])
        assert rc == 0
        out = capsys.readouterr().out
        assert "no runs recorded" in out

    def test_summary_empty_json(self, empty_store: Path, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "learn", "summary", "--store", str(empty_store)])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["total_runs"] == 0


# ── Learn experiments ─────────────────────────────────────────


class TestLearnExperiments:
    def test_experiments_human(
        self,
        experiments_store: Path,
        capsys: pytest.CaptureFixture,
    ) -> None:
        rc = main(["learn", "experiments", "--store", str(experiments_store)])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Experiments (2)" in out
        assert "exp-001" in out
        assert "exp-002" in out

    def test_experiments_json(
        self,
        experiments_store: Path,
        capsys: pytest.CaptureFixture,
    ) -> None:
        rc = main(["--json", "learn", "experiments", "--store", str(experiments_store)])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert len(data["experiments"]) == 2

    def test_experiments_filter_status(
        self,
        experiments_store: Path,
        capsys: pytest.CaptureFixture,
    ) -> None:
        rc = main(
            [
                "--json",
                "learn",
                "experiments",
                "--store",
                str(experiments_store),
                "--status",
                "completed",
            ]
        )
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert len(data["experiments"]) == 1
        assert data["experiments"][0]["experiment_id"] == "exp-001"

    def test_experiments_filter_algorithm(
        self,
        experiments_store: Path,
        capsys: pytest.CaptureFixture,
    ) -> None:
        rc = main(
            [
                "--json",
                "learn",
                "experiments",
                "--store",
                str(experiments_store),
                "--algorithm",
                "consensus",
            ]
        )
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert len(data["experiments"]) == 1
        assert data["experiments"][0]["algorithm"] == "consensus"

    def test_experiments_filter_tag(
        self,
        experiments_store: Path,
        capsys: pytest.CaptureFixture,
    ) -> None:
        rc = main(
            [
                "--json",
                "learn",
                "experiments",
                "--store",
                str(experiments_store),
                "--tag",
                "sweep",
            ]
        )
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert len(data["experiments"]) == 1

    def test_experiments_empty(
        self,
        empty_store: Path,
        capsys: pytest.CaptureFixture,
    ) -> None:
        rc = main(["learn", "experiments", "--store", str(empty_store)])
        assert rc == 0
        out = capsys.readouterr().out
        assert "No experiments found" in out


# ── Learn best ────────────────────────────────────────────────


class TestLearnBest:
    def test_best_human(self, metrics_store: Path, capsys: pytest.CaptureFixture) -> None:
        rc = main(["learn", "best", "--store", str(metrics_store)])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Best Run" in out
        assert "run-002" in out  # Higher edge_f1

    def test_best_json(self, metrics_store: Path, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "learn", "best", "--store", str(metrics_store)])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["best"]["run_id"] == "run-002"
        assert data["metric"] == "edge_f1"

    def test_best_by_shd(self, metrics_store: Path, capsys: pytest.CaptureFixture) -> None:
        # SHD: higher value from max() => run-001 (shd=3) wins max, but for SHD lower is better.
        # best_run uses max() so for SHD it returns highest value.
        rc = main(
            [
                "--json",
                "learn",
                "best",
                "--store",
                str(metrics_store),
                "--metric",
                "shd",
            ]
        )
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["best"]["run_id"] == "run-001"  # shd=3 > shd=1

    def test_best_empty(self, empty_store: Path, capsys: pytest.CaptureFixture) -> None:
        rc = main(["learn", "best", "--store", str(empty_store)])
        assert rc == 0
        out = capsys.readouterr().out
        assert "No runs recorded" in out

    def test_best_empty_json(self, empty_store: Path, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "learn", "best", "--store", str(empty_store)])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["best"] is None


# ── Learn compare ─────────────────────────────────────────────


class TestLearnCompare:
    def test_compare_human(self, metrics_store: Path, capsys: pytest.CaptureFixture) -> None:
        rc = main(
            [
                "learn",
                "compare",
                "--store",
                str(metrics_store),
                "--run-a",
                "run-001",
                "--run-b",
                "run-002",
            ]
        )
        assert rc == 0
        out = capsys.readouterr().out
        assert "Comparison: run-001 vs run-002" in out
        assert "Net improvements" in out

    def test_compare_json(self, metrics_store: Path, capsys: pytest.CaptureFixture) -> None:
        rc = main(
            [
                "--json",
                "learn",
                "compare",
                "--store",
                str(metrics_store),
                "--run-a",
                "run-001",
                "--run-b",
                "run-002",
            ]
        )
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["run_a"] == "run-001"
        assert data["run_b"] == "run-002"
        assert "improved" in data
        assert "regressed" in data

    def test_compare_missing_args(
        self,
        metrics_store: Path,
        capsys: pytest.CaptureFixture,
    ) -> None:
        rc = main(
            [
                "learn",
                "compare",
                "--store",
                str(metrics_store),
            ]
        )
        assert rc == 1
        out = capsys.readouterr().out
        assert "required" in out.lower() or "Error" in out

    def test_compare_unknown_run(
        self,
        metrics_store: Path,
        capsys: pytest.CaptureFixture,
    ) -> None:
        rc = main(
            [
                "learn",
                "compare",
                "--store",
                str(metrics_store),
                "--run-a",
                "run-001",
                "--run-b",
                "run-999",
            ]
        )
        assert rc == 1
        out = capsys.readouterr().out
        assert "not found" in out


# ── Cost summary ──────────────────────────────────────────────


class TestCostSummary:
    def test_summary_empty_human(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["cost", "summary"])
        assert rc == 0
        out = capsys.readouterr().out
        assert "no usage recorded" in out

    def test_summary_empty_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "cost", "summary"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["total_calls"] == 0
        assert data["total_cost_usd"] == 0.0


# ── Cost budget ───────────────────────────────────────────────


class TestCostBudget:
    def test_budget_human(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["cost", "budget"])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Budget Status" in out
        assert "Within budget" in out

    def test_budget_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "cost", "budget"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["within_budget"] is True
        assert "spent_usd" in data

    def test_budget_with_limit(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "cost", "budget", "--limit", "10.0"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["hard_limit_usd"] == 10.0
        assert data["within_budget"] is True


# ── Cost pricing ──────────────────────────────────────────────


class TestCostPricing:
    def test_pricing_human(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["cost", "pricing"])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Default Pricing" in out
        assert "anthropic" in out
        assert "openai" in out
        assert "vllm" in out

    def test_pricing_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "cost", "pricing"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert "pricing" in data
        assert "anthropic" in data["pricing"]
        assert "openai" in data["pricing"]
        assert "vllm" in data["pricing"]

    def test_pricing_json_structure(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "cost", "pricing"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        # Each model entry has input_per_1k and output_per_1k
        for _provider, models in data["pricing"].items():
            for _model, info in models.items():
                assert "input_per_1k" in info
                assert "output_per_1k" in info
