"""Insurance schedule & renewal reminder CLI (thin skill).

All business data is read from shared insurance.sqlite3.
"""
from __future__ import annotations

import argparse
import json
import sqlite3
import sys
from datetime import datetime, timedelta
from typing import Any

from insurance_schedule_cli import db, orchestrator

# Ensure UTF-8 stdout/stderr regardless of host shell codepage (Windows GBK etc.)
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 today() -> datetime:
    return datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)


def fmt_date(d: datetime) -> str:
    return d.strftime("%Y-%m-%d")


def parse_dt(s: str | None) -> datetime | None:
    if not s:
        return None
    try:
        return datetime.strptime(s.strip()[:10], "%Y-%m-%d")
    except ValueError:
        return None


# ---------- renewal ----------

def _renewal_items(days: int) -> list[dict]:
    """One reminder per policy; pick the earliest of (expire_date, next_pay_date) inside horizon."""
    horizon = today() + timedelta(days=int(days))
    horizon_str = fmt_date(horizon)
    by_expire = orchestrator.policy_query(expire_before=horizon_str)
    by_pay = orchestrator.policy_query(next_pay_before=horizon_str)
    best: dict[str, dict] = {}
    for src, key in [(by_expire, "expire"), (by_pay, "next_pay")]:
        for p in src.get("policies") or []:
            pid = p.get("id")
            if not pid:
                continue
            target_date = p.get("expire_date") if key == "expire" else p.get("next_pay_date")
            tdt = parse_dt(target_date)
            if not tdt or tdt < today() or tdt > horizon:
                continue
            days_remaining = (tdt - today()).days
            existing = best.get(pid)
            if existing and existing["days_remaining"] <= days_remaining:
                continue
            cust_d = orchestrator.customer_query(customer_id=p.get("customer_id"))
            cust = (cust_d.get("customers") or [{}])[0]
            best[pid] = {
                "policy_id": pid,
                "customer_id": p.get("customer_id"),
                "customer_name": cust.get("name") or "",
                "company": p.get("company"),
                "product_name": p.get("product_name"),
                "premium": p.get("premium"),
                "kind": key,
                "target_date": target_date,
                "days_remaining": days_remaining,
            }
    items = list(best.values())
    items.sort(key=lambda x: x["days_remaining"])
    return items


def _bucket_for_days(days: int) -> str:
    return f"{int(days)}d"


def _check_sent(conn: sqlite3.Connection, rtype: str, target: str, bucket: str) -> bool:
    row = conn.execute(
        "SELECT 1 FROM sent_reminders WHERE reminder_type=? AND target_id=? AND bucket=?",
        (rtype, target, bucket),
    ).fetchone()
    return bool(row)


def _mark_sent(conn: sqlite3.Connection, rtype: str, target: str, bucket: str, note: str = "") -> bool:
    try:
        conn.execute(
            "INSERT INTO sent_reminders (reminder_type, target_id, bucket, note) VALUES (?,?,?,?)",
            (rtype, target, bucket, note),
        )
        conn.commit()
        return True
    except sqlite3.IntegrityError:
        return False


