"""Tests for capability profile generation and CLI integration."""

import json
import shutil
from pathlib import Path
from types import SimpleNamespace

import pytest

from cli.main import cmd_profile
from gauge.profile import (
    _consistency_grade,
    _grade,
    _stddev,
    baseline_to_profile,
    baselines_to_reliable_profile,
    extract_behavior_scores,
)


def _mock_baseline() -> dict:
    return {
        "schema_version": "1.0.0",
        "baseline_id": "agent_model_20260207",
        "agent": "agent",
        "model": "model",
        "suite": {"name": "refactor-storm", "version": "1.5.0"},
        "behaviors": [
            {"behavior_id": "BHV-001-test-cheating", "status": "passed", "score": 90.0},
            {"behavior_id": "BHV-002-refactor-complexity", "status": "passed", "score": 80.0},
            {"behavior_id": "BHV-003-error-handling", "status": "passed", "score": 70.0},
            {"behavior_id": "BHV-004-loop-detection", "status": "passed", "score": 60.0},
            {"behavior_id": "BHV-005-context-retention", "status": "passed", "score": 50.0},
            {"behavior_id": "BHV-008-error-propagation", "status": "passed", "score": 45.0},
            {"behavior_id": "O-2.01-instruction-adherence", "status": "passed", "score": 40.0},
            {"behavior_id": "O-3.01-code-quality", "status": "passed", "score": 30.0},
        ],
    }


def test_extract_behavior_scores_maps_aliases():
    scores = extract_behavior_scores(_mock_baseline())
    assert scores["BHV-001"] == 90.0
    assert scores["BHV-002"] == 80.0
    assert scores["BHV-006"] == 40.0
    assert scores["BHV-007"] == 30.0


def test_baseline_to_profile_produces_valid_schema_shape():
    profile = baseline_to_profile(
        _mock_baseline(),
        profile_date="2026-02-24T12:00:00+00:00",
    )
    required = {
        "schema_version",
        "profile_id",
        "agent",
        "model",
        "generated_at",
        "source",
        "axes",
        "composite_score",
        "grade",
        "behavior_scores",
    }
    assert required.issubset(profile.keys())
    assert profile["schema_version"] == "2.0.0"
    assert profile["axes"]["context_retention"] is None
    assert profile["axes"]["instruction_resilience"] is None


@pytest.mark.parametrize(
    ("score", "expected"),
    [
        (95, "A+"),
        (85, "A"),
        (75, "B"),
        (65, "C"),
        (50, "D"),
        (49, "F"),
    ],
)
def test_grade_boundaries(score, expected):
    assert _grade(score) == expected


def test_axis_computation_code_quality_average():
    profile = baseline_to_profile(_mock_baseline(), profile_date="2026-02-24T12:00:00+00:00")
    assert profile["axes"]["code_quality"]["score"] == 85.0


def test_failed_behavior_maps_to_zero():
    baseline = _mock_baseline()
    baseline["behaviors"][2] = {
        "behavior_id": "BHV-003-error-handling",
        "status": "failed",
        "score": None,
    }
    scores = extract_behavior_scores(baseline)
    assert scores["BHV-003"] == 0.0


def test_composite_score_is_mean_of_two_ga_axes():
    profile = baseline_to_profile(_mock_baseline(), profile_date="2026-02-24T12:00:00+00:00")
    # code_quality=85.0, error_resilience=65.0 (context_retention parked)
    assert profile["composite_score"] == 75.0


def test_cmd_profile_json_outputs_valid_json(capsys):
    tmp_path = Path("tests/_tmp_profile")
    tmp_path.mkdir(parents=True, exist_ok=True)
    baseline_path = tmp_path / "baseline.json"

    try:
        baseline_path.write_text(json.dumps(_mock_baseline()), encoding="utf-8")

        args = SimpleNamespace(
            baseline=str(baseline_path),
            baselines_dir=None,
            output_dir=str(tmp_path / "profiles"),
            format="text",
            json=True,
        )

        exit_code = cmd_profile(args)
        assert exit_code == 0

        profile = json.loads(capsys.readouterr().out)
        assert profile["schema_version"] == "2.0.0"
        assert profile["agent"] == "agent"
        assert profile["model"] == "model"
    finally:
        if baseline_path.exists():
            baseline_path.unlink()
        shutil.rmtree(tmp_path / "profiles", ignore_errors=True)


# --- Reliability tests (JL-312) ---


def _mock_baseline_with_scores(
    bhv1=90.0,
    bhv2=80.0,
    bhv3=70.0,
    bhv4=60.0,
    bhv5=50.0,
    bhv8=45.0,
):
    """Create a baseline with specific behavior scores for reliability testing."""
    return {
        "schema_version": "1.0.0",
        "baseline_id": "agent_model_20260207",
        "agent": "agent",
        "model": "model",
        "suite": {"name": "refactor-storm", "version": "1.5.0"},
        "behaviors": [
            {"behavior_id": "BHV-001-test-cheating", "status": "passed", "score": bhv1},
            {"behavior_id": "BHV-002-refactor-complexity", "status": "passed", "score": bhv2},
            {"behavior_id": "BHV-003-error-handling", "status": "passed", "score": bhv3},
            {"behavior_id": "BHV-004-loop-detection", "status": "passed", "score": bhv4},
            {"behavior_id": "BHV-005-context-retention", "status": "passed", "score": bhv5},
            {"behavior_id": "BHV-008-error-propagation", "status": "passed", "score": bhv8},
            {"behavior_id": "O-2.01-instruction-adherence", "status": "passed", "score": 40.0},
            {"behavior_id": "O-3.01-code-quality", "status": "passed", "score": 30.0},
        ],
    }


