from __future__ import annotations
from datetime import datetime, timezone

from quart import (
    request, make_response, Blueprint, g, abort, session as qsession
)


from sqlalchemy import select

from models.calendars import Calendar

from sqlalchemy.orm import selectinload,  with_loader_criteria
from shared.browser.app.authz import require_admin

from .admin.routes import register as register_admin
from .services import get_visible_entries_for_period
from .services.calendar_view import (
    parse_int_arg,
    add_months,
    build_calendar_weeks,
    get_calendar_by_post_and_slug,
    get_calendar_by_slug,
    update_calendar_description,
)
from shared.browser.app.utils.htmx import is_htmx_request
from shared.sx.helpers import sx_response

from ..slots.routes import register as register_slots

from models.calendars import CalendarSlot

from bp.calendars.services.calendars import soft_delete

from bp.day.routes import register as register_day

from shared.browser.app.redis_cacher import cache_page, clear_cache

from sqlalchemy import select

import calendar as pycalendar


def register():
    bp = Blueprint("calendar", __name__, url_prefix='/<calendar_slug>')

    bp.register_blueprint(
        register_admin(),
    )
    bp.register_blueprint(
        register_slots(),
    )
    bp.register_blueprint(
        register_day()
    )

    @bp.url_value_preprocessor
    def pull(endpoint, values):
      g.calendar_slug = values.get("calendar_slug")
    
    @bp.before_request
    async def hydrate_calendar_data():
        calendar_slug = getattr(g, "calendar_slug", None)

        # Standalone mode (events app): no post context
        post_data = getattr(g, "post_data", None)
        if post_data:
            post_id = (post_data.get("post") or {}).get("id")
            cal = await get_calendar_by_post_and_slug(g.s, post_id, calendar_slug)
        else:
            cal = await get_calendar_by_slug(g.s, calendar_slug)

        if not cal:
            abort(404)
            return

        g.calendar = cal

    @bp.context_processor
    async def inject_root():
        from shared.infrastructure.fragments import fetch_fragment

        container_nav = ""
        post_data = getattr(g, "post_data", None)
        if post_data:
            post_id = post_data["post"]["id"]
            post_slug = post_data["post"]["slug"]
            container_nav = await fetch_fragment("relations", "container-nav", params={
                "container_type": "page",
                "container_id": str(post_id),
                "post_slug": post_slug,
                "exclude": "page->calendar",
            })

        return {
            "calendar": getattr(g, "calendar", None),
            "container_nav": container_nav,
        }

    # ---------- Pages ----------


        # ---------- Pages ----------

    @bp.get("/")
    @cache_page(tag="calendars")
    async def get(calendar_slug: str, **kwargs):
        """
        Show a month-view calendar for this calendar.

        - One month at a time
        - Outer arrows: +/- 1 year
        - Inner arrows: +/- 1 month
        """

        # --- Determine year & month from query params ---
        today = datetime.now(timezone.utc).date()

        month = parse_int_arg("month")
        year = parse_int_arg("year")

        if year is None:
            year = today.year
        if month is None or not (1 <= month <= 12):
            month = today.month

        # --- Helpers to move between months ---
        prev_month_year, prev_month = add_months(year, month, -1)
        next_month_year, next_month = add_months(year, month, +1)
        prev_year = year - 1
        next_year = year + 1

        # --- Build weeks grid (list of weeks, each week = 7 days) ---
        weeks = build_calendar_weeks(year, month)
        month_name = pycalendar.month_name[month]
        weekday_names = [pycalendar.day_abbr[i] for i in range(7)]

        # --- Period boundaries for this calendar view ---
        period_start = datetime(year, month, 1, tzinfo=timezone.utc)
        next_y, next_m = add_months(year, month, +1)
        period_end = datetime(next_y, next_m, 1, tzinfo=timezone.utc)

        # --- Identity & admin flag ---
        user = getattr(g, "user", None)
        session_id = qsession.get("calendar_sid")

        visible = await get_visible_entries_for_period(
            sess=g.s,
            calendar_id=g.calendar.id,
            period_start=period_start,
            period_end=period_end,
            user=user,
            session_id=session_id,
        )

        month_entries = visible.merged_entries
        user_entries = visible.user_entries
        confirmed_entries = visible.confirmed_entries

        from shared.sx.page import get_template_context
        from sxc.pages.renders import render_calendar_page, render_calendar_oob

        tctx = await get_template_context()
        tctx.update(dict(
            qsession=qsession,
            year=year, month=month, month_name=month_name,
            weekday_names=weekday_names, weeks=weeks,
            prev_month=prev_month, prev_month_year=prev_month_year,
            next_month=next_month, next_month_year=next_month_year,
            prev_year=prev_year, next_year=next_year,
            user_entries=user_entries, confirmed_entries=confirmed_entries,
            month_entries=month_entries,
        ))
        if not is_htmx_request():
            html = await render_calendar_page(tctx)
            return await make_response(html)
        else:
            sx_src = await render_calendar_oob(tctx)
            return sx_response(sx_src)

        
    @bp.put("/")
    @require_admin
    @clear_cache(tag="calendars", tag_scope="all")
    async def put(calendar_slug: str, **kwargs):
        """
        Idempotent update for calendar configuration.
        Accepts HTMX form (POST/PUT) and optional JSON.
        """
        # Try JSON first
        data = await request.get_json(silent=True)
        description = None

        if data and isinstance(data, dict):
            description = (data.get("description") or "").strip()
        else:
            form = await request.form
            description = (form.get("description") or "").strip()

        await update_calendar_description(g.calendar, description)
        from shared.sx.page import get_template_context
        from sxc.pages.calendar import _calendar_admin_main_panel_html
        ctx = await get_template_context()
        html = _calendar_admin_main_panel_html(ctx)
        return sx_response(html)


    @bp.delete("/")
    @require_admin
    @clear_cache(tag="calendars", tag_scope="all")
    async def delete(calendar_slug: str, **kwargs):
        from shared.browser.app.utils.htmx import is_htmx_request

        cal = g.calendar
        cal.deleted_at = datetime.now(timezone.utc)
        await g.s.flush()

        # If we have post context (blog-embedded mode), update nav
        post_data = getattr(g, "post_data", None)
        from shared.sx.page import get_template_context
        from sxc.pages.renders import render_calendars_list_panel
        ctx = await get_template_context()
        html = render_calendars_list_panel(ctx)

        if post_data:
            from shared.services.entry_associations import get_associated_entries
            from sxc.pages.entries import render_post_nav_entries_oob

            post_id = (post_data.get("post") or {}).get("id")
            cals = (
                await g.s.execute(
                    select(Calendar)
                    .where(Calendar.container_type == "page", Calendar.container_id == post_id, Calendar.deleted_at.is_(None))
                    .order_by(Calendar.name.asc())
                )
            ).scalars().all()

            associated_entries = await get_associated_entries(post_id)
            nav_oob = render_post_nav_entries_oob(associated_entries, cals, post_data["post"])
            html = html + nav_oob

        return sx_response(html)


    return bp
