"""Unit tests for report generation: generate_report, generate_compare_report, render_markdown_report."""

import pytest
from agenteval.evaluation.report import (
    generate_compare_report,
    generate_report,
)
from agenteval.evaluation.report_render import render_json, render_markdown
from agenteval.models import (
    Case,
    CaseType,
    ChannelType,
    EvalResult,
    EvalRun,
    EvalTarget,
    PlatformType,
    RunStatus,
    Scenario,
    TargetStatus,
    Turn,
)
from agenteval.storage.repository import ResultRepository, RunRepository, ScenarioRepository, TargetRepository
from sqlmodel import Session, SQLModel, create_engine


@pytest.fixture()
def report_session(tmp_path):
    from agenteval.storage.db import (  # noqa: F401
        EvalResultDB,
        EvalRunDB,
        EvalTargetDB,
        FileCategoryDB,
        FileRecordDB,
        ScenarioDB,
        TurnDB,
    )
    engine = create_engine(
        f"sqlite:///{tmp_path / 'report_test.db'}",
        connect_args={"check_same_thread": False},
    )
    SQLModel.metadata.create_all(engine)
    session = Session(engine)
    try:
        yield session
    finally:
        session.close()
        engine.dispose()


def _seed_run(
    session: Session,
    *,
    pass_rate: float = 1.0,
    n_cases: int = 1,
    scenario_id: str | None = None,
    scenario_version: int = 1,
    connectivity_cases: int = 0,
    errored_cases: int = 0,
) -> str:
    """Create a minimal completed run with real data in the DB and return run_id.

    connectivity_cases: extra cases with replied turns but no rule results.
    errored_cases: extra cases with a missing-reply turn and no rule results.
    """
    target = EvalTarget(
        name="测试对象",
        platform=PlatformType.AI_DIGITAL_EMPLOYEE,
        channel_type=ChannelType.TUTU_API,
        channel_config={},
        status=TargetStatus.ACTIVE,
    )
    target = TargetRepository(session).create(target)

    if scenario_id is None:
        scenario = Scenario(
            name="测试场景",
            cases=(
                [Case(id=f"c{i}", type=CaseType.SINGLE, messages=["hi"]) for i in range(n_cases)]
                + [Case(id=f"conn{i}", type=CaseType.SINGLE, messages=["ping"]) for i in range(connectivity_cases)]
                + [Case(id=f"err{i}", type=CaseType.SINGLE, messages=["ping"]) for i in range(errored_cases)]
            ),
        )
        scenario = ScenarioRepository(session).create(scenario)
        scenario_id = scenario.id

    run = EvalRun(
        target_id=target.id,
        scenario_id=scenario_id,
        scenario_version=scenario_version,
        status=RunStatus.COMPLETED,
    )
    run = RunRepository(session).create(run)

    run_repo = RunRepository(session)
    result_repo = ResultRepository(session)

    total = n_cases
    passed = int(total * pass_rate)

    for i in range(n_cases):
        turn = Turn(
            run_id=run.id,
            case_id=f"c{i}",
            round_index=1,
            sent_message={"msgBody": {"content": f"问题{i}"}},
            reply={"msgBody": {"content": f"回答{i}"}},
            latency_ms=200,
        )
        result_repo.save_turn(turn)
        db_turn = run_repo.get_turns(run.id)[-1]

        eval_result = EvalResult(
            run_id=run.id,
            case_id=f"c{i}",
            turn_id=db_turn.id or "",
            rule_type="keyword_match",
            passed=(i < passed),
            score=1.0 if i < passed else 0.0,
            reason="通过" if i < passed else "失败",
        )
        result_repo.save_result(eval_result)

    for i in range(connectivity_cases):
        result_repo.save_turn(Turn(
            run_id=run.id,
            case_id=f"conn{i}",
            round_index=1,
            sent_message={"msgBody": {"content": "ping"}},
            reply={"msgBody": {"content": "pong"}},
            latency_ms=100,
        ))

    for i in range(errored_cases):
        result_repo.save_turn(Turn(
            run_id=run.id,
            case_id=f"err{i}",
            round_index=1,
            sent_message={"msgBody": {"content": "ping"}},
            reply=None,
            latency_ms=None,
        ))

    all_total = total + connectivity_cases + errored_cases
    all_passed = passed + connectivity_cases  # 连通用例收到回复即通过（引擎口径）
    run.summary = {
        "total_cases": all_total,
        "passed_cases": all_passed,
        "failed_cases": all_total - all_passed,
        "total_rules": total,
        "passed_rules": passed,
        "pass_rate": round(all_passed / all_total, 4) if all_total else 0.0,
    }
    RunRepository(session).update(run)
    return run.id


