from __future__ import annotations

import argparse
import json
import sqlite3
import sys
import time
from calendar import monthrange
from datetime import datetime
from typing import Any, Callable, Dict, List, Optional, Tuple, Union

from store_db.db import connect
from store_db.field_defs import field_type_error_detail, validate_field_type
from store_db.ids import next_id
from store_db.operation_log import commit_with_log, log_read
from store_db.response import emit_error, emit_need_confirm, emit_ok

SKILL = "store-staff"


def _delete_blocked(conn: sqlite3.Connection, staff_id: str) -> Optional[str]:
    appt = conn.execute(
        "SELECT id FROM store_appointments WHERE technician_id=? AND status='scheduled' LIMIT 1",
        (staff_id,),
    ).fetchone()
    if appt:
        return "scheduled appointment exists"
    return None


def _parse_fields(row: Dict[str, Any]) -> Dict[str, Any]:
    row["fields"] = json.loads(row.pop("fields_json") or "{}")
    return row


def cmd_staff_add(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    sid = args.staff_id or next_id(conn, "S", "staff_staff")
    fields = args.fields_json or "{}"
    conn.execute(
        """INSERT INTO staff_staff (id, name, phone, join_date, base_salary, fields_json, note)
           VALUES (?,?,?,?,?,?,?)""",
        (
            sid,
            args.name,
            args.phone or "",
            args.join_date or "",
            float(args.base_salary or 0),
            fields,
            args.note or "",
        ),
    )
    commit_with_log(
        conn, args, skill=SKILL, command="staff-add", op="create", table_name="staff_staff",
        entity_id=sid, duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"staff_id": sid})
    return 0


def cmd_staff_query(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    status_clause = "" if args.include_deleted else " AND status='active'"
    limit = args.limit or 500
    rows: List[Dict[str, Any]] = []
    if args.staff_id:
        row = conn.execute(
            f"SELECT * FROM staff_staff WHERE id=?{status_clause}", (args.staff_id,)
        ).fetchone()
        if row:
            rows = [_parse_fields(dict(row))]
    elif args.name:
        rows = [
            _parse_fields(dict(r))
            for r in conn.execute(
                f"SELECT * FROM staff_staff WHERE name LIKE ?{status_clause} LIMIT ?",
                (f"%{args.name}%", limit),
            )
        ]
    elif args.skill:
        needle = args.skill
        out: List[Dict[str, Any]] = []
        for r in conn.execute(f"SELECT * FROM staff_staff WHERE 1=1{status_clause} LIMIT ?", (limit,)):
            d = _parse_fields(dict(r))
            blob = json.dumps(d.get("fields") or {}, ensure_ascii=False)
            if needle in blob or needle in (d.get("name") or ""):
                out.append(d)
        rows = out
    else:
        rows = [
            _parse_fields(dict(r))
            for r in conn.execute(
                f"SELECT * FROM staff_staff WHERE 1=1{status_clause} ORDER BY id LIMIT ?", (limit,)
            )
        ]
    log_read(
        conn, args, skill=SKILL, command="staff-query", table_name="staff_staff",
        entity_id=args.staff_id, duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"staff": rows, "count": len(rows)})
    return 0


def cmd_staff_update(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    payload = json.loads(args.set_json)
    row = conn.execute(
        "SELECT * FROM staff_staff WHERE id=? AND status='active'", (args.staff_id,)
    ).fetchone()
    if not row:
        emit_error("not_found", {"staff_id": args.staff_id})
    d = dict(row)
    fields = json.loads(d["fields_json"] or "{}")
    if "phone" in payload:
        conn.execute(
            "UPDATE staff_staff SET phone=?, updated_at=datetime('now','localtime') WHERE id=?",
            (payload["phone"], args.staff_id),
        )
    if "name" in payload:
        conn.execute(
            "UPDATE staff_staff SET name=?, updated_at=datetime('now','localtime') WHERE id=?",
            (payload["name"], args.staff_id),
        )
    if "join_date" in payload:
        conn.execute(
            "UPDATE staff_staff SET join_date=?, updated_at=datetime('now','localtime') WHERE id=?",
            (payload["join_date"], args.staff_id),
        )
    if "base_salary" in payload:
        conn.execute(
            "UPDATE staff_staff SET base_salary=?, updated_at=datetime('now','localtime') WHERE id=?",
            (float(payload["base_salary"]), args.staff_id),
        )
    if "note" in payload:
        conn.execute(
            "UPDATE staff_staff SET note=?, updated_at=datetime('now','localtime') WHERE id=?",
            (payload["note"], args.staff_id),
        )
    if "fields" in payload:
        fields.update(payload["fields"])
        conn.execute(
            "UPDATE staff_staff SET fields_json=?, updated_at=datetime('now','localtime') WHERE id=?",
            (json.dumps(fields, ensure_ascii=False), args.staff_id),
        )
    commit_with_log(
        conn, args, skill=SKILL, command="staff-update", op="update", table_name="staff_staff",
        entity_id=args.staff_id, duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"staff_id": args.staff_id})
    return 0


