from __future__ import annotations

import argparse
import json
import sys
import time
from typing import Any, Dict, List, Optional

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

from store_appointment_lib.helpers import (  # noqa: E402
    SettlementError,
    appt_interval,
    query_service_price_and_duration,
    staff_list_for_skill,
    staff_schedule_query,
    suggest_available_slots,
    validate_appt_create,
    within_slots,
)
from store_appointment_lib.settlement import PAY_MODES, complete_appointment  # noqa: E402

SKILL = "store-appointment"


def cmd_appt_create(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    service = args.service
    price, dur = query_service_price_and_duration(conn, service)
    duration_min = int(args.duration) if args.duration else dur
    ok, err = validate_appt_create(
        conn,
        technician_id=args.technician_id,
        datetime_str=args.datetime,
        duration_min=duration_min,
    )
    if not ok:
        code = err.split(":", 1)[0] if err.startswith("schedule_conflict") else err
        detail = {}
        if ":" in err:
            detail = {"conflict_id": err.split(":", 1)[1]}
        emit_error(code, detail if detail else None)
    aid = args.appt_id or next_id(conn, "A", "store_appointments")
    conn.execute(
        """INSERT INTO store_appointments
           (id, member_id, service_name, technician_id, start_dt, duration_min, status, remark)
           VALUES (?,?,?,?,?,?, 'scheduled', ?)""",
        (
            aid,
            args.member_id,
            service,
            args.technician_id,
            args.datetime,
            duration_min,
            args.remark or "",
        ),
    )
    commit_with_log(
        conn, args, skill=SKILL, command="appt-create", op="create", table_name="store_appointments",
        entity_id=aid, duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"appt_id": aid, "duration_min": duration_min, "price": price})
    return 0


def cmd_appt_query(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    if args.appt_id:
        row = conn.execute("SELECT * FROM store_appointments WHERE id=?", (args.appt_id,)).fetchone()
        log_read(
            conn, args, skill=SKILL, command="appt-query", table_name="store_appointments",
            entity_id=args.appt_id, duration_ms=int((time.perf_counter() - t0) * 1000),
        )
        emit_ok({"appointments": [dict(row)] if row else [], "count": 1 if row else 0})
        return 0

    if args.date and (args.date_from or args.date_to):
        emit_error("invalid_query", {"hint": "use --date or --date-from/--date-to, not both"})

    clauses: List[str] = ["1=1"]
    params: List[Any] = []
    if args.member_id:
        clauses.append("member_id=?")
        params.append(args.member_id)
    if args.technician_id:
        clauses.append("technician_id=?")
        params.append(args.technician_id)
    if args.date:
        clauses.append("substr(start_dt,1,10)=?")
        params.append(args.date)
    elif args.date_from:
        clauses.append("substr(start_dt,1,10)>=?")
        params.append(args.date_from)
    if args.date_to:
        clauses.append("substr(start_dt,1,10)<=?")
        params.append(args.date_to)
    if args.status:
        clauses.append("status=?")
        params.append(args.status)

    q = f"SELECT * FROM store_appointments WHERE {' AND '.join(clauses)} ORDER BY start_dt"
    if args.limit:
        q += " LIMIT ?"
        params.append(args.limit)
    elif not args.date and not args.date_from and not args.member_id:
        q += " LIMIT ?"
        params.append(200)
    rows = [dict(r) for r in conn.execute(q, params)]
    log_read(
        conn, args, skill=SKILL, command="appt-query", table_name="store_appointments",
        duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"appointments": rows, "count": len(rows)})
    return 0


