from __future__ import annotations

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

from store_db.db import connect
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-catalog"


def _active_cards_for_service(conn: sqlite3.Connection, service_name: str, service_id: int) -> int:
    n = conn.execute(
        """SELECT COUNT(*) FROM store_cards WHERE status='active'
           AND used_times < total_times
           AND (expire_date IS NULL OR expire_date = '' OR expire_date >= date('now'))
           AND (service_id=? OR service_name=?)""",
        (service_id, service_name),
    ).fetchone()[0]
    return int(n or 0)


def _upsert_inv_item(
    conn: sqlite3.Connection,
    name: str,
    unit: str,
    cost: float,
    sale: float,
    threshold: Optional[float],
) -> None:
    thr = 0.0 if threshold is None else float(threshold)
    existing = conn.execute("SELECT key FROM inv_items WHERE key=?", (name,)).fetchone()
    if existing:
        conn.execute(
            """UPDATE inv_items SET unit=?, cost=?, sale_price=?, low_stock_threshold=?,
               updated_at=datetime('now','localtime') WHERE key=?""",
            (unit, float(cost), float(sale), float(thr), name),
        )
    else:
        conn.execute(
            """INSERT INTO inv_items (key, stock_qty, unit, cost, sale_price, low_stock_threshold)
               VALUES (?,?,?,?,?,?)""",
            (name, 0.0, unit, float(cost), float(sale), float(thr)),
        )


def _service_id_by_name(conn: sqlite3.Connection, name: str) -> Optional[int]:
    row = conn.execute(
        "SELECT id FROM catalog_services WHERE name=? AND status='active'",
        (name,),
    ).fetchone()
    return int(row["id"]) if row else None


def _materials_by_service_id(conn: sqlite3.Connection, service_id: int) -> Dict[str, float]:
    mats = conn.execute(
        "SELECT product_name, qty FROM catalog_service_materials WHERE service_id=? ORDER BY product_name",
        (service_id,),
    ).fetchall()
    return {m["product_name"]: float(m["qty"]) for m in mats}


def cmd_service_add(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    materials: Dict[str, Any] = {}
    if args.materials:
        materials = json.loads(args.materials)
    conn = connect()
    if conn.execute(
        "SELECT 1 FROM catalog_services WHERE name=? AND status='active'", (args.name,)
    ).fetchone():
        emit_error("duplicate_name", {"name": args.name, "kind": "service"})
    cur = conn.cursor()
    cur.execute(
        "INSERT INTO catalog_services (name, price, duration_min) VALUES (?,?,?)",
        (args.name, float(args.price), int(args.duration)),
    )
    sid = int(cur.lastrowid)
    for pname, qty in materials.items():
        cur.execute(
            "INSERT INTO catalog_service_materials (service_id, product_name, qty) VALUES (?,?,?)",
            (sid, str(pname), float(qty)),
        )
    commit_with_log(
        conn, args, skill=SKILL, command="service-add", op="create", table_name="catalog_services",
        entity_id=str(sid), duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"id": sid, "name": args.name})
    return 0


def cmd_service_query(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    if args.name:
        row = conn.execute(
            "SELECT * FROM catalog_services WHERE name=? AND status='active'",
            (args.name,),
        ).fetchone()
        rows = [dict(row)] if row else []
    else:
        rows = [
            dict(r)
            for r in conn.execute(
                "SELECT * FROM catalog_services WHERE status='active' ORDER BY name"
            )
        ]
    for r in rows:
        r["materials"] = _materials_by_service_id(conn, int(r["id"]))
    log_read(
        conn, args, skill=SKILL, command="service-query", table_name="catalog_services",
        duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"services": rows, "count": len(rows)})
    return 0


def cmd_service_update(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    payload = json.loads(args.set)
    conn = connect()
    old_name = args.name
    sid = _service_id_by_name(conn, old_name)
    if sid is None:
        emit_error("service_not_found", {"name": old_name})
    new_name = old_name
    sets: List[str] = []
    vals: List[Any] = []
    if "price" in payload:
        sets.append("price=?")
        vals.append(float(payload["price"]))
    if "duration" in payload or "duration_min" in payload:
        sets.append("duration_min=?")
        vals.append(int(payload.get("duration_min", payload.get("duration"))))
    if "name" in payload:
        new_name = str(payload["name"])
        if new_name != old_name and _service_id_by_name(conn, new_name) is not None:
            emit_error("duplicate_name", {"name": new_name, "kind": "service"})
        sets.append("name=?")
        vals.append(new_name)
    if not sets:
        emit_ok({"id": sid, "name": new_name})
        return 0

    conn.execute("BEGIN IMMEDIATE")
    try:
        if new_name != old_name:
            conn.execute(
                """UPDATE store_appointments SET service_name=?, updated_at=datetime('now','localtime')
                   WHERE service_name=? AND status='scheduled'""",
                (new_name, old_name),
            )
            conn.execute(
                "UPDATE store_cards SET service_name=? WHERE service_name=? AND status='active'",
                (new_name, old_name),
            )
        sets.append("updated_at=datetime('now','localtime')")
        vals.append(sid)
        conn.execute(f"UPDATE catalog_services SET {', '.join(sets)} WHERE id=?", vals)
        commit_with_log(
            conn, args, skill=SKILL, command="service-update", op="update", table_name="catalog_services",
            entity_id=str(sid), duration_ms=int((time.perf_counter() - t0) * 1000),
        )
    except Exception:
        conn.rollback()
        raise
    emit_ok({"id": sid, "name": new_name})
    return 0


def cmd_service_delete(args: argparse.Namespace) -> int:
    conn = connect()
    sid = _service_id_by_name(conn, args.name)
    if sid is None:
        emit_error("service_not_found", {"name": args.name})
    cnt = _active_cards_for_service(conn, args.name, sid)
    if cnt > 0:
        emit_error("cards_reference_service", {"count": cnt, "name": args.name})
    if not args.yes:
        emit_need_confirm({"action": "service-delete", "name": args.name})
        return 0
    t0 = time.perf_counter()
    conn.execute(
        "UPDATE catalog_services SET status='deleted', updated_at=datetime('now','localtime') WHERE id=?",
        (sid,),
    )
    commit_with_log(
        conn, args, skill=SKILL, command="service-delete", op="update", table_name="catalog_services",
        entity_id=str(sid), after_json={"status": "deleted"},
        duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"name": args.name, "status": "deleted"})
    return 0