# ── generate_report ───────────────────────────────────────────────────────

def test_generate_report_structure(report_session):
    run_id = _seed_run(report_session)
    report = generate_report(run_id, report_session)

    assert report["run_id"] == run_id
    assert report["target_name"] == "测试对象"
    assert report["scenario_name"] == "测试场景"
    assert report["status"] == "completed"
    assert "summary" in report
    assert "cases" in report


def test_generate_report_summary_values(report_session):
    run_id = _seed_run(report_session, pass_rate=1.0, n_cases=2)
    report = generate_report(run_id, report_session)
    s = report["summary"]
    assert s["total_cases"] == 2
    assert s["passed_cases"] == 2
    assert s["pass_rate"] == 1.0


def test_generate_report_partial_pass(report_session):
    run_id = _seed_run(report_session, pass_rate=0.5, n_cases=2)
    report = generate_report(run_id, report_session)
    s = report["summary"]
    assert s["passed_cases"] == 1
    assert s["failed_cases"] == 1


def test_generate_report_cases_contain_turns_and_results(report_session):
    run_id = _seed_run(report_session, n_cases=1)
    report = generate_report(run_id, report_session)
    assert len(report["cases"]) == 1
    case = report["cases"][0]
    assert len(case["turns"]) == 1
    assert len(case["results"]) == 1
    assert case["turns"][0]["sent_text"] == "问题0"
    assert case["turns"][0]["latency_ms"] == 200


def test_generate_report_not_found_raises(report_session):
    with pytest.raises(ValueError, match="run not found"):
        generate_report("no-such-id", report_session)


# ── generate_compare_report ───────────────────────────────────────────────

def _scenario_of(session: Session, run_id: str) -> str:
    return RunRepository(session).get(run_id).scenario_id


def test_compare_report_structure(report_session):
    run_id_a = _seed_run(report_session, pass_rate=1.0, n_cases=2)
    sid = _scenario_of(report_session, run_id_a)
    run_id_b = _seed_run(report_session, pass_rate=0.5, n_cases=2, scenario_id=sid)
    result = generate_compare_report(run_id_a, run_id_b, report_session)

    assert "run_a" in result
    assert "run_b" in result
    assert "delta" in result
    assert "cases" in result
    assert result["run_a"]["run_id"] == run_id_a
    assert result["run_b"]["run_id"] == run_id_b


def test_compare_report_delta(report_session):
    run_id_a = _seed_run(report_session, pass_rate=0.5, n_cases=2)
    sid = _scenario_of(report_session, run_id_a)
    run_id_b = _seed_run(report_session, pass_rate=1.0, n_cases=2, scenario_id=sid)
    result = generate_compare_report(run_id_a, run_id_b, report_session)
    assert result["delta"]["pass_rate"] > 0  # B improved over A


def test_compare_report_changed_cases(report_session):
    run_id_a = _seed_run(report_session, pass_rate=1.0, n_cases=2)
    sid = _scenario_of(report_session, run_id_a)
    run_id_b = _seed_run(report_session, pass_rate=0.5, n_cases=2, scenario_id=sid)
    result = generate_compare_report(run_id_a, run_id_b, report_session)
    # At least one case changed (A all-pass vs B half-pass)
    assert result["changed_cases"] >= 1


