from __future__ import annotations

from datetime import datetime, timezone
from typing import Optional
import calendar as pycalendar

from quart import request
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload, with_loader_criteria

from models.calendars import Calendar, CalendarSlot

def parse_int_arg(name: str, default: Optional[int] = None) -> Optional[int]:
    """Parse an integer query parameter from the request."""
    val = request.args.get(name, "").strip()
    if not val:
        return default
    try:
        return int(val)
    except ValueError:
        return default


def add_months(year: int, month: int, delta: int) -> tuple[int, int]:
    """Add (or subtract) months to a given year/month, handling year overflow."""
    new_month = month + delta
    new_year = year + (new_month - 1) // 12
    new_month = ((new_month - 1) % 12) + 1
    return new_year, new_month


def build_calendar_weeks(year: int, month: int) -> list[list[dict]]:
    """
    Build a calendar grid for the given year and month.
    Returns a list of weeks, where each week is a list of 7 day dictionaries.
    """
    today = datetime.now(timezone.utc).date()
    cal = pycalendar.Calendar(firstweekday=0)  # 0 = Monday
    weeks: list[list[dict]] = []

    for week in cal.monthdatescalendar(year, month):
        week_days = []
        for d in week:
            week_days.append(
                {
                    "date": d,
                    "in_month": (d.month == month),
                    "is_today": (d == today),
                }
            )
        weeks.append(week_days)

    return weeks


async def get_calendar_by_post_and_slug(
    session: AsyncSession,
    post_id: int,
    calendar_slug: str,
) -> Optional[Calendar]:
    """
    Fetch a calendar by post_id and slug, with slots eagerly loaded.
    Returns None if not found.
    """
    result = await session.execute(
        select(Calendar)
        .options(
            selectinload(Calendar.slots),
            with_loader_criteria(CalendarSlot, CalendarSlot.deleted_at.is_(None)),
        )
        .where(
            Calendar.container_type == "page",
            Calendar.container_id == post_id,
            Calendar.slug == calendar_slug,
            Calendar.deleted_at.is_(None),
        )
    )
    return result.scalar_one_or_none()


async def get_calendar_by_slug(
    session: AsyncSession,
    calendar_slug: str,
) -> Optional[Calendar]:
    """
    Fetch a calendar by slug only (for standalone events service).
    With slots eagerly loaded. Returns None if not found.
    """
    result = await session.execute(
        select(Calendar)
        .options(
            selectinload(Calendar.slots),
            with_loader_criteria(CalendarSlot, CalendarSlot.deleted_at.is_(None)),
        )
        .where(
            Calendar.slug == calendar_slug,
            Calendar.deleted_at.is_(None),
        )
    )
    return result.scalar_one_or_none()


async def update_calendar_description(
    calendar: Calendar,
    description: Optional[str],
) -> None:
    """Update calendar description (in-place on the calendar object)."""
    calendar.description = description or None