def cmd_product_add(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    if conn.execute(
        "SELECT 1 FROM catalog_products WHERE name=? AND status='active'", (args.name,)
    ).fetchone():
        emit_error("duplicate_name", {"name": args.name, "kind": "product"})
    unit = args.unit or "件"
    threshold = args.threshold
    conn.execute(
        """INSERT INTO catalog_products (name, cost, sale_price, unit, threshold)
           VALUES (?,?,?,?,?)""",
        (
            args.name,
            float(args.cost),
            float(args.price),
            unit,
            float(threshold) if threshold is not None else None,
        ),
    )
    pid = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
    _upsert_inv_item(conn, args.name, unit, float(args.cost), float(args.price), threshold)
    commit_with_log(
        conn, args, skill=SKILL, command="product-add", op="create", table_name="catalog_products",
        entity_id=str(pid), duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"id": int(pid), "name": args.name})
    return 0


def cmd_product_query(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    if args.name:
        row = conn.execute(
            "SELECT * FROM catalog_products WHERE name=? AND status='active'",
            (args.name,),
        ).fetchone()
        rows = [dict(row)] if row else []
    else:
        rows = [
            dict(r)
            for r in conn.execute(
                "SELECT * FROM catalog_products WHERE status='active' ORDER BY name"
            )
        ]
    log_read(
        conn, args, skill=SKILL, command="product-query", table_name="catalog_products",
        duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"products": rows, "count": len(rows)})
    return 0


def cmd_product_update(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    payload = json.loads(args.set)
    conn = connect()
    row = conn.execute(
        "SELECT * FROM catalog_products WHERE name=? AND status='active'",
        (args.name,),
    ).fetchone()
    if not row:
        emit_error("product_not_found", {"name": args.name})
    sets: List[str] = []
    vals: List[Any] = []
    old_name = args.name
    new_name = old_name
    if "name" in payload:
        new_name = str(payload["name"])
        sets.append("name=?")
        vals.append(new_name)
    if "cost" in payload:
        sets.append("cost=?")
        vals.append(float(payload["cost"]))
    if "price" in payload or "sale_price" in payload:
        sets.append("sale_price=?")
        vals.append(float(payload.get("sale_price", payload.get("price"))))
    if "unit" in payload:
        sets.append("unit=?")
        vals.append(str(payload["unit"]))
    if "threshold" in payload:
        sets.append("threshold=?")
        vals.append(float(payload["threshold"]))
    if not sets:
        emit_ok({"id": int(row["id"]), "name": new_name})
        return 0

    inv_cost = float(payload["cost"]) if "cost" in payload else float(row["cost"])
    inv_price = float(payload.get("sale_price", payload.get("price", row["sale_price"])))
    inv_unit = str(payload.get("unit", row["unit"]))
    inv_thr = float(payload["threshold"]) if "threshold" in payload else float(row["threshold"] or 0)

    if new_name != old_name:
        clash = conn.execute("SELECT 1 FROM inv_items WHERE key=?", (new_name,)).fetchone()
        if clash:
            emit_error("duplicate_inv_key", {"name": new_name})

    conn.execute("BEGIN IMMEDIATE")
    try:
        if new_name != old_name:
            old_inv = conn.execute("SELECT 1 FROM inv_items WHERE key=?", (old_name,)).fetchone()
            if old_inv:
                conn.execute("UPDATE inv_items SET key=? WHERE key=?", (new_name, old_name))
            conn.execute(
                "UPDATE catalog_service_materials SET product_name=? WHERE product_name=?",
                (new_name, old_name),
            )

        sets.append("updated_at=datetime('now','localtime')")
        vals.append(row["id"])
        conn.execute(f"UPDATE catalog_products SET {', '.join(sets)} WHERE id=?", vals)

        inv_row = conn.execute("SELECT key FROM inv_items WHERE key=?", (new_name,)).fetchone()
        if inv_row:
            conn.execute(
                """UPDATE inv_items SET unit=?, cost=?, sale_price=?, low_stock_threshold=?,
                   updated_at=datetime('now','localtime') WHERE key=?""",
                (inv_unit, inv_cost, inv_price, inv_thr, new_name),
            )
        elif new_name == old_name:
            _upsert_inv_item(conn, new_name, inv_unit, inv_cost, inv_price, inv_thr)

        commit_with_log(
            conn, args, skill=SKILL, command="product-update", op="update", table_name="catalog_products",
            entity_id=str(row["id"]), duration_ms=int((time.perf_counter() - t0) * 1000),
        )
    except Exception:
        conn.rollback()
        raise
    emit_ok({"id": int(row["id"]), "name": new_name})
    return 0


def cmd_product_delete(args: argparse.Namespace) -> int:
    conn = connect()
    row = conn.execute(
        "SELECT id FROM catalog_products WHERE name=? AND status='active'",
        (args.name,),
    ).fetchone()
    if not row:
        emit_error("product_not_found", {"name": args.name})
    if not args.yes:
        emit_need_confirm({"action": "product-delete", "name": args.name})
        return 0
    t0 = time.perf_counter()
    conn.execute(
        "UPDATE catalog_products SET status='deleted', updated_at=datetime('now','localtime') WHERE id=?",
        (row["id"],),
    )
    commit_with_log(
        conn, args, skill=SKILL, command="product-delete", op="update", table_name="catalog_products",
        entity_id=str(row["id"]), after_json={"status": "deleted"},
        duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"name": args.name, "status": "deleted"})
    return 0