def test_compare_report_case_level(report_session):
    run_id_a = _seed_run(report_session, n_cases=1)
    sid = _scenario_of(report_session, run_id_a)
    run_id_b = _seed_run(report_session, n_cases=1, scenario_id=sid)
    result = generate_compare_report(run_id_a, run_id_b, report_session)
    assert len(result["cases"]) >= 1
    case = result["cases"][0]
    assert "run_a_passed" in case
    assert "run_b_passed" in case
    assert "changed" in case


def test_compare_report_different_scenarios_rejected(report_session):
    run_id_a = _seed_run(report_session, n_cases=1)
    run_id_b = _seed_run(report_session, n_cases=1)  # separate scenario
    with pytest.raises(ValueError, match="same scenario"):
        generate_compare_report(run_id_a, run_id_b, report_session)


def test_compare_report_cross_version_rejected(report_session):
    """同场景不同考纲版本不可比（ticket 05 / ADR-0001）。"""
    run_id_a = _seed_run(report_session, n_cases=1)
    sid = _scenario_of(report_session, run_id_a)
    run_id_b = _seed_run(report_session, n_cases=1, scenario_id=sid, scenario_version=2)
    with pytest.raises(ValueError, match="version"):
        generate_compare_report(run_id_a, run_id_b, report_session)


def test_compare_report_same_version_allowed(report_session):
    run_id_a = _seed_run(report_session, n_cases=1, scenario_version=2)
    sid = _scenario_of(report_session, run_id_a)
    run_id_b = _seed_run(report_session, n_cases=1, scenario_id=sid, scenario_version=2)
    result = generate_compare_report(run_id_a, run_id_b, report_session)
    assert result["run_a"]["scenario_version"] == 2
    assert result["run_b"]["scenario_version"] == 2


# ── connectivity case annotation (ticket 02) ─────────────────────────────

def test_report_marks_connectivity_case(report_session):
    run_id = _seed_run(report_session, pass_rate=0.5, n_cases=2, connectivity_cases=1)
    report = generate_report(run_id, report_session)

    by_id = {c["case_id"]: c for c in report["cases"]}
    assert by_id["conn0"]["connectivity"] is True
    assert by_id["c0"]["connectivity"] is False
    assert by_id["c1"]["connectivity"] is False

    s = report["summary"]
    assert s["connectivity_cases"] == 1
    # 判定型通过率 = 判定型通过 1 ÷ 判定型总数 2
    assert s["judged_pass_rate"] == 0.5
    # 总通过率口径不变（含连通用例，ADR-0002）
    assert s["pass_rate"] == round(2 / 3, 4)


def test_errored_case_not_marked_connectivity(report_session):
    """无判定结果但缺回复的用例是执行失败，不是连通用例。"""
    run_id = _seed_run(report_session, n_cases=1, errored_cases=1)
    report = generate_report(run_id, report_session)

    by_id = {c["case_id"]: c for c in report["cases"]}
    assert by_id["err0"]["connectivity"] is False
    assert report["summary"]["connectivity_cases"] == 0


def test_all_connectivity_judged_pass_rate_none(report_session):
    """全为连通用例时判定型通过率为 None，不除零。"""
    run_id = _seed_run(report_session, n_cases=0, connectivity_cases=2)
    report = generate_report(run_id, report_session)

    s = report["summary"]
    assert s["connectivity_cases"] == 2
    assert s["judged_pass_rate"] is None


def test_compare_report_marks_connectivity(report_session):
    run_id_a = _seed_run(report_session, n_cases=1, connectivity_cases=1)
    sid = _scenario_of(report_session, run_id_a)
    run_id_b = _seed_run(report_session, n_cases=1, connectivity_cases=1, scenario_id=sid)
    result = generate_compare_report(run_id_a, run_id_b, report_session)

    by_id = {c["case_id"]: c for c in result["cases"]}
    assert by_id["conn0"]["connectivity"] is True
    assert by_id["c0"]["connectivity"] is False
    # 连通用例双方均按引擎口径视为通过，不应标记 changed
    assert by_id["conn0"]["run_a_passed"] is True
    assert by_id["conn0"]["run_b_passed"] is True
    assert by_id["conn0"]["changed"] is False


