from __future__ import annotations

import sqlite3
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Tuple

from store_schedule_lib.config_loader import threshold_float, threshold_int
from store_schedule_lib.meta import meta_float, meta_int

REMINDER_TYPES = frozenset(
    {
        "appt_today",
        "appt_30m",
        "birthday_today",
        "low_stock",
        "staff_off_tomorrow",
        "card_expiring",
        "balance_low",
        "followup_due",
        "member_due",
    }
)

BLOCK_SPECS: List[Tuple[str, str, str]] = [
    ("appt_today", "📅 今日预约", "query_appt_today"),
    ("birthday_today", "🎂 客户生日", "query_birthday_today"),
    ("low_stock", "📦 低库存", "query_low_stock"),
    ("staff_off_tomorrow", "👥 明日休息", "query_staff_off_tomorrow"),
    ("followup_due", "📞 待跟进", "query_followup_due"),
    ("card_expiring", "🎫 次卡到期", "query_card_expiring"),
    ("balance_low", "💳 低余额", "query_balance_low"),
    ("member_due", "📅 到期提醒", "query_member_due"),
    ("appt_30m", "🔔 即将开始预约", "query_appt_30m"),
]

QUERY_DISPATCH: Dict[str, str] = {t: fn for t, _, fn in BLOCK_SPECS}


def _rows_to_items(rows: List[sqlite3.Row], keys: Tuple[str, ...]) -> List[Dict[str, Any]]:
    out: List[Dict[str, Any]] = []
    for r in rows:
        item: Dict[str, Any] = {}
        for k in keys:
            if k in r.keys():
                item[k] = r[k]
        out.append(item)
    return out


def query_appt_today(conn: sqlite3.Connection, date: str) -> List[Dict[str, Any]]:
    rows = conn.execute(
        """SELECT a.id AS appt_id, m.name AS member_name, a.service_name AS service,
                  a.start_dt, a.technician_id
           FROM store_appointments a
           JOIN store_members m ON m.id = a.member_id AND m.status = 'active'
           WHERE a.status = 'scheduled' AND substr(a.start_dt, 1, 10) = ?
           ORDER BY a.start_dt""",
        (date,),
    ).fetchall()
    return _rows_to_items(rows, ("appt_id", "member_name", "service", "start_dt", "technician_id"))


def query_appt_range(conn: sqlite3.Connection, start_date: str, end_date: str) -> List[Dict[str, Any]]:
    rows = conn.execute(
        """SELECT a.id AS appt_id, a.member_id, m.name AS member_name, a.service_name AS service,
                  a.start_dt, a.technician_id
           FROM store_appointments a
           JOIN store_members m ON m.id = a.member_id AND m.status = 'active'
           WHERE a.status = 'scheduled'
             AND substr(a.start_dt, 1, 10) >= ?
             AND substr(a.start_dt, 1, 10) <= ?
           ORDER BY a.start_dt""",
        (start_date, end_date),
    ).fetchall()
    return _rows_to_items(
        rows, ("appt_id", "member_id", "member_name", "service", "start_dt", "technician_id")
    )


def query_birthday_today(conn: sqlite3.Connection, date: str) -> List[Dict[str, Any]]:
    mmdd = date[5:10] if len(date) >= 10 else date
    rows = conn.execute(
        """SELECT id AS member_id, name
           FROM store_members
           WHERE status = 'active'
             AND birthday IS NOT NULL AND birthday != ''
             AND substr(birthday, 6, 5) = ?
           ORDER BY name""",
        (mmdd,),
    ).fetchall()
    return _rows_to_items(rows, ("member_id", "name"))


def query_low_stock(conn: sqlite3.Connection, date: str) -> List[Dict[str, Any]]:
    rows = conn.execute(
        """SELECT key, stock_qty, low_stock_threshold
           FROM inv_items
           WHERE status = 'active' AND stock_qty <= low_stock_threshold
           ORDER BY stock_qty ASC, key ASC""",
    ).fetchall()
    return _rows_to_items(rows, ("key", "stock_qty", "low_stock_threshold"))


