"""Minimal aiogram 3 Telegram booking bot using :mod:`booking_core`."""

from __future__ import annotations

import asyncio
import os
from contextlib import suppress
from datetime import datetime, timezone

from aiogram import Bot, Dispatcher, F
from aiogram.filters import Command, CommandStart
from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message

from booking_core import BookingStore, display_time, parse_local_datetime


def admin_ids() -> set[int]:
    return {int(value) for value in os.getenv("ADMIN_IDS", "").split(",") if value.strip()}


def now_utc() -> datetime:
    return datetime.now(timezone.utc)


def slot_keyboard(slots: list) -> InlineKeyboardMarkup:
    return InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text=f"{display_time(s.starts_at, os.getenv('BOT_TIMEZONE', 'UTC'))} · {s.remaining} left", callback_data=f"book:{s.id}")]
        for s in slots
    ])


async def run() -> None:
    token = os.environ["BOT_TOKEN"]
    timezone_name = os.getenv("BOT_TIMEZONE", "UTC")
    store = BookingStore(os.getenv("DATABASE_PATH", "bookings.sqlite3"))
    administrators = admin_ids()
    bot = Bot(token)
    dispatcher = Dispatcher()

    @dispatcher.message(CommandStart())
    async def start(message: Message) -> None:
        await message.answer("Welcome. Use /book to choose a slot or /mybookings to view your bookings.")

    @dispatcher.message(Command("book"))
    async def book(message: Message) -> None:
        slots = store.list_available(now_utc())
        if not slots:
            await message.answer("There are no open slots right now.")
            return
        await message.answer("Choose a slot:", reply_markup=slot_keyboard(slots))

    @dispatcher.callback_query(F.data.startswith("book:"))
    async def confirm_booking(callback: CallbackQuery) -> None:
        await callback.answer()
        user = callback.from_user
        ok, reason = store.book(int(callback.data.split(":", 1)[1]), user.id, user.full_name, now_utc())
        messages = {"booked": "Booked. Use /mybookings to see it.", "already_booked": "You already have that slot.", "sold_out": "That slot just filled; run /book again.", "slot_not_found": "That slot no longer exists."}
        await callback.message.answer(messages.get(reason, "Booking was not completed."))

    @dispatcher.message(Command("mybookings"))
    async def my_bookings(message: Message) -> None:
        rows = store.for_user(message.from_user.id, now_utc())
        if not rows:
            await message.answer("You have no upcoming bookings.")
            return
        await message.answer("\n".join(f"#{row.slot_id} · {display_time(row.starts_at, timezone_name)}" for row in rows))

    @dispatcher.message(Command("cancel"))
    async def cancel(message: Message) -> None:
        parts = (message.text or "").split()
        if len(parts) != 2 or not parts[1].isdigit():
            await message.answer("Usage: /cancel SLOT_ID")
            return
        await message.answer("Cancelled." if store.cancel(int(parts[1]), message.from_user.id) else "No matching booking found.")

    @dispatcher.message(Command("admin_add"))
    async def admin_add(message: Message) -> None:
        if message.from_user.id not in administrators:
            return
        parts = (message.text or "").split()
        if len(parts) not in (3, 4):
            await message.answer("Usage: /admin_add YYYY-MM-DD HH:MM [capacity]")
            return
        try:
            capacity = int(parts[3]) if len(parts) == 4 else 1
            slot_id = store.add_slot(parse_local_datetime(f"{parts[1]} {parts[2]}", timezone_name), capacity)
        except (ValueError, IndexError):
            await message.answer("Use YYYY-MM-DDTHH:MM or YYYY-MM-DD HH:MM and a positive capacity.")
            return
        await message.answer(f"Slot #{slot_id} is ready.")

    @dispatcher.message(Command("admin_list"))
    async def admin_list(message: Message) -> None:
        if message.from_user.id not in administrators:
            return
        slots = store.list_available(now_utc(), limit=50)
        await message.answer("\n".join(f"#{s.id} · {display_time(s.starts_at, timezone_name)} · {s.booked}/{s.capacity}" for s in slots) or "No open slots.")

    async def reminder_loop() -> None:
        while True:
            for booking in store.claim_reminders(now_utc()):
                with suppress(Exception):
                    await bot.send_message(booking.user_id, f"Reminder: your booking is at {display_time(booking.starts_at, timezone_name)}.")
            await asyncio.sleep(60)

    reminder = asyncio.create_task(reminder_loop())
    try:
        await dispatcher.start_polling(bot)
    finally:
        reminder.cancel()
        with suppress(asyncio.CancelledError):
            await reminder
        store.close()
        await bot.session.close()


if __name__ == "__main__":
    asyncio.run(run())
