from __future__ import annotations

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

from store_settlement.catalog import query_service_price_and_duration  # noqa: F401
from store_settlement.errors import SettlementError  # noqa: F401

DT_FMT = "%Y-%m-%d %H:%M"
DATE_FMT = "%Y-%m-%d"


def parse_dt(s: str) -> datetime:
    return datetime.strptime(s.strip(), DT_FMT)


def staff_schedule_query(conn: sqlite3.Connection, staff_id: str, date_yyyy_mm_dd: str) -> Dict[str, Any]:
    row = conn.execute(
        "SELECT status, slots_json FROM staff_schedules WHERE staff_id=? AND date=?",
        (staff_id, date_yyyy_mm_dd),
    ).fetchone()
    if not row:
        return {"status": "work", "slots": None}
    try:
        slots = json.loads(row["slots_json"] or "null")
    except json.JSONDecodeError:
        slots = None
    return {"status": row["status"], "slots": slots}


def staff_list_for_skill(conn: sqlite3.Connection, skill_keyword: str) -> List[Dict[str, Any]]:
    needle = skill_keyword or ""
    out: List[Dict[str, Any]] = []
    for r in conn.execute("SELECT * FROM staff_staff WHERE status='active' ORDER BY id").fetchall():
        d = dict(r)
        try:
            fj = json.loads(d.get("fields_json") or "{}")
        except json.JSONDecodeError:
            fj = {}
        blob = json.dumps(fj, ensure_ascii=False)
        if needle in blob or needle in (d.get("name") or ""):
            d["fields"] = fj
            out.append(d)
    return out


def within_slots(start_dt: str, slots: Optional[List]) -> bool:
    if not slots:
        return True
    t = start_dt.split()[1][:5]
    for slot in slots:
        if "-" not in slot:
            continue
        a, b = slot.split("-", 1)
        a, b = a.strip()[:5], b.strip()[:5]
        if a <= t <= b:
            return True
    return False


def appt_interval(
    conn: sqlite3.Connection,
    technician_id: str,
    start_dt: str,
    duration_min: int,
    exclude_appt_id: Optional[str] = None,
) -> Optional[str]:
    s = parse_dt(start_dt)
    e = s + timedelta(minutes=duration_min)
    q = """SELECT id, start_dt, duration_min FROM store_appointments
           WHERE technician_id=? AND status='scheduled'"""
    params: List[Any] = [technician_id]
    if exclude_appt_id:
        q += " AND id!=?"
        params.append(exclude_appt_id)
    for row in conn.execute(q, params):
        os = parse_dt(row["start_dt"])
        oe = os + timedelta(minutes=int(row["duration_min"]))
        if s < oe and os < e:
            return str(row["id"])
    return None


def validate_appt_create(
    conn: sqlite3.Connection,
    *,
    technician_id: str,
    datetime_str: str,
    duration_min: int,
    exclude_appt_id: Optional[str] = None,
) -> Tuple[bool, str]:
    date_part = datetime_str[:10]
    sch = staff_schedule_query(conn, technician_id, date_part)
    if sch.get("status") == "off":
        return False, "technician_off"
    if not within_slots(datetime_str, sch.get("slots")):
        return False, "outside_work_slots"
    clash = appt_interval(
        conn, technician_id, datetime_str, duration_min, exclude_appt_id=exclude_appt_id
    )
    if clash:
        return False, f"schedule_conflict:{clash}"
    return True, ""


def _slot_datetimes(date_yyyy_mm_dd: str, slots: Optional[List], duration_min: int) -> List[str]:
    out: List[str] = []
    if not slots:
        for hour in range(9, 19):
            for minute in (0, 30):
                out.append(f"{date_yyyy_mm_dd} {hour:02d}:{minute:02d}")
        return out
    for slot in slots:
        if "-" not in slot:
            continue
        a, b = slot.split("-", 1)
        a, b = a.strip()[:5], b.strip()[:5]
        cur = datetime.strptime(f"{date_yyyy_mm_dd} {a}", DT_FMT)
        end = datetime.strptime(f"{date_yyyy_mm_dd} {b}", DT_FMT)
        step = timedelta(minutes=30)
        while cur + timedelta(minutes=duration_min) <= end:
            out.append(cur.strftime(DT_FMT))
            cur += step
    return out


def suggest_available_slots(
    conn: sqlite3.Connection,
    *,
    service: str,
    date_yyyy_mm_dd: str,
    technician_id: Optional[str] = None,
    limit: int = 5,
) -> List[Dict[str, Any]]:
    _, dur = query_service_price_and_duration(conn, service)
    duration_min = int(dur or 60)
    if technician_id:
        staff_ids = [technician_id]
    else:
        staff_ids = [s["id"] for s in staff_list_for_skill(conn, service)]
    suggestions: List[Dict[str, Any]] = []
    for sid in staff_ids:
        sch = staff_schedule_query(conn, sid, date_yyyy_mm_dd)
        if sch.get("status") == "off":
            continue
        name_row = conn.execute("SELECT name FROM staff_staff WHERE id=?", (sid,)).fetchone()
        staff_name = name_row["name"] if name_row else sid
        for dt in _slot_datetimes(date_yyyy_mm_dd, sch.get("slots"), duration_min):
            if not within_slots(dt, sch.get("slots")):
                continue
            if appt_interval(conn, sid, dt, duration_min):
                continue
            suggestions.append(
                {"datetime": dt, "technician_id": sid, "technician_name": staff_name, "service": service}
            )
            if len(suggestions) >= limit:
                return suggestions
    return suggestions