def cmd_staff_delete(args: argparse.Namespace) -> int:
    if not args.yes:
        emit_need_confirm({"action": "staff-delete", "staff_id": args.staff_id})
        return 0
    t0 = time.perf_counter()
    conn = connect()
    row = conn.execute(
        "SELECT id FROM staff_staff WHERE id=? AND status='active'", (args.staff_id,)
    ).fetchone()
    if not row:
        emit_error("not_found", {"staff_id": args.staff_id})
    blocked = _delete_blocked(conn, args.staff_id)
    if blocked:
        emit_error("delete_blocked", {"reason": blocked})
    conn.execute(
        "UPDATE staff_staff SET status='deleted', updated_at=datetime('now','localtime') WHERE id=?",
        (args.staff_id,),
    )
    commit_with_log(
        conn, args, skill=SKILL, command="staff-delete", op="update", table_name="staff_staff",
        entity_id=args.staff_id, duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"staff_id": args.staff_id, "status": "deleted"})
    return 0


def cmd_staff_schedule_set(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    staff = conn.execute(
        "SELECT id FROM staff_staff WHERE id=? AND status='active'", (args.staff_id,)
    ).fetchone()
    if not staff:
        emit_error("not_found", {"staff_id": args.staff_id})
    slots_json = None
    if args.slots_json:
        slots_json = json.dumps(json.loads(args.slots_json), ensure_ascii=False)
    status = args.status or "work"
    conn.execute(
        """INSERT INTO staff_schedules (staff_id, date, status, slots_json)
           VALUES (?,?,?,?)
           ON CONFLICT(staff_id, date) DO UPDATE SET
           status=excluded.status,
           slots_json=excluded.slots_json""",
        (args.staff_id, args.date, status, slots_json),
    )
    commit_with_log(
        conn, args, skill=SKILL, command="staff-schedule-set", op="update", table_name="staff_schedules",
        entity_id=args.staff_id, duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"staff_id": args.staff_id, "date": args.date, "status": status})
    return 0