def cmd_appt_update(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    row = conn.execute("SELECT * FROM store_appointments WHERE id=?", (args.appt_id,)).fetchone()
    if not row or row["status"] != "scheduled":
        emit_error("invalid_appt", {"appt_id": args.appt_id})

    new_dt = args.datetime or row["start_dt"]
    new_tech = args.technician_id or row["technician_id"]
    new_service = args.service or row["service_name"]

    if not args.datetime and not args.technician_id and not args.service:
        emit_error("invalid_query", {"hint": "provide at least one of --datetime, --technician-id, --service"})

    if args.service and args.service != row["service_name"]:
        svc = conn.execute(
            "SELECT name FROM catalog_services WHERE name=? AND status='active'",
            (args.service,),
        ).fetchone()
        if not svc:
            emit_error("service_not_found", {"name": args.service})

    _, dur = query_service_price_and_duration(conn, new_service)
    duration_min = int(args.duration) if args.duration else int(row["duration_min"] or dur)

    ok, err = validate_appt_create(
        conn,
        technician_id=new_tech,
        datetime_str=new_dt,
        duration_min=duration_min,
        exclude_appt_id=args.appt_id,
    )
    if not ok:
        code = err.split(":", 1)[0] if err.startswith("schedule_conflict") else err
        detail = {}
        if ":" in err:
            detail = {"conflict_id": err.split(":", 1)[1]}
        emit_error(code, detail if detail else None)

    conn.execute(
        """UPDATE store_appointments SET start_dt=?, technician_id=?, service_name=?,
           duration_min=?, updated_at=datetime('now','localtime') WHERE id=?""",
        (new_dt, new_tech, new_service, duration_min, args.appt_id),
    )
    commit_with_log(
        conn, args, skill=SKILL, command="appt-update", op="update", table_name="store_appointments",
        entity_id=args.appt_id, duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"appt_id": args.appt_id})
    return 0


def cmd_appt_cancel(args: argparse.Namespace) -> int:
    if not args.yes:
        emit_need_confirm({"action": "appt-cancel", "appt_id": args.appt_id})
        return 0
    t0 = time.perf_counter()
    conn = connect()
    row = conn.execute("SELECT status FROM store_appointments WHERE id=?", (args.appt_id,)).fetchone()
    if not row:
        emit_error("not_found", {"appt_id": args.appt_id})
    conn.execute(
        """UPDATE store_appointments SET status='cancelled', updated_at=datetime('now','localtime')
           WHERE id=?""",
        (args.appt_id,),
    )
    commit_with_log(
        conn, args, skill=SKILL, command="appt-cancel", op="update", table_name="store_appointments",
        entity_id=args.appt_id, duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"appt_id": args.appt_id, "status": "cancelled"})
    return 0