def query_staff_off_tomorrow(conn: sqlite3.Connection, date: str) -> List[Dict[str, Any]]:
    tomorrow = (datetime.strptime(date, "%Y-%m-%d") + timedelta(days=1)).strftime("%Y-%m-%d")
    rows = conn.execute(
        """SELECT s.id AS staff_id, s.name, sch.date
           FROM staff_schedules sch
           JOIN staff_staff s ON s.id = sch.staff_id AND s.status = 'active'
           WHERE sch.date = ? AND sch.status = 'off'
           ORDER BY s.id""",
        (tomorrow,),
    ).fetchall()
    return _rows_to_items(rows, ("staff_id", "name", "date"))


def query_followup_due(conn: sqlite3.Connection, date: str) -> List[Dict[str, Any]]:
    rows = conn.execute(
        """SELECT f.id AS followup_id, f.member_id, m.name AS member_name, f.next_followup_date
           FROM store_followups f
           JOIN store_members m ON m.id = f.member_id AND m.status = 'active'
           WHERE f.next_followup_date IS NOT NULL AND f.next_followup_date != ''
             AND f.next_followup_date <= ?
           ORDER BY f.next_followup_date, f.id""",
        (date,),
    ).fetchall()
    return _rows_to_items(rows, ("followup_id", "member_id", "member_name", "next_followup_date"))


def query_card_expiring(conn: sqlite3.Connection, date: str, *, days: Optional[int] = None) -> List[Dict[str, Any]]:
    span = days if days is not None else threshold_int(conn, "card_expiring_days", 30)
    end = (datetime.strptime(date, "%Y-%m-%d") + timedelta(days=span)).strftime("%Y-%m-%d")
    rows = conn.execute(
        """SELECT c.id AS card_id, c.member_id, m.name AS member_name, c.name,
                  c.expire_date,
                  (c.total_times - c.used_times) AS remaining
           FROM store_cards c
           JOIN store_members m ON m.id = c.member_id AND m.status = 'active'
           WHERE c.status = 'active'
             AND c.expire_date IS NOT NULL AND c.expire_date != ''
             AND c.expire_date >= ? AND c.expire_date <= ?
             AND c.used_times < c.total_times
           ORDER BY c.expire_date, c.id""",
        (date, end),
    ).fetchall()
    return _rows_to_items(
        rows, ("card_id", "member_id", "member_name", "name", "expire_date", "remaining")
    )


def query_balance_low(conn: sqlite3.Connection, date: str) -> List[Dict[str, Any]]:
    thr = threshold_float(conn, "balance_low_threshold", 500.0)
    rows = conn.execute(
        """SELECT m.id AS member_id, m.name, w.balance
           FROM store_members m
           JOIN store_wallet w ON w.member_id = m.id
           WHERE m.status = 'active' AND w.balance < ?
           ORDER BY w.balance ASC, m.id""",
        (thr,),
    ).fetchall()
    return _rows_to_items(rows, ("member_id", "name", "balance"))


