#!/usr/bin/env python3
"""preflight_verify.py — 开盘前钉对钉验证

在 morning_health_check 之后运行（8:30），专测今天踩过的坑：
  1. MoFin ↔ cron 脚本同步
  2. 关键脚本可正常导入（修 import 遗漏）
  3. 关键函数可正常调用（修 NameError / 参数不匹配）
  4. DB 现金/资产公式一致性
  5. 关键 no_agent 脚本可执行（exit=0）

输出格式：PASS/FAIL 逐项。任意 FAIL → 写 TODO + 推报警。
"""
import sys, os, json, sqlite3, subprocess
from pathlib import Path
from datetime import datetime

MOFIN = Path("/home/hmo/MoFin")
DATA = MOFIN / "data"
DB_PATH = DATA / "mofin.db"
PROFILE_SCRIPTS = Path("/home/hmo/.hermes/profiles/position-analyst/scripts")

results = {"pass": 0, "fail": 0, "items": []}
def check(name, ok, detail=""):
    results["items"].append({"name": name, "ok": bool(ok), "detail": detail})
    if ok:
        results["pass"] += 1
        print(f"  ✅ {name}")
    else:
        results["fail"] += 1
        print(f"  ❌ {name}")
        if detail:
            print(f"      {detail}")

# ── 1. 同步检查 ──
def check_sync():
    r = subprocess.run(["bash", str(MOFIN/"scripts/deploy_sync.sh"), "--check"],
                       capture_output=True, text=True, timeout=15)
    ok = "一致" in r.stdout
    check("脚本同步: MoFin↔cron目录", ok, r.stdout.strip())

# ── 2. 关键脚本导入检查 ──
SCRIPTS = [
    "price_monitor", "strategy_review", "collect_evaluation_data",
    "data_governance", "market_screener", "stock_quote",
    "strategy_evaluator", "mo_data", "mofin_db",
]
def check_imports():
    for name in SCRIPTS:
        path = PROFILE_SCRIPTS / f"{name}.py"
        if not path.exists():
            check(f"导入检查: {name}", False, f"文件不存在: {path}")
            continue
        # 检查是否有明显的语法/依赖错误
        r = subprocess.run([sys.executable, "-c", f"import sys; sys.path.insert(0, '{PROFILE_SCRIPTS}'); import {name}; print('ok')"],
                          capture_output=True, text=True, timeout=10)
        ok = r.returncode == 0
        err = r.stderr.strip()[:200] if not ok else ""
        check(f"导入检查: {name}", ok, err)

# ── 3. DB 完整性检查 ──
def check_db():
    if not DB_PATH.exists():
        check("DB 文件存在", False, f"找不到 {DB_PATH}")
        return
    try:
        conn = sqlite3.connect(str(DB_PATH))
        pragma = conn.execute("PRAGMA integrity_check").fetchone()[0]
        check("DB 完整性", pragma == "ok", pragma)
        
        # 检查核心表是否存在
        core_tables = ["portfolio_summary", "holdings", "holding_strategies", "cash_log", "live_prices"]
        missing = []
        for t in core_tables:
            r = conn.execute(f"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='{t}'").fetchone()[0]
            if r == 0: missing.append(t)
        check("核心表完整", len(missing)==0, f"缺失: {missing}" if missing else "")
        
        # 现金/资产公式检查
        try:
            ps = conn.execute("SELECT id, cash, frozen_cash, total_mv, total_assets, updated_at FROM portfolio_summary ORDER BY id DESC LIMIT 1").fetchone()
            if ps:
                # total_assets = total_mv + cash + frozen_cash
                expected = ps[3] + ps[1] + ps[2] if ps[3] else 0
                actual = ps[4]
                if expected > 0 and actual:
                    diff = abs(actual - expected)
                    ok = diff < 1.0  # 最多差1元
                    check(f"资产公式: total_mv({ps[3]:.0f})+cash({ps[1]:.0f})+frozen({ps[2]:.0f})={expected:.0f} vs total_assets({actual:.0f})",
                          ok, f"差异{diff:.2f}")
                else:
                    check("资产公式验证", False, f"total_mv或total_assets为空: mv={ps[3]}, assets={ps[4]}")
            else:
                check("资产公式验证", False, "portfolio_summary无数据")
        except Exception as e:
            check("资产公式验证", False, str(e))
        
        conn.close()
    except Exception as e:
        check("DB 检查异常", False, str(e))

# ── 4. 关键 no_agent 脚本快速执行检查 ──
SMOKE_SCRIPTS = [
    ("stock_quote.py", ["--code", "000001", "--once"]),
    ("strategy_review.py", None),  # 不加参数运行
]
def check_smoke():
    for name, args in SMOKE_SCRIPTS:
        cmd = [sys.executable, str(PROFILE_SCRIPTS / name)]
        if args: cmd.extend(args)
        r = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
        ok = r.returncode == 0
        err = r.stderr.strip()[:200] if not ok else ""
        out = r.stdout.strip()[:100] if ok else ""
        check(f"冒烟测试: {name}", ok, err or out)

