"""五张观测表的落库写入与回读 + step/epoch 报告文件输出。

合并 TRM4 的 metric_log.py（五表）和 loop_report.py（报告）。

五张表均经 structured-logging 定义，DDL 与之逐列一致：
    dual_metric_eval / shadow_gate / holdout_eval / quadrant_pair / gate_evidence。

公共契约（守 P5）：soft/mixed 为 None（invalid，无 span / 诊断失败）时存 NULL，**绝不存 0**——
SQLite 对 dict 中 None 值写入即 NULL，分析时按 NULL 跳过。每个写函数内幂等建表
（``HarnessLog.create_table`` 用 CREATE TABLE IF NOT EXISTS），run_id/timestamp 列由
HarnessLog 自动补。

报告函数输出 JSON 到 workspace 的 analyses/ 目录，供人工审查诊断 prompt 与进化 prompt。
"""

from __future__ import annotations

import json
import sqlite3
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from pathlib import Path


def _read_table(db_path: str, table: str, run_id: str) -> list[dict[str, Any]]:
    """纯读某表指定 run 的全部行——不经 HarnessLog 生命周期，避免回读污染 _runs 运行状态。

    HarnessLog.__enter__/__exit__ 会对 run_id 做 INSERT OR IGNORE 并在退出时标 completed；
    回读指标绝不应改运行状态，故 read_* 一律走本只读连接（仅 SELECT）。

    参数:
        db_path: SQLite 路径。
        table: 表名（内部固定常量，非外部输入，无注入风险）。
        run_id: 过滤的 run ID。

    返回:
        行 dict 列表；表尚未建（没写过）视为无数据返 []。
    """
    conn = sqlite3.connect(db_path)
    conn.row_factory = sqlite3.Row
    try:
        exists = conn.execute(
            "SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table,)
        ).fetchone()
        if exists is None:
            return []
        rows = conn.execute(f"SELECT * FROM {table} WHERE run_id=?", (run_id,)).fetchall()
        return [dict(r) for r in rows]
    finally:
        conn.close()


# ---------------------------------------------------------------------------
# 列定义严格对齐 research-wiki/schemas/*.md（run_id/timestamp 由 create_table 自动补）
# ---------------------------------------------------------------------------

_DUAL_COLS: dict[str, str] = {
    "epoch": "INTEGER",
    "version_kind": "TEXT",
    "skills_version": "TEXT",
    "prompts_version": "TEXT",
    "pool": "TEXT",
    "hard_acc": "REAL",
    "soft_score": "REAL",
    "mixed_score": "REAL",
}

_SHADOW_COLS: dict[str, str] = {
    "epoch": "INTEGER",
    "candidate_version": "TEXT",
    "hard_acc": "REAL",
    "soft_score": "REAL",
    "mixed_score": "REAL",
    "is_mixed_best": "INTEGER",
}

_HOLDOUT_COLS: dict[str, str] = {
    "epoch": "INTEGER",
    "version_kind": "TEXT",
    "hard_acc": "REAL",
    "soft_score": "REAL",
    "mixed_score": "REAL",
    "per_task_type_json": "TEXT",
}

_QUADRANT_COLS: dict[str, str] = {
    "epoch": "INTEGER",
    "step": "INTEGER",
    # question_id 列承载 unit_id（single=question_id，pair=pair_id）；
    # 逐题明细在 predictions 表溯源，按 pair_id join 真实 question 表会 join 不上。
    "question_id": "TEXT",
    "task_type": "TEXT",
    "prev_correct": "INTEGER",
    "curr_correct": "INTEGER",
    "category": "TEXT",
}

_GATE_EVIDENCE_COLS: dict[str, str] = {
    "epoch": "INTEGER",
    "step": "INTEGER",
    "task_type": "TEXT",
    # question_id 列承载 unit_id（single=question_id，pair=pair_id）；
    # 逐题明细在 predictions 表溯源，按 pair_id join 真实 question 表会 join 不上。
    "question_id": "TEXT",
    "block_idx": "INTEGER",
    "baseline_correct": "INTEGER",
    "candidate_correct": "INTEGER",
    "e_value": "REAL",
    "stop_reason": "TEXT",
}


# ---------------------------------------------------------------------------
# dual_metric_eval
# ---------------------------------------------------------------------------