def test_markdown_report_shows_connectivity(report_session):
    run_id = _seed_run(report_session, n_cases=1, connectivity_cases=1)
    md = render_markdown(generate_report(run_id, report_session))
    assert "连通用例" in md
    assert "判定型通过率" in md


# ── render_markdown_report ────────────────────────────────────────────────

def test_render_markdown_contains_header(report_session):
    run_id = _seed_run(report_session)
    md = render_markdown(generate_report(run_id, report_session))
    assert "# 评测报告" in md


def test_render_markdown_contains_summary_table(report_session):
    run_id = _seed_run(report_session)
    md = render_markdown(generate_report(run_id, report_session))
    assert "## 汇总" in md
    assert "| 指标 | 数值 |" in md
    assert "通过率" in md


def test_render_markdown_contains_case_section(report_session):
    run_id = _seed_run(report_session, n_cases=1)
    md = render_markdown(generate_report(run_id, report_session))
    assert "## 用例明细" in md
    assert "### " in md  # case header


def test_render_markdown_contains_rule_table(report_session):
    run_id = _seed_run(report_session)
    md = render_markdown(generate_report(run_id, report_session))
    assert "**规则评估结果**" in md
    assert "keyword_match" in md


# ── render_json_report ────────────────────────────────────────────────────

def test_render_json_report_is_valid_json(report_session):
    import json
    run_id = _seed_run(report_session)
    json_text = render_json(generate_report(run_id, report_session))
    parsed = json.loads(json_text)
    assert parsed["run_id"] == run_id


# ── 权威判定消费（judgement 语义收敛） ────────────────────────────────────

def test_case_dict_contains_passed_fallback(report_session):
    """老 run（无 case_outcomes）：passed 反推得出，故障用例=False（ADR-0002）。"""
    run_id = _seed_run(report_session, pass_rate=0.5, n_cases=2,
                       connectivity_cases=1, errored_cases=1)
    report = generate_report(run_id, report_session)
    by_id = {c["case_id"]: c for c in report["cases"]}
    assert by_id["c0"]["passed"] is True
    assert by_id["c1"]["passed"] is False
    assert by_id["conn0"]["passed"] is True
    assert by_id["err0"]["passed"] is False


def test_markdown_errored_case_shows_failed_badge(report_session):
    """故障用例（无结果且非连通）在 MD 中必须 ❌ —— 此前 all([]) 误判 ✅。"""
    run_id = _seed_run(report_session, n_cases=1, errored_cases=1)
    md = render_markdown(generate_report(run_id, report_session))
    assert "❌ 用例 `err0`" in md
    assert "✅ 用例 `err0`" not in md


def test_authoritative_case_outcomes_override_reconstruction(report_session):
    """summary.case_outcomes 是权威判定：与规则结果反推冲突时以权威为准。"""
    run_id = _seed_run(report_session, pass_rate=1.0, n_cases=1)
    repo = RunRepository(report_session)
    run = repo.get(run_id)
    # 模拟 weighted 阈值未达：规则单条通过但用例判失败（反推 all() 会误判 True）
    run.summary = {**run.summary.model_dump(), "case_outcomes": {"c0": {"passed": False, "connectivity": False}}}
    repo.update(run)

    report = generate_report(run_id, report_session)
    assert report["cases"][0]["passed"] is False
    md = render_markdown(generate_report(run_id, report_session))
    assert "❌ 用例 `c0`" in md


def test_compare_errored_case_counts_as_failed(report_session):
    """对比中故障用例判 False 而非 None（ADR-0002 服务视角）。"""
    run_id_a = _seed_run(report_session, n_cases=1, errored_cases=1)
    sid = _scenario_of(report_session, run_id_a)
    run_id_b = _seed_run(report_session, n_cases=1, errored_cases=1, scenario_id=sid)
    result = generate_compare_report(run_id_a, run_id_b, report_session)
    by_id = {c["case_id"]: c for c in result["cases"]}
    assert by_id["err0"]["run_a_passed"] is False
    assert by_id["err0"]["run_b_passed"] is False
    assert by_id["err0"]["changed"] is False
