import argparse
from contextlib import closing
import json
from pathlib import Path
import re
import sqlite3

from semantic_notes import api

SLOTS = {
    "scanner-1400": "Flatbed scanner at 14:00",
    "scanner-1500": "Flatbed scanner at 15:00",
    "projector-1400": "Projector at 14:00",
}


def requested_slots(request):
    # This small demo supports explicit 24-hour times such as 14:00.
    times = re.findall(r"(?<![\w:])[0-9]{1,2}:[0-9]{2}(?![\w:])", request)
    if len(times) != 1:
        return []
    return [slot for slot, label in SLOTS.items() if label.endswith(" " + times[0])]


def suggest_slot(request):
    choices = "\n".join(f"{key}: {label}" for key, label in SLOTS.items())
    reply = api("/api/chat", {
        "model": "qwen2.5:1.5b",
        "messages": [
            {"role": "system", "content": (
                "Choose a slot only when the request explicitly specifies one available item "
                "and its time. Use null for an unavailable item, missing time, or ambiguity.\n"
                f"Slots:\n{choices}"
            )},
            {"role": "user", "content": request},
        ],
        "format": {
            "type": "object",
            "properties": {"slot": {"enum": [*SLOTS, None]}},
            "required": ["slot"],
            "additionalProperties": False,
        },
        "options": {"temperature": 0, "num_ctx": 2048, "num_predict": 80},
        "stream": False,
    })
    proposal = json.loads(reply["message"]["content"])
    if not isinstance(proposal, dict) or set(proposal) != {"slot"}:
        raise ValueError("The model did not return a slot proposal.")
    slot = proposal["slot"]
    if slot is not None and (not isinstance(slot, str) or slot not in SLOTS):
        raise ValueError("The proposed slot is outside the available choices.")
    return slot


def reserve(database, slot):
    if slot not in SLOTS:
        raise ValueError("Unknown slot.")
    with closing(sqlite3.connect(database)) as db, db:
        db.execute("CREATE TABLE IF NOT EXISTS reservations (slot TEXT PRIMARY KEY)")
        result = db.execute(
            "INSERT INTO reservations(slot) VALUES (?) ON CONFLICT(slot) DO NOTHING", (slot,)
        )
        return result.rowcount == 1


def review(database, request):
    candidates = requested_slots(request)
    if not candidates:
        print("Please supply one available item and a specific time. Nothing was saved.")
        return
    slot = suggest_slot(request)
    if slot not in candidates:
        print("Please supply one available item and a specific time. Nothing was saved.")
        return
    print(f"Original request: {request!r}\nProposed reservation: {SLOTS[slot]}")
    try:
        answer = input("Type APPROVE to save this reservation: ")
    except (EOFError, KeyboardInterrupt):
        answer = ""
        print()
    if answer.strip() != "APPROVE":
        print("Cancelled. Nothing was saved.")
        return
    if reserve(database, slot):
        print(f"Saved: {SLOTS[slot]}")
    else:
        print("That slot is already reserved. No new record was saved.")


def list_reservations(database):
    rows = []
    if database.is_file():
        with closing(sqlite3.connect(database.resolve().as_uri() + "?mode=ro", uri=True)) as db:
            rows = db.execute("SELECT slot FROM reservations ORDER BY slot").fetchall()
    for (slot,) in rows:
        print(SLOTS[slot])
    if not rows:
        print("No reservations.")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Review a proposal for a fictional reservation.")
    parser.add_argument("--db", type=Path, default=Path("demo-reservations.sqlite3"))
    commands = parser.add_subparsers(dest="command", required=True)
    propose = commands.add_parser("propose")
    propose.add_argument("request")
    commands.add_parser("list")
    args = parser.parse_args()
    try:
        if args.command == "propose":
            review(args.db, args.request)
        else:
            list_reservations(args.db)
    except (OSError, KeyError, ValueError, sqlite3.Error) as error:
        raise SystemExit(str(error)) from None