def cmd_renewal_query(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    items = _renewal_items(int(args.days))
    print_json({"ok": True, "days": int(args.days), "items": items, "count": len(items)})
    return 0


def cmd_renewal_remind(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    items = _renewal_items(int(args.days))
    bucket = _bucket_for_days(args.days)
    new_items = []
    skipped = []
    for it in items:
        if _check_sent(conn, "renewal", it["policy_id"], bucket):
            skipped.append(it)
            continue
        if args.mark_sent:
            _mark_sent(conn, "renewal", it["policy_id"], bucket, note=it.get("kind", ""))
        new_items.append(it)
    print_json({"ok": True, "bucket": bucket, "new": new_items, "already_sent": skipped,
                "new_count": len(new_items), "skipped_count": len(skipped)})
    return 0


# ---------- followup ----------

def _followup_lapsed(days_threshold: int) -> list[dict]:
    cust = orchestrator.customer_query()
    fu = orchestrator.followup_query()
    last_by_cust: dict[str, str] = {}
    for f in fu.get("followups") or []:
        cid = f.get("customer_id")
        ts = f.get("created_at")
        if cid and ts and (cid not in last_by_cust or ts > last_by_cust[cid]):
            last_by_cust[cid] = ts
    out: list[dict] = []
    for c in cust.get("customers") or []:
        cid = c["id"]
        last = last_by_cust.get(cid) or c.get("last_followup_at")
        if not last:
            try:
                created = datetime.strptime(c["created_at"][:19], "%Y-%m-%d %H:%M:%S")
            except (ValueError, TypeError):
                continue
            days_since = (datetime.now() - created).days
            last_str = c.get("created_at")
        else:
            try:
                last_dt = datetime.strptime(last[:19], "%Y-%m-%d %H:%M:%S")
            except ValueError:
                continue
            days_since = (datetime.now() - last_dt).days
            last_str = last
        if days_since >= int(days_threshold):
            out.append({
                "customer_id": cid,
                "customer_name": c.get("name"),
                "phone": c.get("phone"),
                "days_since_followup": days_since,
                "last_followup_at": last_str,
            })
    out.sort(key=lambda x: x["days_since_followup"], reverse=True)
    return out


def cmd_followup_check(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    items = _followup_lapsed(int(args.days))
    print_json({"ok": True, "days": int(args.days), "items": items, "count": len(items)})
    return 0


def cmd_followup_remind(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    items = _followup_lapsed(int(args.days))
    bucket = _bucket_for_days(args.days)
    new_items, skipped = [], []
    for it in items:
        if _check_sent(conn, "followup", it["customer_id"], bucket):
            skipped.append(it)
            continue
        if args.mark_sent:
            _mark_sent(conn, "followup", it["customer_id"], bucket)
        new_items.append(it)
    print_json({"ok": True, "bucket": bucket, "new": new_items, "already_sent": skipped,
                "new_count": len(new_items), "skipped_count": len(skipped)})
    return 0


# ---------- dormant ----------

def cmd_dormant_query(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    items = _followup_lapsed(int(args.days))
    print_json({"ok": True, "days": int(args.days), "items": items, "count": len(items)})
    return 0


WAKE_TEMPLATES = {
    "renewal": "{name} 您好，您在 {company} 的{product}快到期了，正好趁此机会一起复盘下保障情况，有什么调整想做的吗？",
    "gap": "{name} 您好，最近梳理客户档案时注意到您还差{gap}方面的保障，有空我们聊 5 分钟看看怎么补齐？",
    "service": "{name} 您好，最近天气变化大，提醒您注意身体；顺便跟您同步一下今年理赔体验上的几个小变化。",
}


def cmd_dormant_wake(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    items = _followup_lapsed(int(args.days))
    suggestions: list[dict] = []
    for it in items:
        cid = it["customer_id"]
        gap = orchestrator.gap_analysis(customer_id=cid)
        gaps = (gap.get("result") or {}).get("gaps") or []
        renewals = _renewal_items(60)
        my_renewals = [r for r in renewals if r["customer_id"] == cid]
        if my_renewals:
            r0 = my_renewals[0]
            tpl = WAKE_TEMPLATES["renewal"].format(name=it["customer_name"],
                                                   company=r0["company"], product=r0["product_name"])
            angle = "renewal"
        elif gaps:
            tpl = WAKE_TEMPLATES["gap"].format(name=it["customer_name"], gap="、".join(gaps[:2]))
            angle = "gap"
        else:
            tpl = WAKE_TEMPLATES["service"].format(name=it["customer_name"])
            angle = "service"
        suggestions.append({
            "customer_id": cid,
            "customer_name": it["customer_name"],
            "phone": it.get("phone"),
            "days_since_followup": it["days_since_followup"],
            "gaps": gaps,
            "angle": angle,
            "script": tpl,
        })
    print_json({"ok": True, "days": int(args.days), "items": suggestions, "count": len(suggestions)})
    return 0


# ---------- special-date ----------

def cmd_special_date_query(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    month = int(args.month) if args.month else datetime.now().month
    out: list[dict] = []
    if args.type == "birthday":
        cust = orchestrator.customer_query()
        for c in cust.get("customers") or []:
            bday = c.get("birthday")
            if not bday:
                continue
            try:
                m = int(str(bday)[5:7])
            except (ValueError, TypeError):
                continue
            if m == month:
                out.append({
                    "customer_id": c["id"],
                    "customer_name": c["name"],
                    "phone": c.get("phone"),
                    "birthday": bday,
                })
    elif args.type == "anniversary":
        pol = orchestrator.policy_query()
        for p in pol.get("policies") or []:
            eff = p.get("effective_date")
            if not eff:
                continue
            try:
                m = int(str(eff)[5:7])
            except (ValueError, TypeError):
                continue
            if m == month:
                cust_d = orchestrator.customer_query(customer_id=p.get("customer_id"))
                cust = (cust_d.get("customers") or [{}])[0]
                eff_dt = parse_dt(eff)
                years = (datetime.now().year - eff_dt.year) if eff_dt else None
                out.append({
                    "policy_id": p["id"],
                    "customer_id": p.get("customer_id"),
                    "customer_name": cust.get("name"),
                    "company": p.get("company"),
                    "product_name": p.get("product_name"),
                    "effective_date": eff,
                    "years": years,
                })
    else:
        print_json({"ok": False, "error": "bad_type", "hint": "type 必须是 birthday 或 anniversary"})
        return 1
    print_json({"ok": True, "type": args.type, "month": month, "items": out, "count": len(out)})
    return 0


# ---------- daily-summary / dashboard ----------

def _summarize() -> dict:
    renewals_30 = _renewal_items(30)
    followup_30 = _followup_lapsed(30)
    dormant_60 = _followup_lapsed(60)
    bday = orchestrator.customer_query()
    month = datetime.now().month
    bday_count = 0
    for c in bday.get("customers") or []:
        try:
            if c.get("birthday") and int(str(c["birthday"])[5:7]) == month:
                bday_count += 1
        except (ValueError, TypeError):
            pass
    pol = orchestrator.policy_query()
    anniv_count = 0
    for p in pol.get("policies") or []:
        try:
            if p.get("effective_date") and int(str(p["effective_date"])[5:7]) == month:
                anniv_count += 1
        except (ValueError, TypeError):
            pass
    return {
        "renewals_30d": renewals_30,
        "followup_30d": followup_30,
        "dormant_60d": dormant_60,
        "birthday_this_month": bday_count,
        "anniversary_this_month": anniv_count,
    }


def cmd_daily_summary(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    s = _summarize()
    today_str = datetime.now().strftime("%Y-%m-%d")
    md_lines = [f"⏰ **今日提醒（{today_str}）**", ""]
    md_lines.append(f"📋 续保提醒：{len(s['renewals_30d'])} 份保单 30 天内到期/缴费")
    for it in s["renewals_30d"][:5]:
        md_lines.append(f"  - {it['customer_name']} - {it['product_name']} - {it['target_date']}")
    md_lines.append(f"📞 跟进提醒：{len(s['followup_30d'])} 位客户超 30 天未联系")
    md_lines.append(f"💤 沉睡唤醒：{len(s['dormant_60d'])} 位客户超 60 天未联系")
    md_lines.append(f"🎂 客户生日：{s['birthday_this_month']} 位")
    md_lines.append(f"🎊 保单周年日：{s['anniversary_this_month']} 位")
    print_json({
        "ok": True,
        "summary": {
            "renewal_30d": len(s["renewals_30d"]),
            "followup_30d_lapsed": len(s["followup_30d"]),
            "dormant_60d": len(s["dormant_60d"]),
            "birthday_this_month": s["birthday_this_month"],
            "anniversary_this_month": s["anniversary_this_month"],
        },
        "details": s,
        "markdown": "\n".join(md_lines),
    })
    return 0


def cmd_dashboard(conn: sqlite3.Connection, args: argparse.Namespace) -> int:
    s = _summarize()
    print_json({
        "ok": True,
        "skill": "insurance-schedule-renewal",
        "renewal_30d": len(s["renewals_30d"]),
        "followup_30d_lapsed": len(s["followup_30d"]),
        "dormant_60d": len(s["dormant_60d"]),
        "birthday_this_month": s["birthday_this_month"],
        "anniversary_this_month": s["anniversary_this_month"],
    })
    return 0


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

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

    a = sub.add_parser("renewal-query")
    a.add_argument("--days", required=True)
    a.set_defaults(func=cmd_renewal_query)

    a = sub.add_parser("renewal-remind")
    a.add_argument("--days", required=True)
    a.add_argument("--mark-sent", action="store_true")
    a.set_defaults(func=cmd_renewal_remind)

    a = sub.add_parser("followup-check")
    a.add_argument("--days", required=True)
    a.set_defaults(func=cmd_followup_check)

    a = sub.add_parser("followup-remind")
    a.add_argument("--days", required=True)
    a.add_argument("--mark-sent", action="store_true")
    a.set_defaults(func=cmd_followup_remind)

    a = sub.add_parser("dormant-query")
    a.add_argument("--days", default="60")
    a.set_defaults(func=cmd_dormant_query)

    a = sub.add_parser("dormant-wake")
    a.add_argument("--days", default="60")
    a.set_defaults(func=cmd_dormant_wake)

    a = sub.add_parser("special-date-query")
    a.add_argument("--type", required=True, choices=["birthday", "anniversary"])
    a.add_argument("--month")
    a.set_defaults(func=cmd_special_date_query)

    a = sub.add_parser("daily-summary")
    a.set_defaults(func=cmd_daily_summary)

    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())