def write_dual_metric(
    db_path: str,
    *,
    run_id: str,
    epoch: int,
    version_kind: str,
    skills_version: str,
    prompts_version: str,
    pool: str,
    hard_acc: float,
    soft_score: float | None,
    mixed_score: float | None,
) -> None:
    """落 dual_metric_eval 一行：epoch 末关键版本的 hard+soft+mixed 双轨度量。

    参数:
        db_path: SQLite 路径。
        run_id: 训练 run ID。
        epoch: 轮次（1-based）。
        version_kind: baseline / best_hard / best_mixed / final。
        skills_version / prompts_version: 评估的资源版本。
        pool: val / test。
        hard_acc: hard 准确率。
        soft_score: soft 连续分；invalid 传 None -> 存 NULL。
        mixed_score: 0.5*hard+0.5*soft；soft 缺失传 None -> 存 NULL。
    """
    from app.harness.log import HarnessLog

    with HarnessLog(db_path, run_id) as log:
        log.create_table("dual_metric_eval", _DUAL_COLS)
        log.insert(
            "dual_metric_eval",
            {
                "epoch": epoch,
                "version_kind": version_kind,
                "skills_version": skills_version,
                "prompts_version": prompts_version,
                "pool": pool,
                "hard_acc": hard_acc,
                "soft_score": soft_score,
                "mixed_score": mixed_score,
            },
        )


def read_dual_metric(db_path: str, *, run_id: str) -> list[dict[str, Any]]:
    """回读指定 run 的 dual_metric_eval 全部行（纯读，不污染运行状态）。"""
    return _read_table(db_path, "dual_metric_eval", run_id)


# ---------------------------------------------------------------------------
# shadow_gate
# ---------------------------------------------------------------------------


def write_shadow_gate(
    db_path: str,
    *,
    run_id: str,
    epoch: int,
    candidate_version: str,
    hard_acc: float,
    soft_score: float | None,
    mixed_score: float | None,
    is_mixed_best: bool,
) -> None:
    """落 shadow_gate 一行：mixed 影子 best 候选的 hard/soft/mixed 及是否 argmax 选中。

    参数:
        db_path: SQLite 路径。
        run_id: 训练 run ID。
        epoch: 轮次（1-based）。
        candidate_version: 候选版本标识（如 skills/vX+prompts/vY）。
        hard_acc: hard 准确率。
        soft_score: soft 连续分；invalid 传 None -> 存 NULL（该版本不进 argmax）。
        mixed_score: 0.5*hard+0.5*soft；soft 缺失传 None -> 存 NULL。
        is_mixed_best: 是否本 epoch mixed argmax 选中（存 1/0）。
    """
    from app.harness.log import HarnessLog

    with HarnessLog(db_path, run_id) as log:
        log.create_table("shadow_gate", _SHADOW_COLS)
        log.insert(
            "shadow_gate",
            {
                "epoch": epoch,
                "candidate_version": candidate_version,
                "hard_acc": hard_acc,
                "soft_score": soft_score,
                "mixed_score": mixed_score,
                "is_mixed_best": int(is_mixed_best),
            },
        )


def read_shadow_gate(db_path: str, *, run_id: str) -> list[dict[str, Any]]:
    """回读指定 run 的 shadow_gate 全部行（纯读，不污染运行状态）。"""
    return _read_table(db_path, "shadow_gate", run_id)


# ---------------------------------------------------------------------------
# holdout_eval
# ---------------------------------------------------------------------------


def write_holdout_eval(
    db_path: str,
    *,
    run_id: str,
    epoch: int,
    version_kind: str,
    hard_acc: float,
    soft_score: float | None,
    mixed_score: float | None,
    per_task_type_json: str,
) -> None:
    """落 holdout_eval 一行：四向 held-out 在 test 池的 hard+soft+mixed 及按题型细分。

    参数:
        db_path: SQLite 路径。
        run_id: 训练 run ID。
        epoch: 轮次（1-based）。
        version_kind: baseline / best_hard / best_mixed / final。
        hard_acc: hard 准确率。
        soft_score: soft 连续分；invalid 传 None -> 存 NULL。
        mixed_score: 0.5*hard+0.5*soft；soft 缺失传 None -> 存 NULL。
        per_task_type_json: 按 task_type 的 {accuracy,total,correct} JSON 串。
    """
    from app.harness.log import HarnessLog

    with HarnessLog(db_path, run_id) as log:
        log.create_table("holdout_eval", _HOLDOUT_COLS)
        log.insert(
            "holdout_eval",
            {
                "epoch": epoch,
                "version_kind": version_kind,
                "hard_acc": hard_acc,
                "soft_score": soft_score,
                "mixed_score": mixed_score,
                "per_task_type_json": per_task_type_json,
            },
        )


