from __future__ import annotations

from datetime import datetime, timedelta
from typing import Any
from zoneinfo import ZoneInfo

import aiosqlite

from server.config_loader import MedsConfig, Slot, load_meds_config, slot_datetime, today_str
from server.db import utc_now_iso


async def get_log_for_day(db: aiosqlite.Connection, day: str) -> dict[str, dict[str, Any]]:
    cur = await db.execute(
        "SELECT slot_id, status, logged_at, source FROM intake_log WHERE day = ? ORDER BY logged_at DESC",
        (day,),
    )
    rows = await cur.fetchall()
    await cur.close()
    result: dict[str, dict[str, Any]] = {}
    for slot_id, status, logged_at, source in rows:
        if slot_id not in result:
            result[slot_id] = {"status": status, "logged_at": logged_at, "source": source}
    return result


async def get_snooze_for_day(db: aiosqlite.Connection, day: str) -> dict[str, str]:
    cur = await db.execute("SELECT slot_id, snooze_until FROM snooze WHERE day = ?", (day,))
    rows = await cur.fetchall()
    await cur.close()
    return {slot_id: snooze_until for slot_id, snooze_until in rows}


def compute_status(
    slot: Slot,
    now: datetime,
    tz: ZoneInfo,
    log_entry: dict[str, Any] | None,
    snooze_until: str | None,
) -> str:
    if log_entry:
        return log_entry["status"]

    slot_dt = slot_datetime(now, slot, tz)
    window_end = slot_dt + timedelta(minutes=slot.reminder_window_minutes)

    if snooze_until:
        try:
            snooze_dt = datetime.fromisoformat(snooze_until)
            if now < snooze_dt:
                return "snoozed"
        except ValueError:
            pass

    if now < slot_dt:
        return "upcoming"

    if now <= window_end:
        return "pending"

    return "overdue"


async def build_today(db: aiosqlite.Connection, config: MedsConfig | None = None) -> dict[str, Any]:
    config = config or load_meds_config()
    tz = config.timezone
    now = datetime.now(tz)
    day = today_str(tz)
    logs = await get_log_for_day(db, day)
    snoozes = await get_snooze_for_day(db, day)

    slots_out: list[dict[str, Any]] = []
    for slot in config.slots:
        log_entry = logs.get(slot.id)
        snooze_until = snoozes.get(slot.id)
        status = compute_status(slot, now, tz, log_entry, snooze_until)
        slot_dt = slot_datetime(now, slot, tz)
        window_end = slot_dt + timedelta(minutes=slot.reminder_window_minutes)
        slots_out.append(
            {
                "id": slot.id,
                "time": slot.time.strftime("%H:%M"),
                "label": slot.label,
                "meds": [{"name": m.name, "dose": m.dose} for m in slot.meds],
                "status": status,
                "logged_at": log_entry["logged_at"] if log_entry else None,
                "window_end": window_end.isoformat(),
                "snooze_until": snooze_until,
            }
        )

    return {"day": day, "timezone": str(tz), "now": now.isoformat(), "slots": slots_out}


async def mark_missed_for_overdue(db: aiosqlite.Connection, config: MedsConfig | None = None) -> list[str]:
    from server.db import new_id

    config = config or load_meds_config()
    tz = config.timezone
    now = datetime.now(tz)
    day = today_str(tz)
    logs = await get_log_for_day(db, day)
    marked: list[str] = []

    for slot in config.slots:
        if slot.id in logs:
            continue
        status = compute_status(slot, now, tz, None, None)
        if status == "overdue":
            await db.execute(
                "INSERT INTO intake_log (id, slot_id, day, status, logged_at, source) VALUES (?, ?, ?, ?, ?, ?)",
                (new_id(), slot.id, day, "missed", utc_now_iso(), "system"),
            )
            marked.append(slot.id)

    if marked:
        await db.commit()
    return marked