# ── 5. 关键cron时间戳检查 ──
def check_cron_timestamps():
    cron_path = Path("/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json")
    if not cron_path.exists():
        check("cron时间戳", False, "jobs.json不存在")
        return
    with open(cron_path) as f:
        jobs = json.load(f).get("jobs", [])
    now = datetime.now()
    stale_count = 0
    for j in jobs:
        if not j.get("enabled", True):
            continue
        name = j.get("name", "?")
        last = j.get("last_run_at", "")
        status = j.get("last_status", "")
        if not last or not status:
            continue
        # 非周任务检查是否在48小时内跑过
        try:
            last_dt = datetime.fromisoformat(last)
            days_gone = (now - last_dt).total_seconds() / 86400
            # 周任务可接受7天，日任务可接受2天
            limit = 7 if any(k in name for k in ["每周", "周末", "周六"]) else 2
            if days_gone > limit and status != "ok":
                stale_count += 1
        except:
            pass
    check("cron过期检查", stale_count == 0, f"{stale_count}个cron长时间未正常运行" if stale_count else "")

# ── 7. 提示词一致性检查 ──
def check_prompt_sync():
    """检查注册的cron prompt版本与jobs.json实际运行的是否一致"""
    try:
        reg_path = "/home/hmo/projects/MoFin/data/prompts/registry.json"
        cron_path = "/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json"
        if not os.path.exists(reg_path) or not os.path.exists(cron_path):
            check("提示词一致性", False, "registry或jobs.json不存在")
            return
        with open(reg_path) as f:
            reg = json.load(f)
        with open(cron_path) as f:
            crons = json.load(f).get("jobs", [])
        # 建立cron prompt索引
        cron_prompts = {}
        for j in crons:
            name = j.get("name", "")
            pid = name.replace(" ","-").replace("（","-").replace("）","").replace("(","-").replace(")","")
            if j.get("prompt"):
                cron_prompts[pid] = {"name": name, "prompt": j["prompt"]}
        # 逐个检查注册的版本文件是否与cron一致
        drift = 0
        for p in reg.get("prompts", []):
            pid = p["id"]
            if pid not in cron_prompts:
                continue
            cv = p.get("current_version", "v1")
            # 找版本文件路径
            content_path = ""
            for v in p.get("versions", []):
                if v.get("version") == cv:
                    content_path = v.get("content_path", "")
                    break
            if not content_path or not os.path.exists(content_path):
                drift += 1
                continue
            with open(content_path) as f:
                registered = f.read()
            actual = cron_prompts[pid]["prompt"]
            if registered != actual:
                drift += 1
        check("提示词一致性: 注册版≈运行版", drift == 0,
              f"{drift}个prompt不一致" if drift else "")
    except Exception as e:
        check("提示词一致性", False, str(e))
def check_db_locks():
    """检查最近24小时是否有 database is locked 错误"""
    try:
        import subprocess
        r = subprocess.run(
            ["sudo", "journalctl", "-u", "xmpp-zhiwei", "--since", "24 hours ago", "--no-pager"],
            capture_output=True, text=True, timeout=30
        )
        locked_lines = [l for l in r.stdout.split("\n") if "database is locked" in l.lower()]
        check("DB死锁: 24h内lock错误", len(locked_lines) == 0,
              f"发现{len(locked_lines)}次: {locked_lines[0][:120]}" if locked_lines else "")
    except Exception as e:
        check("DB死锁检查", False, str(e))

if __name__ == "__main__":
    print("🔍 MoFin 开盘前验证", datetime.now().strftime("%m-%d %H:%M"))
    print("=" * 40)
    check_sync()
    check_imports()
    check_db()
    check_smoke()
    check_cron_timestamps()
    check_db_locks()
    check_prompt_sync()
    
    # 汇总
    print()
    total = results["pass"] + results["fail"]
    status = "✅ 全部通过" if results["fail"] == 0 else f"❌ {results['fail']}/{total} 项失败"
    summary = {
        "timestamp": datetime.now().isoformat(),
        "total": total,
        "pass": results["pass"],
        "fail": results["fail"],
        "status": "pass" if results["fail"] == 0 else "fail",
        "items": results["items"],
    }
    summary_path = DATA / "preflight_result.json"
    with open(summary_path, "w") as f:
        json.dump(summary, f, ensure_ascii=False, indent=2)
    print(f"{status} | {summary_path}")
    
    # FAIL 项写 TODO
    if results["fail"] > 0:
        failures = [i["name"] for i in results["items"] if not i["ok"]]
        todo_text = f"开盘前验证发现 {results['fail']} 项失败: {'; '.join(failures)}"
        print(f"\n⚠️ 已记录TODO: {todo_text}")
        # 尝试写mofin.db todos
        try:
            conn = sqlite3.connect(str(DB_PATH))
            conn.execute("""
                INSERT OR REPLACE INTO todos (id, content, status, source, created_at, fix_action)
                VALUES (?, ?, 'pending', 'preflight', datetime('now'), 'check preflight_result.json and fix issues')
            """, (f"preflight-{datetime.now().strftime('%Y%m%d')}", todo_text))
            conn.commit()
            conn.close()
        except:
            pass
    
    sys.exit(0 if results["fail"] == 0 else 1)