def read_holdout_eval(db_path: str, *, run_id: str) -> list[dict[str, Any]]:
    """回读指定 run 的 holdout_eval 全部行（纯读，不污染运行状态）。"""
    return _read_table(db_path, "holdout_eval", run_id)


# ---------------------------------------------------------------------------
# quadrant_pair
# ---------------------------------------------------------------------------


def write_quadrant_pairs(
    db_path: str,
    *,
    run_id: str,
    epoch: int,
    step: int,
    pairs: list[dict[str, Any]],
) -> None:
    """落 quadrant_pair 多行：fast gate 后按 **unit** 四象限（prev/curr 翻转 + category）落库。

    参数:
        db_path: SQLite 路径。
        run_id: 训练 run ID。
        epoch: 轮次（1-based）。
        step: epoch 内 step 序号（0-based）。
        pairs: 每条含 question_id/task_type/prev_correct/curr_correct/category；
            question_id 字段承载 **unit_id**（single=question_id，pair=pair_id，
            与 gate e-process 同粒度）——逐题明细在 predictions 表溯源，按 pair_id
            join 真实 question 表会 join 不上；prev_correct/curr_correct 为 bool，
            写库前转 0/1。

    关键实现:
        用 insert_many 批量落库；pairs 为空时只建表不插入（fast gate 无翻转的极端情况）。
    """
    records = [
        {
            "epoch": epoch,
            "step": step,
            "question_id": pair["question_id"],
            "task_type": pair["task_type"],
            "prev_correct": int(pair["prev_correct"]),
            "curr_correct": int(pair["curr_correct"]),
            "category": pair["category"],
        }
        for pair in pairs
    ]
    from app.harness.log import HarnessLog

    with HarnessLog(db_path, run_id) as log:
        log.create_table("quadrant_pair", _QUADRANT_COLS)
        if records:
            log.insert_many("quadrant_pair", records)


def read_quadrant_pairs(db_path: str, *, run_id: str) -> list[dict[str, Any]]:
    """回读指定 run 的 quadrant_pair 全部行（纯读，不污染运行状态）。"""
    return _read_table(db_path, "quadrant_pair", run_id)


# ---------------------------------------------------------------------------
# gate_evidence
# ---------------------------------------------------------------------------


def write_gate_evidence(
    db_path: str,
    *,
    run_id: str,
    epoch: int,
    step: int,
    rows: list[dict[str, Any]],
) -> None:
    """落 gate_evidence 单元行：CE-Gate 每次决策的可回放审计记录（unit 口径）。

    参数:
        db_path: SQLite 路径。
        run_id: 训练 run ID。
        epoch: 该 gate 所属的轮次（1-based）。
        step: epoch 内 step 序号（0-based）。
        rows: 每 **单元** 一行，含 question_id/task_type/block_idx/baseline_correct/
            candidate_correct/e_value（该单元所在块判定后的累计 e 值）/
            stop_reason（仅最后一单元携带最终 stop_reason，其余空串）。
            question_id 字段承载 **unit_id**（single=question_id，pair=pair_id）——
            逐题明细在 predictions 表溯源，按 pair_id join 真实 question 表会 join 不上。

    关键实现:
        逐行 insert（非 insert_many），保证每行独立事务。
    """
    from app.harness.log import HarnessLog

    with HarnessLog(db_path, run_id) as log:
        log.create_table("gate_evidence", _GATE_EVIDENCE_COLS)
        for row in rows:
            log.insert("gate_evidence", {"epoch": epoch, "step": step, **row})


def read_gate_evidence(db_path: str, *, run_id: str) -> list[dict[str, Any]]:
    """回读指定 run 的 gate_evidence 全部行（纯读，不污染运行状态）。"""
    return _read_table(db_path, "gate_evidence", run_id)


