# -*- coding: utf-8 -*-
"""
账本重建: 预检 → 执行 → 判收 (README 待办 #4)
==============================================
    docker compose run --rm --no-deps pms-web python scripts/rebuild_ledger.py          # 只预检
    docker compose run --rm --no-deps pms-web python scripts/rebuild_ledger.py --yes    # 预检通过就执行
    docker compose run --rm --no-deps pms-web python scripts/rebuild_ledger.py --accept # 只看判收
    (或 make rebuild / make rebuild GO=1 / make rebuild-accept)

清账之后账本是空的, 真实持仓要按「以下游为准」认领回来。这一步**只发生一次**, 但它定死了
每一只票的开仓价, 而开仓价一错, 后面每一条纪律都错在一个看不出来的地方:

    下游 cost_price → RECON 批次开仓价 → 摊薄成本 → 安全垫
                                                   ↓
                        盈利加仓(≥3%) · 保垫减仓(峰值≥6%) · 补仓评估档(−8%/−15%)

所以本脚本**默认只预检不执行**。加 `--yes` 才真的改账, 且预检不过照样不执行 (要强行放行得
再加 `--force`, 那等于声明"我确认这份数据就是对的, 接受安全垫从 0 起算")。

三段各自的意思:
  [1] 预检 —— 事实源给不给得出持仓 / 成本价能不能用 / 建完验不验得到纪律 (只读)
  [2] 执行 —— reconcile(apply_fix=True), 走的是生产同一条路, 不是另写一套
  [3] 判收 —— 安全垫分布是最硬的判据: 全 0 就是踩了"拿现价当成本"那个坑
"""
from __future__ import annotations

import argparse
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

OK, BAD, WARN = "  OK  ", " FAIL ", " WARN "


def _line(tag, msg):
    print(f"[{tag}] {msg}")


def _steps(steps):
    for s in steps:
        tag = OK if s.get("ok") else (WARN if s.get("blocking") is False else BAD)
        print(f"  [{tag}] {s['step']}: {s['why']}")


def show_preflight(pf) -> bool:
    print("=" * 72)
    print("[1] 预检 (只读, 一个字都不写)")
    print("=" * 72)
    _line("账本", f"当前持仓 {pf['book_held']} 只"
                  + (" —— 空账本, 这是首次建账, 成本价闸生效" if pf["first_build"]
                     else " —— 非空, 走的是日常对账口径, 成本价闸不生效"))
    src = pf.get("source") or {}
    _line("事实源", f"{src.get('source')} (mode={src.get('mode')}, "
                    f"age={src.get('age_sec')}s, 列={src.get('columns')})")
    for a in (src.get("alerts") or []):
        _line(a.get("level", "WARN"), a.get("message"))
    _steps(pf.get("steps") or [])

    chk = pf.get("cost_check") or {}
    if chk.get("rows"):
        print(f"\n  成本价明细 ({chk['n']} 只, 判定分布 {chk['counts']}):")
        for r in chk["rows"]:
            cu = "" if r.get("cushion_pct") is None else f" 安全垫 {r['cushion_pct']:+.1%}"
            mark = "  " if r["verdict"] == "OK" else "! "
            print(f"    {mark}{r['ts_code']:<12} {str(r['qty']):>7} 股  "
                  f"成本 {_g(r['cost'])}  现价 {_g(r['price'])}  "
                  f"[{r['verdict']}]{cu}")
            if r.get("why"):
                print(f"        {r['why']}")

    cov = pf.get("coverage") or {}
    if cov:
        print("\n  情形覆盖 (不阻断建账, 只影响这轮验不验得到纪律):")
        for k, v in (cov.get("counts") or {}).items():
            print(f"    [{OK if v else WARN}] {k}: {v}")

    sw = pf.get("switches") or {}
    if sw:
        print("\n  开关现状: " + " · ".join(f"{k}={v}" for k, v in sw.items()))
    print(f"\n  结论: {pf.get('hint')}")
    return bool(pf.get("ready"))


def show_accept(ac):
    print("=" * 72)
    print("[3] 判收 (只读)")
    print("=" * 72)
    _line("持仓", f"{ac.get('held')} 只")
    for c in ac.get("checks") or []:
        print(f"  [{OK if c['ok'] else BAD}] {c['check']}: {c['why']}")
    if ac.get("sector", {}).get("top"):
        print("\n  行业分布 (占规模):")
        for s in ac["sector"]["top"]:
            print(f"    {s['sector'] or '(无)':<20} {s['ratio']:>7.1%}  {s['names']} 只")
    print(f"\n  结论: {ac.get('hint')}")
    return bool(ac.get("ok"))


def _g(v):
    return "-" if v in (None, "") else f"{float(v):.4g}"


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--yes", action="store_true", help="预检通过就执行重建 (缺省只预检)")
    ap.add_argument("--force", action="store_true",
                    help="预检不过也执行。**这等于声明你接受安全垫从 0 起算**")
    ap.add_argument("--accept", action="store_true", help="只跑判收段 (重建已经做过了)")
    args = ap.parse_args()

    from app.services import ledger_service as ls

    if args.accept:
        return 0 if show_accept(ls.rebuild_accept()) else 1

    pf = ls.rebuild_preflight()
    ready = show_preflight(pf)

    # EMPTY 是**终态不是故障**: 账户空 + 账本空 = 两边一致, 没有需要接管的持仓,
    # 重建这一步本来就该跳过。原来它和「读不到事实源」「成本价不过关」一起退 2,
    # make 报 Error 2 —— 一个完全正常的状态被报成失败, 让人以为还有事没做完。
    if pf.get("verdict") == "EMPTY":
        print("\n(没有需要接管的持仓, 重建这一步跳过 —— **这不是错误**。)"
              if pf.get("ok") else
              "\n(顺序反了: 先 make reset-ledger CONFIRM=1 清账本, "
              "别让对账拿空集去核销已有持仓。)")
        return 0 if pf.get("ok") else 2

    if not args.yes:
        print("\n(只预检, 没有改任何东西。要执行加 --yes)")
        return 0 if ready else 2
    if not ready and not args.force:
        print("\n预检没过, 不执行。请对端把 trading_position 的 cost_price / "
              "available_quantity 填成真实值后重跑;\n"
              "确认这份数据就是对的可以加 --force 放行 (那意味着安全垫从 0 起算, "
              "这一轮的补仓/加仓/保垫减仓都判不准)。")
        return 2

    print("\n" + "=" * 72)
    print("[2] 执行 reconcile(apply_fix=True" + (", force=True" if args.force else "") + ")")
    print("=" * 72)
    res = ls.reconcile(apply_fix=True, force=args.force)
    _line("结果", f"ok={res.get('ok')} 事实源={res.get('source')} "
                  f"差异 {len(res.get('diffs') or [])} 项 修正 {len(res.get('fixes') or [])} 项")
    if res.get("blocked"):
        _line("BLOCKED", res["blocked"].get("why"))
        if res["blocked"].get("how"):
            _line("怎么办", res["blocked"]["how"])
        return 1
    for f in res.get("fixes") or []:
        print(f"    {f['ts_code']:<12} {f['op']:<14} {f['qty']:>7} 股 "
              f"@ {_g(f.get('price'))}  {f.get('price_source') or ''}")
    for e in res.get("errors") or []:
        _line("ERROR", e)

    print()
    ok = show_accept(ls.rebuild_accept())
    print("\n收尾 (脚本不替你做, 因为这几件事该由人确认):")
    for s in pf.get("after") or []:
        print(f"  - {s}")
    return 0 if ok else 1


if __name__ == "__main__":
    sys.exit(main())
