"""预算↔现金流↔行动 三闭环异常自检 — 路线图R5 (2026-08-30)

预算闭环加固：预算执行率异常（<70% 或 >110%）的KPI，
检查是否同步了 现金流计划(CashPlan) 和 行动方案(ActionPlan)，
缺失则输出提示（防止"预算改了，现金流/行动没跟上"）。

输出：控制台 + reports/closed_loop_check_YYYYMMDD.md
用法: /root/cma-management/backend/venv/bin/python3 scripts/closed_loop_check.py [--period 2026-08] [--push]
"""
import sys
import os
import json
import argparse
from datetime import datetime

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

from app.database import get_session_local
from app.models import KPIDefinition, KPIValue, BudgetPlan, CashPlan, ActionPlan

LOW_RATIO = 0.7
HIGH_RATIO = 1.1
REPORTS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "reports")


def check_entity(db, entity_id: int, period: str) -> dict:
    """检测一个账套的闭环状态"""
    issues = []
    rows = db.query(BudgetPlan).filter(
        BudgetPlan.entity_id == entity_id,
        BudgetPlan.status == "active",
        BudgetPlan.period == period,
        BudgetPlan.budget_value > 0,
    ).all()

    seen = set()
    for b in rows:
        key = (b.kpi_id, b.period)
        if key in seen:
            continue
        seen.add(key)

        k = db.query(KPIDefinition).filter(KPIDefinition.id == b.kpi_id).first()
        kpi_name = k.kpi_name if k else f"KPI#{b.kpi_id}"

        actual = db.query(KPIValue).filter(
            KPIValue.kpi_id == b.kpi_id,
            KPIValue.period == b.period,
            KPIValue.actual_value.isnot(None),
        ).order_by(KPIValue.calculated_at.desc()).first()

        actual_val = actual.actual_value if actual else None
        if actual_val is None:
            continue
        ratio = actual_val / b.budget_value
        abnormal = ratio < LOW_RATIO or ratio > HIGH_RATIO
        if not abnormal:
            continue

        # 现金流检查：该KPI该期间是否有收付款计划（related_kpi_id 或 budget_plan_id 关联）
        period_start = datetime.strptime(period + "-01", "%Y-%m-%d")
        if period.endswith("-12"):
            period_end = datetime(period_start.year + 1, 1, 1)
        else:
            period_end = datetime(period_start.year, period_start.month + 1, 1)
        cash_plans = db.query(CashPlan).filter(
            CashPlan.entity_id == entity_id,
            CashPlan.status.in_(["pending", "completed"]),
            CashPlan.plan_date >= period_start,
            CashPlan.plan_date < period_end,
        ).filter(
            (CashPlan.related_kpi_id == b.kpi_id) | (CashPlan.budget_plan_id == b.id)
        ).count()
        # 兜底：无关联但期间内有任意现金流计划也算基本闭环
        any_cash = db.query(CashPlan).filter(
            CashPlan.entity_id == entity_id,
            CashPlan.status.in_(["pending", "completed"]),
            CashPlan.plan_date >= period_start,
            CashPlan.plan_date < period_end,
        ).count()

        # 行动检查：该KPI是否有非完成的行动方案
        actions = db.query(ActionPlan).filter(
            ActionPlan.kpi_id == b.kpi_id,
            ActionPlan.status.in_(["pending", "in_progress"]),
        ).count()

        missing = []
        if cash_plans == 0:
            if any_cash > 0:
                missing.append("现金流(本期间有其他计划但未关联本KPI)")
            else:
                missing.append("现金流")
        if actions == 0:
            missing.append("行动方案")

        level = "critical" if ratio > HIGH_RATIO else "warning"
        issues.append({
            "kpi_id": b.kpi_id,
            "kpi_name": kpi_name,
            "period": period,
            "budget_value": b.budget_value,
            "actual_value": actual_val,
            "exec_ratio": round(ratio * 100, 1),
            "abnormal_type": "超预算" if ratio > HIGH_RATIO else "低执行",
            "level": level,
            "cash_plan_count": cash_plans,
            "action_plan_count": actions,
            "missing": missing,
            "suggestion": (
                f"预算执行率{ratio*100:.0f}%异常，请同步"
                + ("现金流计划" if "现金流" in missing else "现金流情况核对")
                + ("、行动方案" if "行动方案" in missing else "")
                + f"（{kpi_name} {period}）"
            ),
        })

    return {"entity_id": entity_id, "period": period, "issues": issues}


def build_report(results: list, checked_at: str) -> str:
    lines = [f"# 预算↔现金流↔行动 闭环自检报告", f"**检查时间**: {checked_at}", ""]
    total_issues = 0
    for r in results:
        lines.append(f"## 账套 #{r['entity_id']} · 期间 {r['period']}")
        if not r["issues"]:
            lines.append("- ✅ 无预算执行率异常")
        for it in r["issues"]:
            total_issues += 1
            icon = "🔴" if it["level"] == "critical" else "🟡"
            lines.append(f"- {icon} {it['kpi_name']}（{it['period']}）")
            lines.append(f"  预算 {it['budget_value']:g} / 实际 {it['actual_value']:g} = 执行率 {it['exec_ratio']}%（{it['abnormal_type']}）")
            lines.append(f"  现金流计划: {it['cash_plan_count']} 条 | 行动方案: {it['action_plan_count']} 条")
            if it["missing"]:
                lines.append(f"  ⚠️ 缺失: {'、'.join(it['missing'])}")
                lines.append(f"  💡 {it['suggestion']}")
            else:
                lines.append(f"  ✅ 三闭环已同步")
        lines.append("")
    lines.append(f"---")
    lines.append(f"共发现异常 {total_issues} 项")
    return "\n".join(lines)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--period", default=datetime.now().strftime("%Y-%m"))
    parser.add_argument("--entity-id", type=int, default=1)
    parser.add_argument("--push", action="store_true", help="异常时推送企微(8800/send)")
    args = parser.parse_args()

    os.makedirs(REPORTS_DIR, exist_ok=True)
    checked_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    db = get_session_local()()
    try:
        result = check_entity(db, args.entity_id, args.period)
        report = build_report([result], checked_at)
        print(report)

        # 写报告文件
        fname = f"closed_loop_check_{datetime.now().strftime('%Y%m%d')}.md"
        fpath = os.path.join(REPORTS_DIR, fname)
        with open(fpath, "w", encoding="utf-8") as f:
            f.write(report)
        print(f"\n📄 报告已写入: {fpath}")

        # 异常推送
        if args.push and result["issues"]:
            try:
                import urllib.request
                import urllib.parse
                content = f"## 🔄 预算闭环自检({args.period})\n"
                for it in result["issues"][:10]:
                    content += f"- {it['kpi_name']} 执行率{it['exec_ratio']}% 缺{'/'.join(it['missing']) or '无'}\n"
                content += f"\n共{len(result['issues'])}项异常，详见系统报告"
                data = urllib.parse.urlencode({"msg": content, "source": "管理会计OS"}).encode("utf-8")
                req = urllib.request.Request("http://127.0.0.1:8800/send", data=data)
                with urllib.request.urlopen(req, timeout=15) as resp:
                    print("推送:", resp.read().decode()[:200])
            except Exception as e:
                print(f"推送失败: {e}")
    finally:
        db.close()


if __name__ == "__main__":
    main()