# ---------------------------------------------------------------------------
# 报告函数（从 TRM4 loop_report.py 迁移）
# ---------------------------------------------------------------------------


def write_step_report(
    workspace_dir: Path,
    epoch: int,
    step: int,
    global_step: int,
    task_type: str,
    gate_action: str,
    candidate_acc: float,
    class_baseline_acc: float,
    edit_budget: int,
    rank_clip_triggered: bool,
    gate_w: int | None,
    gate_l: int | None,
    gate_e_value: float | None,
    gate_n_used: int | None,
    gate_stop_reason: str | None,
) -> Path:
    """写单个 (step, task_type) 快路径 gate 的最小观测记录 JSON。

    文件名按 (epoch, step, task_type) 命名，slug 由 task_type 规范化（小写、空格转 '-'）得到。

    参数:
        workspace_dir: 实验工作区目录。
        epoch: 当前轮次（1-based）。
        step: epoch 内 step 序号（0-based）。
        global_step: 全局步计数（驱动 edit_budget 退火）。
        task_type: 本条 gate 的任务类型。
        gate_action: 闸门动作（accept_confirmed / accept_provisional / reject /
            skipped / cooldown）。
        candidate_acc: 候选在 gate 已观测题上的准确率（观测口径）。
        class_baseline_acc: 基线在 gate 已观测题上的准确率（观测口径）。
        edit_budget: 该 step 按 global_step 退火得到的 per-target 编辑预算上限。
        rank_clip_triggered: 该 skill 进化是否触发了 rank 裁剪。
        gate_w: e-process 累计 W（基线错->候选对翻转数）；skipped/cooldown 路径传 None。
        gate_l: e-process 累计 L（基线对->候选错翻转数）；skipped/cooldown 路径传 None。
        gate_e_value: 停时的 e 值；skipped/cooldown 路径传 None。
        gate_n_used: gate 实际消费的阶梯题数；skipped/cooldown 路径传 None。
        gate_stop_reason: e-process 停止原因；skipped/cooldown 路径传 None。

    返回:
        写入的 step_report 文件路径。
    """
    report = {
        "epoch": epoch,
        "step": step,
        "global_step": global_step,
        "task_type": task_type,
        "gate_action": gate_action,
        "candidate_acc": candidate_acc,
        "class_baseline_acc": class_baseline_acc,
        "edit_budget": edit_budget,
        "rank_clip_triggered": rank_clip_triggered,
        "gate_w": gate_w,
        "gate_l": gate_l,
        "gate_e_value": gate_e_value,
        "gate_n_used": gate_n_used,
        "gate_stop_reason": gate_stop_reason,
    }
    slug = task_type.lower().replace(" ", "-")
    out_dir = workspace_dir / "analyses"
    out_dir.mkdir(parents=True, exist_ok=True)
    path = out_dir / f"step_report_e{epoch}_s{step}_{slug}.json"
    path.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
    return path


def write_epoch_report(
    workspace_dir: Path,
    epoch: int,
    system_tool_action: str,
    momentum_updated_task_types: list[str],
    best_val_acc: float,
) -> Path:
    """写 epoch 末慢更新汇总 JSON。

    慢更新无单一 ValidationOutcome，故本函数只落慢更新可观测的最小集：
    system/tool gate 动作、本 epoch 写过 momentum 的题型、慢更新后的全局 best。

    参数:
        workspace_dir: 实验工作区目录。
        epoch: 当前轮次（1-based）。
        system_tool_action: 慢更新 system/tool 动作（updated / reverted / none）。
        momentum_updated_task_types: 本 epoch 写过 momentum 的题型列表。
        best_val_acc: 慢更新后（含 best argmax）的全局 best 验证准确率。

    返回:
        写入的 epoch_report 文件路径。
    """
    report = {
        "epoch": epoch,
        "system_tool_action": system_tool_action,
        "momentum_updated_task_types": momentum_updated_task_types,
        "best_val_acc": best_val_acc,
    }
    out_dir = workspace_dir / "analyses"
    out_dir.mkdir(parents=True, exist_ok=True)
    path = out_dir / f"epoch_report_{epoch}.json"
    path.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
    return path
