"""Insurance sales pipeline CLI."""
from __future__ import annotations

import argparse
import json
import re
import sqlite3
import sys
from datetime import datetime
from typing import Any

from insurance_pipeline_cli import db, orchestrator

try:
    sys.stdout.reconfigure(encoding="utf-8")
    sys.stderr.reconfigure(encoding="utf-8")
except (AttributeError, OSError):
    pass


def print_json(obj: Any) -> None:
    print(json.dumps(obj, ensure_ascii=False, indent=2))


def next_id(conn: sqlite3.Connection, prefix: str, table: str) -> str:
    rows = conn.execute(
        f"SELECT id FROM {table} WHERE id GLOB ?", (f"{prefix}[0-9]*",)
    ).fetchall()
    max_n = 0
    for r in rows:
        m = re.match(rf"^{prefix}(\d+)$", r["id"])
        if m:
            max_n = max(max_n, int(m.group(1)))
    return f"{prefix}{max_n + 1:03d}"


STAGES = ("在谈", "方案中", "议价", "已成交", "已流失")
STAGE_PROBABILITY = {"在谈": 0.20, "方案中": 0.50, "议价": 0.80, "已成交": 1.0, "已流失": 0.0}
ACTIVE_STAGES = ("在谈", "方案中", "议价")
LOST_REASONS = ("价格", "条款", "竞品", "自身", "其他")


# ---------- deal CRUD ----------