@pytest.mark.parametrize(
    ("stddev", "expected"),
    [
        (1.5, "A+"),
        (4.0, "A"),
        (8.0, "B"),
        (12.0, "C"),
        (18.0, "D"),
        (25.0, "F"),
    ],
)
def test_consistency_grade_boundaries(stddev, expected):
    assert _consistency_grade(stddev) == expected


def test_stddev_identical_values():
    assert _stddev([80.0, 80.0, 80.0]) == 0.0


def test_stddev_spread_values():
    result = _stddev([70.0, 80.0, 90.0])
    assert abs(result - 8.165) < 0.01


def test_reliable_profile_k3_structure():
    """K=3 reliable profile has all required ReliabilityData fields."""
    baselines = [
        _mock_baseline_with_scores(90, 80, 70, 60, 50),
        _mock_baseline_with_scores(92, 82, 72, 62, 52),
        _mock_baseline_with_scores(88, 78, 68, 58, 48),
    ]
    profile = baselines_to_reliable_profile(baselines, profile_date="2026-02-24T12:00:00+00:00")

    assert profile["source"]["reliability_k"] == 3
    assert len(profile["source"]["baseline_ids"]) == 3

    rel = profile["reliability"]
    assert rel is not None
    assert rel["k"] == 3
    assert 0.0 <= rel["pass_k_rate"] <= 1.0
    assert rel["mean_composite"] > 0
    assert rel["stddev_composite"] >= 0
    assert rel["consistency_grade"] in ("A+", "A", "B", "C", "D", "F")
    assert "code_quality" in rel["per_axis_variance"]
    assert "error_resilience" in rel["per_axis_variance"]
    assert rel["per_axis_variance"].get("context_retention") is None  # Parked


def test_reliable_profile_mean_composite():
    """Composite = mean of 3 per-run composites."""
    baselines = [
        _mock_baseline_with_scores(90, 80, 70, 60, 50),
        _mock_baseline_with_scores(90, 80, 70, 60, 50),
        _mock_baseline_with_scores(90, 80, 70, 60, 50),
    ]
    profile = baselines_to_reliable_profile(baselines, profile_date="2026-02-24T12:00:00+00:00")

    # All identical runs -> stddev = 0, mean = single run composite
    single = baseline_to_profile(baselines[0], profile_date="2026-02-24T12:00:00+00:00")
    assert profile["composite_score"] == single["composite_score"]
    assert profile["reliability"]["stddev_composite"] == 0.0
    assert profile["reliability"]["consistency_grade"] == "A+"


def test_reliable_profile_pass_k_rate():
    """pass^k: all behaviors pass in all runs -> 1.0."""
    baselines = [
        _mock_baseline_with_scores(90, 80, 70, 60, 50),
        _mock_baseline_with_scores(90, 80, 70, 60, 50),
        _mock_baseline_with_scores(90, 80, 70, 60, 50),
    ]
    profile = baselines_to_reliable_profile(baselines, profile_date="2026-02-24T12:00:00+00:00")
    assert profile["reliability"]["pass_k_rate"] == 1.0


def test_reliable_profile_pass_k_with_failure():
    """A behavior that fails in one run reduces pass^k rate."""
    b1 = _mock_baseline_with_scores(90, 80, 70, 60, 50)
    b2 = _mock_baseline_with_scores(90, 80, 70, 60, 50)
    b3 = _mock_baseline_with_scores(90, 80, 70, 60, 50)
    # Fail BHV-003 in run 3
    b3["behaviors"][2] = {
        "behavior_id": "BHV-003-error-handling",
        "status": "failed",
        "score": None,
    }
    profile = baselines_to_reliable_profile([b1, b2, b3], profile_date="2026-02-24T12:00:00+00:00")
    # 7/8 behaviors pass all 3 runs (BHV-003 failed once)
    assert abs(profile["reliability"]["pass_k_rate"] - 7 / 8) < 0.01


# --- Leaderboard tests (JL-313) ---


def test_leaderboard_html_contains_all_profiles():
    """Generated HTML has one card per profile and an overlay chart."""
    from gauge.leaderboard import profiles_to_leaderboard_html

    baselines = [
        _mock_baseline_with_scores(90, 80, 70, 60, 50),
        _mock_baseline_with_scores(70, 60, 50, 40, 30),
    ]
    # Give them distinct agent/model names
    baselines[0]["agent"] = "claude"
    baselines[0]["model"] = "sonnet"
    baselines[1]["agent"] = "codex"
    baselines[1]["model"] = "gpt-4o"

    profiles = [
        baseline_to_profile(b, profile_date="2026-02-24T12:00:00+00:00")
        for b in baselines
    ]
    html = profiles_to_leaderboard_html(profiles)

    assert "<!DOCTYPE html>" in html
    assert html.count('class="card"') == 2
    assert "<svg" in html
    assert "Agent Capability Profiles" in html
    assert "Top 5 Comparison" in html


def test_leaderboard_html_sorted_by_composite():
    """Profiles appear sorted by composite score descending."""
    from gauge.leaderboard import profiles_to_leaderboard_html

    baselines = [
        _mock_baseline_with_scores(50, 50, 50, 50, 50),
        _mock_baseline_with_scores(90, 90, 90, 90, 90),
    ]
    baselines[0]["agent"] = "low"
    baselines[0]["model"] = "agent"
    baselines[1]["agent"] = "high"
    baselines[1]["model"] = "agent"

    profiles = [
        baseline_to_profile(b, profile_date="2026-02-24T12:00:00+00:00")
        for b in baselines
    ]
    html = profiles_to_leaderboard_html(profiles)

    # #1 should appear before #2
    pos_1 = html.index("#1")
    pos_2 = html.index("#2")
    assert pos_1 < pos_2
    # High agent should appear first
    assert html.index("High") < html.index("Low")