def cmd_staff_schedule_query(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    if args.query_all:
        rows = [
            dict(r)
            for r in conn.execute(
                "SELECT staff_id, date, status, slots_json FROM staff_schedules WHERE date=? ORDER BY staff_id",
                (args.date,),
            )
        ]
        for r in rows:
            r["slots"] = json.loads(r.pop("slots_json") or "null")
        log_read(
            conn, args, skill=SKILL, command="staff-schedule-query", table_name="staff_schedules",
            duration_ms=int((time.perf_counter() - t0) * 1000),
        )
        emit_ok({"schedules": rows, "date": args.date})
        return 0
    if not args.staff_id:
        emit_error("missing_staff_id", {"hint": "provide --staff-id or --query-all"})
    row = conn.execute(
        "SELECT * FROM staff_schedules WHERE staff_id=? AND date=?",
        (args.staff_id, args.date),
    ).fetchone()
    if not row:
        log_read(
            conn, args, skill=SKILL, command="staff-schedule-query", table_name="staff_schedules",
            entity_id=args.staff_id, duration_ms=int((time.perf_counter() - t0) * 1000),
        )
        emit_ok(
            {
                "staff_id": args.staff_id,
                "date": args.date,
                "status": "work",
                "slots": None,
                "note": "default_full_day",
            }
        )
        return 0
    d = dict(row)
    d["slots"] = json.loads(d.pop("slots_json") or "null")
    log_read(
        conn, args, skill=SKILL, command="staff-schedule-query", table_name="staff_schedules",
        entity_id=args.staff_id, duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok(d)
    return 0


def _ratio_for_service(conn: sqlite3.Connection, staff_id: str, service_name: str) -> float:
    row = conn.execute(
        """SELECT ratio FROM staff_commission_rules
           WHERE staff_id=? AND kind='service' AND target_name=?""",
        (staff_id, service_name),
    ).fetchone()
    return float(row["ratio"]) if row else 0.0


def _ratio_for_product(conn: sqlite3.Connection, staff_id: str) -> float:
    row = conn.execute(
        """SELECT ratio FROM staff_commission_rules
           WHERE staff_id=? AND kind='product' AND target_name IS NULL""",
        (staff_id,),
    ).fetchone()
    return float(row["ratio"]) if row else 0.0


def cmd_commission_rule_set(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    if args.query:
        rows = [
            dict(r)
            for r in conn.execute(
                "SELECT kind, target_name, ratio FROM staff_commission_rules WHERE staff_id=? ORDER BY kind, target_name",
                (args.staff_id,),
            )
        ]
        log_read(
            conn, args, skill=SKILL, command="commission-rule-set", table_name="staff_commission_rules",
            entity_id=args.staff_id, duration_ms=int((time.perf_counter() - t0) * 1000),
        )
        emit_ok({"staff_id": args.staff_id, "rules": rows})
        return 0
    if not args.yes:
        emit_need_confirm(
            {
                "action": "commission-rule-set",
                "staff_id": args.staff_id,
                "product_commission": args.product_commission,
                "service_commission": args.service_commission,
            }
        )
        return 0
    staff = conn.execute(
        "SELECT id FROM staff_staff WHERE id=? AND status='active'", (args.staff_id,)
    ).fetchone()
    if not staff:
        emit_error("not_found", {"staff_id": args.staff_id})
    if args.product_commission is not None:
        conn.execute(
            "DELETE FROM staff_commission_rules WHERE staff_id=? AND kind='product'", (args.staff_id,)
        )
        conn.execute(
            "INSERT INTO staff_commission_rules (staff_id, kind, target_name, ratio) VALUES (?,?,NULL,?)",
            (args.staff_id, "product", float(args.product_commission)),
        )
    if args.service_commission:
        payload = json.loads(args.service_commission)
        conn.execute(
            "DELETE FROM staff_commission_rules WHERE staff_id=? AND kind='service'", (args.staff_id,)
        )
        for svc, ratio in payload.items():
            conn.execute(
                "INSERT INTO staff_commission_rules (staff_id, kind, target_name, ratio) VALUES (?,?,?,?)",
                (args.staff_id, "service", str(svc), float(ratio)),
            )
    commit_with_log(
        conn, args, skill=SKILL, command="commission-rule-set", op="update", table_name="staff_commission_rules",
        entity_id=args.staff_id, duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"staff_id": args.staff_id})
    return 0


def cmd_staff_salary_calc(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    month = args.month or datetime.now().strftime("%Y-%m")
    staff = conn.execute(
        "SELECT id, name, base_salary FROM staff_staff WHERE id=? AND status='active'", (args.staff_id,)
    ).fetchone()
    if not staff:
        emit_error("not_found", {"staff_id": args.staff_id})
    y1, m1 = map(int, month.split("-"))
    start = f"{month}-01"
    last = monthrange(y1, m1)[1]
    end = f"{month}-{last:02d}"
    perfs = conn.execute(
        """SELECT kind, target_name, amount FROM staff_performance
           WHERE staff_id=? AND ymd >= ? AND ymd <= ?""",
        (args.staff_id, start, end),
    ).fetchall()
    base = float(staff["base_salary"])
    commission_detail: List[Dict[str, Any]] = []
    commission_total = 0.0
    pr_ratio = _ratio_for_product(conn, args.staff_id)
    for p in perfs:
        kind = p["kind"]
        amt = float(p["amount"])
        if kind == "service":
            r = _ratio_for_service(conn, args.staff_id, p["target_name"] or "")
            c = amt * r
            commission_detail.append(
                {"kind": "service", "target": p["target_name"], "amount": amt, "ratio": r, "commission": c}
            )
            commission_total += c
        elif kind == "product":
            c = amt * pr_ratio
            commission_detail.append(
                {"kind": "product", "target": p["target_name"], "amount": amt, "ratio": pr_ratio, "commission": c}
            )
            commission_total += c
    total = base + commission_total
    log_read(
        conn, args, skill=SKILL, command="staff-salary-calc", table_name="staff_performance",
        entity_id=args.staff_id, duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok(
        {
            "staff_id": staff["id"],
            "name": staff["name"],
            "month": month,
            "base_salary": base,
            "commission_total": round(commission_total, 2),
            "total_salary": round(total, 2),
            "detail": commission_detail,
        }
    )
    return 0


def cmd_staff_ranking(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    month = args.month or datetime.now().strftime("%Y-%m")
    y1, m1 = map(int, month.split("-"))
    start = f"{month}-01"
    last = monthrange(y1, m1)[1]
    end = f"{month}-{last:02d}"
    rows = conn.execute(
        """
        SELECT s.id, s.name,
               COALESCE(SUM(p.amount), 0) AS revenue
        FROM staff_staff s
        LEFT JOIN staff_performance p ON p.staff_id = s.id AND p.ymd >= ? AND p.ymd <= ?
        WHERE s.status = 'active'
        GROUP BY s.id
        ORDER BY revenue DESC
        """,
        (start, end),
    ).fetchall()
    ranking = [
        {"rank": i + 1, "staff_id": r["id"], "name": r["name"], "revenue": float(r["revenue"])}
        for i, r in enumerate(rows)
    ]
    log_read(
        conn, args, skill=SKILL, command="staff-ranking", table_name="staff_performance",
        duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"month": month, "ranking": ranking})
    return 0


ENTITY = "staff"


def cmd_staff_field_def_add(args: argparse.Namespace) -> int:
    if not validate_field_type(args.field_type):
        emit_error("invalid_field_type", field_type_error_detail())
    t0 = time.perf_counter()
    conn = connect()
    conn.execute(
        """INSERT OR REPLACE INTO store_custom_field_defs (entity, field_key, label, field_type, options_json)
           VALUES (?,?,?,?,?)""",
        (ENTITY, args.field_key, args.label, args.field_type, args.options_json or "[]"),
    )
    commit_with_log(
        conn, args, skill=SKILL, command="staff-field-def-add", op="create",
        table_name="store_custom_field_defs", entity_id=f"{ENTITY}:{args.field_key}",
        duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"entity": ENTITY, "field_key": args.field_key})
    return 0


def cmd_staff_field_def_update(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    payload = json.loads(args.set)
    conn = connect()
    row = conn.execute(
        "SELECT * FROM store_custom_field_defs WHERE entity=? AND field_key=?",
        (ENTITY, args.field_key),
    ).fetchone()
    if not row:
        emit_error("not_found", {"entity": ENTITY, "field_key": args.field_key})
    sets: List[str] = []
    vals: List[Any] = []
    for key in ("label", "field_type", "options_json", "sort_order"):
        if key in payload:
            val = payload[key]
            if key == "field_type" and not validate_field_type(str(val)):
                emit_error("invalid_field_type", field_type_error_detail())
            if key == "options_json" and not isinstance(val, str):
                val = json.dumps(val, ensure_ascii=False)
            sets.append(f"{key}=?")
            vals.append(val)
    if sets:
        sets.append("updated_at=datetime('now','localtime')")
        vals.extend([ENTITY, args.field_key])
        conn.execute(
            f"UPDATE store_custom_field_defs SET {', '.join(sets)} WHERE entity=? AND field_key=?",
            vals,
        )
        commit_with_log(
            conn, args, skill=SKILL, command="staff-field-def-update", op="update",
            table_name="store_custom_field_defs", entity_id=f"{ENTITY}:{args.field_key}",
            duration_ms=int((time.perf_counter() - t0) * 1000),
        )
    emit_ok({"entity": ENTITY, "field_key": args.field_key})
    return 0


def cmd_staff_field_def_delete(args: argparse.Namespace) -> int:
    if not args.yes:
        emit_need_confirm({"action": "staff-field-def-delete", "field_key": args.field_key})
        return 0
    t0 = time.perf_counter()
    conn = connect()
    conn.execute(
        "DELETE FROM store_custom_field_defs WHERE entity=? AND field_key=?",
        (ENTITY, args.field_key),
    )
    commit_with_log(
        conn, args, skill=SKILL, command="staff-field-def-delete", op="delete",
        table_name="store_custom_field_defs", entity_id=f"{ENTITY}:{args.field_key}",
        duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"entity": ENTITY, "field_key": args.field_key, "deleted": True})
    return 0


def cmd_staff_field_def_list(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    rows = [
        dict(r)
        for r in conn.execute(
            "SELECT * FROM store_custom_field_defs WHERE entity=? ORDER BY sort_order, field_key",
            (ENTITY,),
        )
    ]
    log_read(
        conn, args, skill=SKILL, command="staff-field-def-list", table_name="store_custom_field_defs",
        duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"field_defs": rows})
    return 0


def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(description="Store staff CLI")
    sub = p.add_subparsers(dest="cmd", required=True)

    s = sub.add_parser("staff-add")
    s.add_argument("--name", required=True)
    s.add_argument("--phone")
    s.add_argument("--join-date")
    s.add_argument("--base-salary", type=float, default=0)
    s.add_argument("--note")
    s.add_argument("--fields-json", default="{}")
    s.add_argument("--staff-id")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_staff_add)

    s = sub.add_parser("staff-query")
    s.add_argument("--staff-id")
    s.add_argument("--name")
    s.add_argument("--skill", help="匹配 fields / 姓名中的关键词")
    s.add_argument("--include-deleted", action="store_true")
    s.add_argument("--limit", type=int)
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_staff_query)

    s = sub.add_parser("staff-update")
    s.add_argument("--staff-id", required=True)
    s.add_argument("--set-json", required=True, help="JSON: phone/name/join_date/base_salary/note/fields")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_staff_update)

    s = sub.add_parser("staff-delete")
    s.add_argument("--staff-id", required=True)
    s.add_argument("--yes", action="store_true")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_staff_delete)

    sched = sub.add_parser("staff-schedule")
    sched_sub = sched.add_subparsers(dest="schedule_cmd", required=True)

    s_set = sched_sub.add_parser("set")
    s_set.add_argument("--staff-id", required=True)
    s_set.add_argument("--date", required=True, help="YYYY-MM-DD")
    s_set.add_argument("--status", help="off / work")
    s_set.add_argument("--slots-json", help='JSON 数组，如 ["09:00-12:00","14:00-18:00"]')
    s_set.add_argument("--session-id")
    s_set.set_defaults(func=cmd_staff_schedule_set)

    s_q = sched_sub.add_parser("query")
    s_q.add_argument("--date", required=True, help="YYYY-MM-DD")
    s_q.add_argument("--staff-id")
    s_q.add_argument("--query-all", action="store_true", help="查当日全员排班")
    s_q.add_argument("--session-id")
    s_q.set_defaults(func=cmd_staff_schedule_query)

    cr = sub.add_parser("commission-rule")
    cr_sub = cr.add_subparsers(dest="commission_cmd", required=True)

    c_set = cr_sub.add_parser("set")
    c_set.add_argument("--staff-id", required=True)
    c_set.add_argument("--service-commission", help='JSON，项目名→比例，如 {"基础服务":0.15}')
    c_set.add_argument("--product-commission", type=float)
    c_set.add_argument("--query", action="store_true", help="查询当前规则（无需 --yes）")
    c_set.add_argument("--yes", action="store_true")
    c_set.add_argument("--session-id")
    c_set.set_defaults(func=cmd_commission_rule_set)

    s = sub.add_parser("staff-salary-calc")
    s.add_argument("--staff-id", required=True)
    s.add_argument("--month", help="YYYY-MM")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_staff_salary_calc)

    s = sub.add_parser("staff-ranking")
    s.add_argument("--month", help="YYYY-MM")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_staff_ranking)

    s = sub.add_parser("staff-field-def-add")
    s.add_argument("--field-key", required=True)
    s.add_argument("--label", required=True)
    s.add_argument("--field-type", default="text")
    s.add_argument("--options-json", default="[]")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_staff_field_def_add)

    s = sub.add_parser("staff-field-def-update")
    s.add_argument("--field-key", required=True)
    s.add_argument("--set", required=True, help="JSON: label/field_type/options_json/sort_order")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_staff_field_def_update)

    s = sub.add_parser("staff-field-def-delete")
    s.add_argument("--field-key", required=True)
    s.add_argument("--yes", action="store_true")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_staff_field_def_delete)

    s = sub.add_parser("staff-field-def-list")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_staff_field_def_list)

    return p


def main() -> int:
    args = build_parser().parse_args()
    return int(args.func(args))


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