from __future__ import annotations

import json
import sqlite3
from datetime import datetime
from typing import Any, Dict, List, Optional

from store_settlement.catalog import DATE_FMT
from store_settlement.errors import SettlementError
from store_settlement.money import amounts_equal

PAY_MODES = ("stored_value", "cash", "card", "mixed", "card_plus_cash")


def resolve_payment_lines(
    *,
    pay: str,
    total: float,
    lines_json: Optional[str],
    card_name: Optional[str],
    cash_amount: Optional[float],
) -> List[Dict[str, Any]]:
    if lines_json:
        raw = json.loads(lines_json)
        if not isinstance(raw, list) or not raw:
            raise SettlementError("invalid_lines_json")
        return [{"type": str(i["type"]), "amount": float(i.get("amount", 0))} for i in raw]

    pay = pay or "stored_value"
    if pay == "stored_value":
        return [{"type": "consume", "amount": total}]
    if pay == "cash":
        return [{"type": "cash", "amount": total}]
    if pay == "card":
        return [{"type": "card_use", "amount": 0.0}]
    if pay == "mixed":
        raise SettlementError("lines_json_required", {"pay": "mixed"})
    if pay == "card_plus_cash":
        return [{"type": "card_use", "amount": 0.0}, {"type": "cash", "amount": float(cash_amount or 0)}]
    raise SettlementError("invalid_pay_mode", {"pay": pay})


def validate_payment_lines(lines: List[Dict[str, Any]], total: float, pay: str) -> None:
    types = {l["type"] for l in lines}
    if pay == "card":
        if "card_use" not in types:
            raise SettlementError("card_use_required")
        return
    if pay == "card_plus_cash":
        if "card_use" not in types or "cash" not in types:
            raise SettlementError("card_plus_cash_requires_card_and_cash")
        return
    if pay == "mixed":
        if "consume" not in types or "cash" not in types:
            raise SettlementError("mixed_requires_consume_and_cash")
        s = sum(l["amount"] for l in lines if l["type"] in ("consume", "cash"))
        if not amounts_equal(s, total):
            raise SettlementError("payment_sum_mismatch", {"expected": total, "got": s})
        return
    monetary = [l for l in lines if l["type"] in ("consume", "cash")]
    if monetary:
        s = sum(l["amount"] for l in monetary)
        if not amounts_equal(s, total):
            raise SettlementError("payment_sum_mismatch", {"expected": total, "got": s})


def _find_card(
    conn: sqlite3.Connection,
    member_id: str,
    card_name: Optional[str],
    service_name: Optional[str],
) -> sqlite3.Row:
    if not card_name:
        raise SettlementError("need_card_name")
    today = datetime.now().strftime(DATE_FMT)
    row = conn.execute(
        """SELECT * FROM store_cards WHERE member_id=? AND status='active'
           AND (name=? OR service_name=?)""",
        (member_id, card_name, card_name),
    ).fetchone()
    if not row:
        raise SettlementError("card_not_found", {"card_name": card_name})
    if row["expire_date"] and str(row["expire_date"]) < today:
        raise SettlementError("card_expired")
    if int(row["used_times"]) >= int(row["total_times"]):
        raise SettlementError("no_times_left")
    if service_name and row["service_name"] and row["service_name"] != service_name:
        raise SettlementError(
            "card_service_mismatch",
            {"card_service": row["service_name"], "service": service_name},
        )
    return row


def apply_payment_line(
    conn: sqlite3.Connection,
    *,
    member_id: Optional[str],
    staff_id: Optional[str],
    service_name: Optional[str],
    line: Dict[str, Any],
    card_name: Optional[str],
    order_id: Optional[str] = None,
    appt_id: Optional[str] = None,
) -> None:
    typ = line["type"]
    amount = float(line.get("amount", 0))
    meta: Dict[str, Any] = {}
    if order_id:
        meta["order_id"] = order_id
    if service_name:
        meta["service"] = service_name

    if typ == "consume":
        if not member_id:
            raise SettlementError("member_required_for_consume")
        w = conn.execute("SELECT balance FROM store_wallet WHERE member_id=?", (member_id,)).fetchone()
        bal = float(w["balance"]) if w else 0.0
        if bal < amount:
            raise SettlementError("insufficient_balance", {"balance": bal, "need": amount})
        conn.execute(
            """UPDATE store_wallet SET balance=balance-?, total_consume=total_consume+?,
               updated_at=datetime('now','localtime') WHERE member_id=?""",
            (amount, amount, member_id),
        )
        conn.execute(
            """INSERT INTO store_transactions (member_id, type, amount, ref_order_id, ref_appt_id,
               technician_id, meta_json)
               VALUES (?,?,?,?,?,?,?)""",
            (
                member_id,
                "consume",
                amount,
                order_id,
                appt_id,
                staff_id or "",
                json.dumps(meta, ensure_ascii=False),
            ),
        )
    elif typ == "cash":
        if member_id:
            conn.execute(
                """INSERT INTO store_transactions (member_id, type, amount, ref_order_id, ref_appt_id,
                   technician_id, meta_json)
                   VALUES (?,?,?,?,?,?,?)""",
                (
                    member_id,
                    "cash",
                    amount,
                    order_id,
                    appt_id,
                    staff_id or "",
                    json.dumps(meta, ensure_ascii=False),
                ),
            )
    elif typ == "card_use":
        if not member_id:
            raise SettlementError("member_required_for_card")
        card = _find_card(conn, member_id, card_name, service_name)
        meta["card_id"] = card["id"]
        meta["card_name"] = card["name"]
        conn.execute("UPDATE store_cards SET used_times=used_times+1 WHERE id=?", (card["id"],))
        conn.execute(
            """INSERT INTO store_transactions (member_id, type, amount, ref_order_id, ref_appt_id,
               technician_id, meta_json)
               VALUES (?,?,?,?,?,?,?)""",
            (
                member_id,
                "card_use",
                0,
                order_id,
                appt_id,
                staff_id or "",
                json.dumps(meta, ensure_ascii=False),
            ),
        )
    else:
        raise SettlementError("invalid_payment_type", {"type": typ})

    if order_id:
        conn.execute(
            "INSERT INTO store_payments (order_id, type, amount, meta_json) VALUES (?,?,?,?)",
            (order_id, typ, amount, json.dumps(meta, ensure_ascii=False)),
        )
    elif appt_id:
        conn.execute(
            "INSERT INTO store_payments (appt_id, type, amount, meta_json) VALUES (?,?,?,?)",
            (appt_id, typ, amount, json.dumps(meta, ensure_ascii=False)),
        )
