# -*- coding: utf-8 -*-
"""検証スクリプト（開発システム）。

最終パイプライン（上流ゲート + assessable=1ゲート → 線形回帰 / visual_plus_brix_slot）を
5-fold OOF 交差検証し、報告用の検証値（MAE / 相関 / グレード一致% / ±15点以内%）を出力する。
LLM 再実行なし・追加課金ゼロ。

使い方:
    python validate.py
    python validate.py --input <training_dataset互換CSV>
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path

import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold
from sklearn.pipeline import Pipeline

HERE = Path(__file__).resolve().parent
ROOT = HERE.parent
sys.path.insert(0, str(HERE / "core"))
import soup_scoring as S  # noqa: E402

DEFAULT_INPUT = ROOT / "local_batch_api" / "output_full" / "training_dataset.csv"
OUTDIR = HERE / "validation_results"
SEED, N = 42, 5


def oof(data: pd.DataFrame, fs, target: str, kf: KFold) -> tuple[np.ndarray, np.ndarray]:
    numeric, categorical = list(fs.numeric), list(fs.categorical)
    X = data[numeric + categorical]
    y = data[target].to_numpy()
    pred = np.zeros(len(y))
    for tr, te in kf.split(X):
        pipe = Pipeline([("prep", S._preprocessor(numeric, categorical)),
                         ("model", LinearRegression())])
        pipe.fit(X.iloc[tr], y[tr])
        pred[te] = np.clip(pipe.predict(X.iloc[te]), 0, 100)
    return y, pred


def stat(y: np.ndarray, pred: np.ndarray) -> dict:
    corr = float(np.corrcoef(y, pred)[0, 1]) if np.std(pred) > 0 else float("nan")
    return {
        "MAE": round(float(np.abs(y - pred).mean()), 2),
        "相関": round(corr, 3),
        "グレード一致%": round(float((S.grade(y) == S.grade(pred)).mean()) * 100, 1),
        "±15点以内%": round(float((np.abs(y - pred) <= 15).mean()) * 100, 1),
    }


def md_table(df: pd.DataFrame) -> str:
    cols = list(df.columns)
    out = ["| " + " | ".join(cols) + " |", "| " + " | ".join(["---"] * len(cols)) + " |"]
    for _, r in df.iterrows():
        out.append("| " + " | ".join(str(r[c]) for c in cols) + " |")
    return "\n".join(out)


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("--input", default=str(DEFAULT_INPUT))
    args = ap.parse_args()
    OUTDIR.mkdir(parents=True, exist_ok=True)

    raw = pd.read_csv(args.input)
    f = S.prepare(raw, require_targets=True)            # 範囲内のみ
    g = f[f["assessable"] == 1].reset_index(drop=True)  # 採点対象（2段ゲート後）
    fsets = S.feature_sets()
    kf = KFold(N, shuffle=True, random_state=SEED)

    print("=" * 64)
    print(" 最終パイプライン 報告用検証 (5-fold OOF / 線形回帰)")
    print("=" * 64)
    print(f"全件(範囲内): {len(f)}  /  採点対象(assessable=1): {len(g)}"
          f"  /  要確認分岐: {len(f) - len(g)} ({(len(f)-len(g))/len(f)*100:.1f}%)")
    print()

    rows = []
    for target, tname in [("human_total_score", "total"), ("human_soup_score", "soup")]:
        fs = fsets[S.TARGETS[target]]  # 本番採用の特徴量で検証
        y0, p0 = oof(f, fs, target, kf)
        s0 = stat(y0, p0); s0.update({"target": tname, "条件": "改修前(全件)", "n": len(f)})
        rows.append(s0)
        y1, p1 = oof(g, fs, target, kf)
        s1 = stat(y1, p1); s1.update({"target": tname, "条件": "改修後(採点対象)", "n": len(g)})
        rows.append(s1)

    res = pd.DataFrame(rows)[["target", "条件", "n", "MAE", "相関", "グレード一致%", "±15点以内%"]]
    res.to_csv(OUTDIR / "final_validation.csv", index=False, encoding="utf-8-sig")
    for t in ["total", "soup"]:
        print(f"[{t}]")
        print(res[res.target == t].to_string(index=False))
        print()

    summary = ["# 最終パイプライン 報告用検証値", "",
               "- 構成: 上流ゲート(RF-DETR可読性) + 採点ゲート(LLM assessable=1) → 線形回帰",
               f"- 特徴量: {S.TARGETS['human_total_score']} (動画LLM評価 + 濃度brix + 時間slot / LLM濃度は除外)",
               f"- 評価: {N}-fold OOF 交差検証 / seed={SEED}",
               f"- 全件(範囲内) {len(f)} 件中 採点対象 {len(g)} 件 / 要確認分岐 {len(f)-len(g)} 件", "",
               "## 結果", "", md_table(res)]
    (OUTDIR / "summary.md").write_text("\n".join(summary), encoding="utf-8")
    print("saved:", OUTDIR)


if __name__ == "__main__":
    main()