def cmd_material_bind(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    sid = _service_id_by_name(conn, args.service)
    if sid is None:
        emit_error("service_not_found", {"name": args.service})
    materials = json.loads(args.materials or "{}")
    conn.execute("DELETE FROM catalog_service_materials WHERE service_id=?", (sid,))
    for pname, qty in materials.items():
        conn.execute(
            "INSERT INTO catalog_service_materials (service_id, product_name, qty) VALUES (?,?,?)",
            (sid, str(pname), float(qty)),
        )
    commit_with_log(
        conn, args, skill=SKILL, command="material-bind", op="update", table_name="catalog_service_materials",
        entity_id=str(sid), duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"service": args.service, "materials": materials})
    return 0


def cmd_material_query(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    sid = _service_id_by_name(conn, args.service)
    if sid is None:
        emit_error("service_not_found", {"name": args.service})
    materials = _attach_materials(conn, sid)
    log_read(
        conn, args, skill=SKILL, command="material-query", table_name="catalog_service_materials",
        entity_id=str(sid), duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"service": args.service, "materials": materials})
    return 0


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

    s = sub.add_parser("service-add", help="新增服务项目")
    s.add_argument("--name", required=True)
    s.add_argument("--price", type=float, required=True)
    s.add_argument("--duration", type=int, required=True)
    s.add_argument("--materials", default=None, help='耗材 JSON，如 {"商品A":1}')
    s.set_defaults(func=cmd_service_add)

    s = sub.add_parser("service-query", help="查询服务")
    s.add_argument("--name", default=None)
    s.set_defaults(func=cmd_service_query)

    s = sub.add_parser("service-update", help="更新服务")
    s.add_argument("--name", required=True)
    s.add_argument("--set", required=True, help="JSON，如 price/duration_min/name")
    s.set_defaults(func=cmd_service_update)

    s = sub.add_parser("service-delete", help="删除服务")
    s.add_argument("--name", required=True)
    s.add_argument("--yes", action="store_true")
    s.set_defaults(func=cmd_service_delete)

    s = sub.add_parser("product-add", help="新增产品")
    s.add_argument("--name", required=True)
    s.add_argument("--cost", type=float, required=True)
    s.add_argument("--price", type=float, required=True)
    s.add_argument("--unit", default="件")
    s.add_argument("--threshold", type=float, default=None)
    s.set_defaults(func=cmd_product_add)

    s = sub.add_parser("product-query", help="查询产品")
    s.add_argument("--name", default=None)
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_product_query)

    s = sub.add_parser("product-update", help="更新产品")
    s.add_argument("--name", required=True)
    s.add_argument("--set", required=True, help="JSON: cost/price/unit/threshold/name")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_product_update)

    s = sub.add_parser("product-delete", help="删除产品")
    s.add_argument("--name", required=True)
    s.add_argument("--yes", action="store_true")
    s.set_defaults(func=cmd_product_delete)

    s = sub.add_parser("material-bind", help="绑定服务耗材")
    s.add_argument("--service", required=True)
    s.add_argument("--materials", required=True, help="JSON，如 {\"商品A\":1}")
    s.set_defaults(func=cmd_material_bind)

    s = sub.add_parser("material-query", help="查询服务耗材")
    s.add_argument("--service", required=True)
    s.set_defaults(func=cmd_material_query)

    return p


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


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