def query_appt_30m(conn: sqlite3.Connection, date: str) -> List[Dict[str, Any]]:
    now = datetime.now()
    end = now + timedelta(minutes=30)
    rows = conn.execute(
        """SELECT a.id AS appt_id, m.name AS member_name, a.service_name AS service,
                  a.start_dt, a.technician_id
           FROM store_appointments a
           JOIN store_members m ON m.id = a.member_id AND m.status = 'active'
           WHERE a.status = 'scheduled'
             AND substr(a.start_dt, 1, 10) = ?
           ORDER BY a.start_dt""",
        (date,),
    ).fetchall()
    items: List[Dict[str, Any]] = []
    for r in rows:
        try:
            start = datetime.strptime(r["start_dt"], "%Y-%m-%d %H:%M")
        except ValueError:
            continue
        if now <= start <= end:
            d = dict(r)
            d["minutes_until"] = int((start - now).total_seconds() // 60)
            items.append(d)
    return items


def query_member_due(conn: sqlite3.Connection, date: str, *, days: Optional[int] = None) -> List[Dict[str, Any]]:
    span = days if days is not None else threshold_int(conn, "member_due_days", 30)
    end = (datetime.strptime(date, "%Y-%m-%d") + timedelta(days=span)).strftime("%Y-%m-%d")
    rows = conn.execute(
        """SELECT id AS member_id, name, due_date
           FROM store_members
           WHERE status = 'active'
             AND due_date IS NOT NULL AND due_date != ''
             AND due_date >= ? AND due_date <= ?
           ORDER BY due_date, id""",
        (date, end),
    ).fetchall()
    return _rows_to_items(rows, ("member_id", "name", "due_date"))


def run_query(
    conn: sqlite3.Connection,
    reminder_type: str,
    date: str,
    *,
    days: Optional[int] = None,
) -> List[Dict[str, Any]]:
    if reminder_type == "appt_today":
        return query_appt_today(conn, date)
    if reminder_type == "birthday_today":
        return query_birthday_today(conn, date)
    if reminder_type == "low_stock":
        return query_low_stock(conn, date)
    if reminder_type == "staff_off_tomorrow":
        return query_staff_off_tomorrow(conn, date)
    if reminder_type == "followup_due":
        return query_followup_due(conn, date)
    if reminder_type == "card_expiring":
        return query_card_expiring(conn, date, days=days)
    if reminder_type == "balance_low":
        return query_balance_low(conn, date)
    if reminder_type == "appt_30m":
        return query_appt_30m(conn, date)
    if reminder_type == "member_due":
        return query_member_due(conn, date, days=days)
    raise ValueError(reminder_type)


def bucket_for_item(reminder_type: str, date: str, item: Dict[str, Any], *, days: Optional[int] = None) -> str:
    if reminder_type in (
        "appt_today", "birthday_today", "low_stock", "staff_off_tomorrow",
        "balance_low", "followup_due",
    ):
        return date
    if reminder_type == "appt_30m":
        return "{}_{}".format(item.get("appt_id"), date)
    if reminder_type == "card_expiring":
        d = days if days is not None else 30
        return "{}d".format(d)
    if reminder_type == "member_due":
        d = days if days is not None else 30
        return "{}d".format(d)
    if reminder_type == "appt":
        return item.get("remind_at", date)
    return date


def target_id_for_item(reminder_type: str, item: Dict[str, Any]) -> str:
    mapping = {
        "appt_today": "appt_id",
        "appt_30m": "appt_id",
        "appt": "appt_id",
        "birthday_today": "member_id",
        "birthday": "member_id",
        "low_stock": "key",
        "staff_off_tomorrow": "staff_id",
        "followup_due": "followup_id",
        "follow_up": "member_id",
        "card_expiring": "card_id",
        "balance_low": "member_id",
        "member_due": "member_id",
    }
    key = mapping.get(reminder_type, "id")
    return str(item.get(key) or item.get("member_id") or "")


def mark_items_sent(
    conn: sqlite3.Connection,
    reminder_type: str,
    date: str,
    items: List[Dict[str, Any]],
    *,
    days: Optional[int] = None,
) -> int:
    n = 0
    for item in items:
        tid = target_id_for_item(reminder_type, item)
        if not tid:
            continue
        bucket = bucket_for_item(reminder_type, date, item, days=days)
        cur = conn.execute(
            """INSERT OR IGNORE INTO sent_reminders (reminder_type, target_id, bucket, note)
               VALUES (?,?,?,?)""",
            (reminder_type, tid, bucket, item.get("note", "")),
        )
        if cur.rowcount:
            n += 1
    conn.commit()
    return n