def cmd_deal_add(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    if not orchestrator.customer_exists(args.customer_id):
        print_json({"ok": False, "error": "customer_not_found",
                    "hint": "请先在 insurance-customer-policy 录入该客户"})
        return 1
    customer_name = ""
    cd = orchestrator.customer_query(args.customer_id)
    if cd.get("ok") and cd.get("customers"):
        customer_name = cd["customers"][0].get("name") or ""
    if args.product:
        pd = orchestrator.product_query(args.product)
        if not (pd.get("ok") and pd.get("products")):
            print_json({"ok": False, "error": "product_not_found",
                        "hint": "请先在 insurance-product-analysis 录入该产品",
                        "product": args.product})
            return 1
    stage = args.stage or "在谈"
    if stage not in STAGES:
        print_json({"ok": False, "error": "bad_stage", "stages": list(STAGES)})
        return 1
    did = args.deal_id or next_id(conn, "D", "deals")
    conn.execute(
        """INSERT INTO deals
           (id, customer_id, customer_name, product_name, product_type,
            stage, estimated_premium, expected_close_date, note)
           VALUES (?,?,?,?,?,?,?,?,?)""",
        (
            did,
            args.customer_id,
            customer_name,
            args.product or "",
            args.product_type or "",
            stage,
            float(args.estimated_premium or 0),
            args.expected_close_date or "",
            args.note or "",
        ),
    )
    conn.execute(
        "INSERT INTO deal_stage_log (deal_id, from_stage, to_stage, note) VALUES (?,?,?,?)",
        (did, None, stage, "deal-add"),
    )
    conn.commit()
    print_json({"ok": True, "id": did, "customer_id": args.customer_id, "stage": stage})
    return 0


def cmd_deal_query(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    cur = conn.cursor()
    if args.deal_id:
        rows = list(cur.execute("SELECT * FROM deals WHERE id=?", (args.deal_id,)))
    elif args.customer_id:
        rows = list(cur.execute("SELECT * FROM deals WHERE customer_id=? ORDER BY created_at DESC",
                                (args.customer_id,)))
    elif args.stage:
        rows = list(cur.execute("SELECT * FROM deals WHERE stage=? ORDER BY created_at DESC", (args.stage,)))
    elif args.status == "active":
        placeholders = ",".join("?" * len(ACTIVE_STAGES))
        rows = list(cur.execute(
            f"SELECT * FROM deals WHERE stage IN ({placeholders}) ORDER BY created_at DESC",
            ACTIVE_STAGES,
        ))
    else:
        rows = list(cur.execute("SELECT * FROM deals ORDER BY created_at DESC"))
    print_json({"ok": True, "deals": [dict(r) for r in rows], "count": len(rows)})
    return 0


_DEAL_UPDATE_FIELDS = {
    "customer_id": "customer_id",
    "product_name": "product_name",
    "product_type": "product_type",
    "estimated_premium": "estimated_premium",
    "actual_premium": "actual_premium",
    "expected_close_date": "expected_close_date",
    "note": "note",
}


def cmd_deal_update(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    try:
        payload = json.loads(args.set)
    except json.JSONDecodeError as e:
        print_json({"ok": False, "error": "bad_json", "hint": str(e)})
        return 1
    row = conn.execute("SELECT id FROM deals WHERE id=?", (args.deal_id,)).fetchone()
    if not row:
        print_json({"ok": False, "error": "deal_not_found"})
        return 1
    sets, vals = [], []
    for k, v in payload.items():
        col = _DEAL_UPDATE_FIELDS.get(k)
        if col is not None:
            sets.append(f"{col}=?")
            vals.append(v)
    if sets:
        sets.append("updated_at=datetime('now','localtime')")
        vals.append(args.deal_id)
        conn.execute(f"UPDATE deals SET {', '.join(sets)} WHERE id=?", vals)
        conn.commit()
    print_json({"ok": True, "id": args.deal_id})
    return 0


def cmd_deal_delete(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    row = conn.execute("SELECT id FROM deals WHERE id=?", (args.deal_id,)).fetchone()
    if not row:
        print_json({"ok": False, "error": "deal_not_found"})
        return 1
    if not args.yes:
        print_json({"ok": False, "error": "need_confirm", "hint": "请加 --yes 确认删除"})
        return 1
    conn.execute("DELETE FROM deals WHERE id=?", (args.deal_id,))
    conn.commit()
    print_json({"ok": True})
    return 0


# ---------- deal-stage ----------

def cmd_deal_stage(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    if args.to not in STAGES:
        print_json({"ok": False, "error": "bad_stage", "stages": list(STAGES)})
        return 1
    row = conn.execute("SELECT * FROM deals WHERE id=?", (args.deal_id,)).fetchone()
    if not row:
        print_json({"ok": False, "error": "deal_not_found"})
        return 1
    from_stage = row["stage"]
    sets = ["stage=?", "updated_at=datetime('now','localtime')"]
    vals: list[Any] = [args.to]
    if args.to == "已成交":
        sets.append("closed_at=datetime('now','localtime')")
        if args.actual_premium is not None:
            sets.append("actual_premium=?")
            vals.append(float(args.actual_premium))
    if args.to == "已流失":
        sets.append("closed_at=datetime('now','localtime')")
        if args.lost_reason:
            sets.append("lost_reason=?")
            vals.append(args.lost_reason)
        if args.lost_note:
            sets.append("lost_note=?")
            vals.append(args.lost_note)
    vals.append(args.deal_id)
    conn.execute(f"UPDATE deals SET {', '.join(sets)} WHERE id=?", vals)
    conn.execute(
        "INSERT INTO deal_stage_log (deal_id, from_stage, to_stage, note) VALUES (?,?,?,?)",
        (args.deal_id, from_stage, args.to, args.note or ""),
    )
    conn.commit()
    print_json({"ok": True, "id": args.deal_id, "from": from_stage, "to": args.to})
    return 0


# ---------- deal-forecast ----------

def _month_bounds(month: str) -> tuple[str, str]:
    """month = 'YYYY-MM' → ('YYYY-MM-01 00:00:00', 'YYYY-(MM+1)-01 00:00:00')."""
    y, m = map(int, month.split("-"))
    start = f"{y:04d}-{m:02d}-01 00:00:00"
    if m == 12:
        end = f"{y + 1:04d}-01-01 00:00:00"
    else:
        end = f"{y:04d}-{m + 1:02d}-01 00:00:00"
    return start, end


def cmd_deal_forecast(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    month = args.month or datetime.now().strftime("%Y-%m")
    cur = conn.cursor()
    by_stage: dict[str, dict] = {}
    by_type: dict[str, float] = {}
    total_forecast = 0.0
    closed = 0.0
    rows = list(cur.execute("SELECT * FROM deals"))
    for r in rows:
        if r["stage"] == "已成交":
            if r["closed_at"]:
                start, end = _month_bounds(month)
                if start <= r["closed_at"] < end:
                    closed += float(r["actual_premium"] or r["estimated_premium"] or 0)
        elif r["stage"] in ACTIVE_STAGES:
            prob = STAGE_PROBABILITY[r["stage"]]
            est = float(r["estimated_premium"] or 0)
            forecast = prob * est
            total_forecast += forecast
            slot = by_stage.setdefault(r["stage"], {"count": 0, "estimated": 0.0, "forecast": 0.0})
            slot["count"] += 1
            slot["estimated"] += est
            slot["forecast"] += forecast
            ptype = r["product_type"] or "其他"
            by_type[ptype] = by_type.get(ptype, 0.0) + forecast
    target_row = conn.execute(
        "SELECT target_premium FROM monthly_targets WHERE month=?", (month,)
    ).fetchone()
    target = float(target_row["target_premium"]) if target_row else 0.0
    print_json({
        "ok": True,
        "month": month,
        "by_stage": by_stage,
        "by_type": by_type,
        "total_forecast": round(total_forecast, 2),
        "closed": round(closed, 2),
        "target": target,
        "gap_to_target": round(max(0.0, target - closed - total_forecast), 2),
    })
    return 0


# ---------- target-track ----------

def _days_remaining_in_month(month: str) -> int:
    y, m = map(int, month.split("-"))
    if m == 12:
        next_first = datetime(y + 1, 1, 1)
    else:
        next_first = datetime(y, m + 1, 1)
    today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
    if today >= next_first:
        return 0
    return max(0, (next_first - today).days)


def cmd_target_track(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    month = args.month or datetime.now().strftime("%Y-%m")
    if args.target is not None:
        conn.execute(
            """INSERT INTO monthly_targets (month, target_premium, note)
               VALUES (?,?,?)
               ON CONFLICT(month) DO UPDATE SET
                 target_premium=excluded.target_premium,
                 note=excluded.note,
                 updated_at=datetime('now','localtime')""",
            (month, float(args.target), args.note or ""),
        )
        conn.commit()
    target_row = conn.execute(
        "SELECT target_premium, note FROM monthly_targets WHERE month=?", (month,)
    ).fetchone()
    target = float(target_row["target_premium"]) if target_row else 0.0
    target_note = target_row["note"] if target_row else ""
    start, end = _month_bounds(month)
    closed = float(conn.execute(
        """SELECT COALESCE(SUM(COALESCE(actual_premium, estimated_premium, 0)),0) AS s
           FROM deals WHERE stage='已成交' AND closed_at >= ? AND closed_at < ?""",
        (start, end),
    ).fetchone()["s"])
    forecast = 0.0
    forecast_breakdown: dict[str, float] = {}
    for stage in ACTIVE_STAGES:
        s_total = float(conn.execute(
            "SELECT COALESCE(SUM(estimated_premium),0) AS s FROM deals WHERE stage=?", (stage,),
        ).fetchone()["s"])
        forecast_breakdown[stage] = round(s_total * STAGE_PROBABILITY[stage], 2)
        forecast += s_total * STAGE_PROBABILITY[stage]
    gap = max(0.0, target - closed)
    days_left = _days_remaining_in_month(month)
    progress = (closed / target * 100) if target > 0 else 0.0
    bar_filled = int(progress / 100 * 16)
    bar = "█" * max(0, min(16, bar_filled)) + "░" * max(0, 16 - max(0, min(16, bar_filled)))
    md = (
        f"🎯 **{month} 目标跟踪**\n\n"
        f"目标：¥{target:,.0f}\n"
        f"已完成：¥{closed:,.0f}（{progress:.0f}%）\n"
        f"在谈预估：¥{forecast:,.0f}\n"
        f"差距：¥{gap:,.0f}\n\n"
        f"⏰ 本月剩余：{days_left} 天\n"
        f"📊 完成进度：{bar} {progress:.0f}%"
    )
    print_json({
        "ok": True,
        "month": month,
        "target": target,
        "target_note": target_note,
        "closed": round(closed, 2),
        "forecast": round(forecast, 2),
        "forecast_breakdown": forecast_breakdown,
        "gap": round(gap, 2),
        "progress_pct": round(progress, 2),
        "days_remaining": days_left,
        "markdown": md,
    })
    return 0


# ---------- deal-lost ----------

def cmd_deal_lost(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    if args.stats:
        month = args.month or datetime.now().strftime("%Y-%m")
        start, end = _month_bounds(month)
        rows = list(conn.execute(
            """SELECT lost_reason, COUNT(*) AS n, COALESCE(SUM(estimated_premium),0) AS s
               FROM deals WHERE stage='已流失' AND closed_at >= ? AND closed_at < ?
               GROUP BY lost_reason ORDER BY n DESC""",
            (start, end),
        ))
        total_n = sum(r["n"] for r in rows)
        total_premium = sum(float(r["s"]) for r in rows)
        breakdown = [{"reason": (r["lost_reason"] or "其他"),
                      "count": int(r["n"]),
                      "premium": float(r["s"])} for r in rows]
        print_json({"ok": True, "month": month, "total_count": total_n,
                    "total_premium": round(total_premium, 2),
                    "by_reason": breakdown})
        return 0
    if not args.deal_id:
        print_json({"ok": False, "error": "missing_args", "hint": "请提供 --deal-id 或 --stats"})
        return 1
    if args.reason and args.reason not in LOST_REASONS:
        print_json({"ok": False, "error": "bad_reason", "reasons": list(LOST_REASONS)})
        return 1
    row = conn.execute("SELECT * FROM deals WHERE id=?", (args.deal_id,)).fetchone()
    if not row:
        print_json({"ok": False, "error": "deal_not_found"})
        return 1
    from_stage = row["stage"]
    conn.execute(
        """UPDATE deals SET stage='已流失', lost_reason=?, lost_note=?,
           closed_at=datetime('now','localtime'),
           updated_at=datetime('now','localtime') WHERE id=?""",
        (args.reason or "其他", args.note or "", args.deal_id),
    )
    conn.execute(
        "INSERT INTO deal_stage_log (deal_id, from_stage, to_stage, note) VALUES (?,?,?,?)",
        (args.deal_id, from_stage, "已流失", args.note or ""),
    )
    conn.commit()
    print_json({"ok": True, "id": args.deal_id, "from": from_stage, "to": "已流失"})
    return 0


# ---------- dashboard ----------

def cmd_dashboard(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    month = datetime.now().strftime("%Y-%m")
    start, end = _month_bounds(month)
    placeholders = ",".join("?" * len(ACTIVE_STAGES))
    active = int(conn.execute(
        f"SELECT COUNT(*) AS n FROM deals WHERE stage IN ({placeholders})", ACTIVE_STAGES,
    ).fetchone()["n"])
    closed = float(conn.execute(
        """SELECT COALESCE(SUM(COALESCE(actual_premium, estimated_premium, 0)),0) AS s
           FROM deals WHERE stage='已成交' AND closed_at >= ? AND closed_at < ?""",
        (start, end),
    ).fetchone()["s"])
    forecast = 0.0
    for stage in ACTIVE_STAGES:
        s_total = float(conn.execute(
            "SELECT COALESCE(SUM(estimated_premium),0) AS s FROM deals WHERE stage=?", (stage,),
        ).fetchone()["s"])
        forecast += s_total * STAGE_PROBABILITY[stage]
    target_row = conn.execute(
        "SELECT target_premium FROM monthly_targets WHERE month=?", (month,)
    ).fetchone()
    target = float(target_row["target_premium"]) if target_row else 0.0
    print_json({
        "ok": True,
        "skill": "insurance-sales-pipeline",
        "this_month": month,
        "active_deals": active,
        "this_month_target": target,
        "this_month_closed": round(closed, 2),
        "this_month_forecast": round(forecast, 2),
    })
    return 0


# ---------- argparse ----------

def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(prog="insurance_pipeline_cli")
    sub = p.add_subparsers(dest="cmd", required=True)

    a = sub.add_parser("deal-add")
    a.add_argument("--customer-id", required=True)
    a.add_argument("--deal-id")
    a.add_argument("--product")
    a.add_argument("--product-type")
    a.add_argument("--stage")
    a.add_argument("--estimated-premium", type=float)
    a.add_argument("--expected-close-date")
    a.add_argument("--note")
    a.set_defaults(func=cmd_deal_add)

    a = sub.add_parser("deal-query")
    a.add_argument("--deal-id")
    a.add_argument("--customer-id")
    a.add_argument("--stage")
    a.add_argument("--status", choices=["active"])
    a.set_defaults(func=cmd_deal_query)

    a = sub.add_parser("deal-update")
    a.add_argument("--deal-id", required=True)
    a.add_argument("--set", required=True)
    a.set_defaults(func=cmd_deal_update)

    a = sub.add_parser("deal-delete")
    a.add_argument("--deal-id", required=True)
    a.add_argument("--yes", action="store_true")
    a.set_defaults(func=cmd_deal_delete)

    a = sub.add_parser("deal-stage")
    a.add_argument("--deal-id", required=True)
    a.add_argument("--to", required=True)
    a.add_argument("--note")
    a.add_argument("--actual-premium", type=float)
    a.add_argument("--lost-reason")
    a.add_argument("--lost-note")
    a.set_defaults(func=cmd_deal_stage)

    a = sub.add_parser("deal-forecast")
    a.add_argument("--month")
    a.set_defaults(func=cmd_deal_forecast)

    a = sub.add_parser("target-track")
    a.add_argument("--month")
    a.add_argument("--target", type=float)
    a.add_argument("--note")
    a.set_defaults(func=cmd_target_track)

    a = sub.add_parser("deal-lost")
    a.add_argument("--deal-id")
    a.add_argument("--reason")
    a.add_argument("--note")
    a.add_argument("--stats", action="store_true")
    a.add_argument("--month")
    a.set_defaults(func=cmd_deal_lost)

    a = sub.add_parser("dashboard")
    a.add_argument("--json", action="store_true")
    a.set_defaults(func=cmd_dashboard)

    return p


def main(argv: list[str] | None = None) -> int:
    parser = build_parser()
    args = parser.parse_args(argv)
    conn = db.connect()
    try:
        return int(args.func(conn, args) or 0)
    except sqlite3.Error as e:
        print_json({"ok": False, "error": "sqlite_error", "hint": str(e)})
        return 1
    finally:
        conn.close()


if __name__ == "__main__":
    raise SystemExit(main())