def cmd_appt_complete(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    row = conn.execute("SELECT * FROM store_appointments WHERE id=?", (args.appt_id,)).fetchone()
    if not row:
        emit_error("not_found", {"appt_id": args.appt_id})
    price, _ = query_service_price_and_duration(conn, row["service_name"])
    if not args.yes:
        emit_need_confirm(
            {
                "action": "appt-complete",
                "appt_id": args.appt_id,
                "member_id": row["member_id"],
                "service": row["service_name"],
                "pay": args.pay,
                "total_amount": price,
            }
        )
        return 0
    audit = {
        "session_id": getattr(args, "session_id", None),
        "skill": SKILL,
        "command": "appt-complete",
        "request_json": request_dict(args),
        "duration_ms": int((time.perf_counter() - t0) * 1000),
    }
    try:
        summary = complete_appointment(
            conn,
            appt_id=args.appt_id,
            pay=args.pay,
            lines_json=args.lines_json,
            staff_lines_json=args.staff_lines_json,
            card_name=args.card_name,
            cash_amount=args.cash_amount,
            audit=audit,
        )
    except SettlementError as e:
        emit_error(e.code, e.detail if e.detail else None)
    emit_ok(summary)
    return 0


def cmd_tech_recommend(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    _, dur = query_service_price_and_duration(conn, args.service)
    candidates = staff_list_for_skill(conn, args.service)
    date_part = args.datetime[:10]
    good: List[Dict[str, Any]] = []
    for st in candidates:
        sid = st["id"]
        sch = staff_schedule_query(conn, sid, date_part)
        if sch.get("status") == "off":
            continue
        if not within_slots(args.datetime, sch.get("slots")):
            continue
        clash = appt_interval(conn, sid, args.datetime, dur)
        if clash:
            continue
        good.append({"staff_id": sid, "name": st.get("name"), "fields": st.get("fields")})
    log_read(
        conn, args, skill=SKILL, command="tech-recommend", table_name="staff_staff",
        duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"technicians": good, "count": len(good)})
    return 0


def cmd_tech_auto_assign(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    _, dur = query_service_price_and_duration(conn, args.service)
    candidates = staff_list_for_skill(conn, args.service)
    date_part = args.datetime[:10]
    for st in candidates:
        sid = st["id"]
        sch = staff_schedule_query(conn, sid, date_part)
        if sch.get("status") == "off":
            continue
        if not within_slots(args.datetime, sch.get("slots")):
            continue
        clash = appt_interval(conn, sid, args.datetime, dur)
        if clash:
            continue
        log_read(
            conn, args, skill=SKILL, command="tech-auto-assign", table_name="staff_staff",
            entity_id=sid, duration_ms=int((time.perf_counter() - t0) * 1000),
        )
        emit_ok({"assigned": {"staff_id": sid, "name": st.get("name")}})
        return 0
    emit_error("no_technician_available")
    return 0


def cmd_appt_suggest_slots(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    slots = suggest_available_slots(
        conn,
        service=args.service,
        date_yyyy_mm_dd=args.date,
        technician_id=args.technician_id,
        limit=int(args.limit or 5),
    )
    log_read(
        conn, args, skill=SKILL, command="appt-suggest-slots", table_name="store_appointments",
        duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"slots": slots, "count": len(slots)})
    return 0


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

    s = sub.add_parser("appt-create")
    s.add_argument("--member-id", required=True)
    s.add_argument("--service", "--project", dest="service", required=True)
    s.add_argument("--technician-id", required=True)
    s.add_argument("--datetime", required=True)
    s.add_argument("--duration", type=int)
    s.add_argument("--appt-id")
    s.add_argument("--remark", default="")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_appt_create)

    s = sub.add_parser("appt-query")
    s.add_argument("--appt-id")
    s.add_argument("--member-id")
    s.add_argument("--date")
    s.add_argument("--date-from")
    s.add_argument("--date-to")
    s.add_argument("--technician-id")
    s.add_argument("--status")
    s.add_argument("--limit", type=int)
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_appt_query)

    s = sub.add_parser("appt-update")
    s.add_argument("--appt-id", required=True)
    s.add_argument("--datetime")
    s.add_argument("--technician-id")
    s.add_argument("--service")
    s.add_argument("--duration", type=int)
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_appt_update)

    s = sub.add_parser("appt-cancel")
    s.add_argument("--appt-id", required=True)
    s.add_argument("--yes", action="store_true")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_appt_cancel)

    s = sub.add_parser("appt-complete")
    s.add_argument("--appt-id", required=True)
    s.add_argument("--pay", default="stored_value", choices=list(PAY_MODES))
    s.add_argument("--lines-json")
    s.add_argument("--staff-lines-json")
    s.add_argument("--card-name")
    s.add_argument("--cash-amount", type=float)
    s.add_argument("--yes", action="store_true")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_appt_complete)

    s = sub.add_parser("tech-recommend")
    s.add_argument("--service", "--project", dest="service", required=True)
    s.add_argument("--datetime", required=True)
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_tech_recommend)

    s = sub.add_parser("tech-auto-assign")
    s.add_argument("--service", "--project", dest="service", required=True)
    s.add_argument("--datetime", required=True)
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_tech_auto_assign)

    s = sub.add_parser("appt-suggest-slots")
    s.add_argument("--service", "--project", dest="service", required=True)
    s.add_argument("--date", required=True, help="YYYY-MM-DD")
    s.add_argument("--technician-id")
    s.add_argument("--limit", type=int, default=5)
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_appt_suggest_slots)

    return p


def main() -> int:
    args = build_parser().parse_args()
    try:
        return int(args.func(args))
    except SettlementError as e:
        emit_error(e.code, e.detail if e.detail else None)
        return 1


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