"""
Tickets blueprint — user-facing ticket views and QR codes.

Routes:
  GET  /tickets/              — My tickets list
  GET  /tickets/<code>/       — Ticket detail with QR code
  POST /tickets/buy/          — Purchase tickets for an entry
  POST /tickets/adjust/       — Adjust ticket quantity (+/-)
"""
from __future__ import annotations

import logging

from quart import (
    Blueprint, g, request, make_response,
)
from sqlalchemy import select
from sqlalchemy.orm import selectinload

from models.calendars import CalendarEntry
from shared.infrastructure.cart_identity import current_cart_identity
from shared.browser.app.redis_cacher import clear_cache
from shared.sx.helpers import sx_response

from .services.tickets import (
    create_ticket,
    get_available_ticket_count,
    get_tickets_for_entry,
    get_sold_ticket_count,
    get_user_reserved_count,
    cancel_latest_reserved_ticket,
)

logger = logging.getLogger(__name__)


def register() -> Blueprint:
    bp = Blueprint("tickets", __name__, url_prefix="/tickets")

    @bp.post("/buy/")
    @clear_cache(tag="calendars", tag_scope="all")
    async def buy_tickets():
        """
        Purchase tickets for a calendar entry.
        Creates ticket records with state='reserved' (awaiting payment).

        Form fields:
          entry_id     — the calendar entry ID
          ticket_type_id (optional) — specific ticket type
          quantity     — number of tickets (default 1)
        """
        form = await request.form

        entry_id_raw = form.get("entry_id", "").strip()
        if not entry_id_raw:
            return await make_response("Entry ID required", 400)

        try:
            entry_id = int(entry_id_raw)
        except ValueError:
            return await make_response("Invalid entry ID", 400)

        # Load entry
        entry = await g.s.scalar(
            select(CalendarEntry)
            .where(
                CalendarEntry.id == entry_id,
                CalendarEntry.deleted_at.is_(None),
            )
            .options(selectinload(CalendarEntry.ticket_types))
        )
        if not entry:
            return await make_response("Entry not found", 404)

        if entry.ticket_price is None:
            return await make_response("Tickets not available for this entry", 400)

        # Check availability
        available = await get_available_ticket_count(g.s, entry_id)
        quantity = int(form.get("quantity", 1))
        if quantity < 1:
            quantity = 1

        if available is not None and quantity > available:
            return await make_response(
                f"Only {available} ticket(s) remaining", 400
            )

        # Ticket type (optional)
        ticket_type_id = None
        tt_raw = form.get("ticket_type_id", "").strip()
        if tt_raw:
            try:
                ticket_type_id = int(tt_raw)
            except ValueError:
                pass

        ident = current_cart_identity()

        # Create tickets
        created = []
        for _ in range(quantity):
            ticket = await create_ticket(
                g.s,
                entry_id=entry_id,
                ticket_type_id=ticket_type_id,
                user_id=ident["user_id"],
                session_id=ident["session_id"],
                state="reserved",
            )
            created.append(ticket)

        # Re-check availability for display
        remaining = await get_available_ticket_count(g.s, entry_id)
        all_tickets = await get_tickets_for_entry(g.s, entry_id)

        # Compute cart count for OOB mini-cart update
        from shared.infrastructure.data_client import fetch_data
        from shared.contracts.dtos import CartSummaryDTO, dto_from_dict
        summary_params = {}
        if ident["user_id"] is not None:
            summary_params["user_id"] = ident["user_id"]
        if ident["session_id"] is not None:
            summary_params["session_id"] = ident["session_id"]
        raw_summary = await fetch_data("cart", "cart-summary", params=summary_params, required=False)
        summary = dto_from_dict(CartSummaryDTO, raw_summary) if raw_summary else CartSummaryDTO()
        cart_count = summary.count + summary.calendar_count + summary.ticket_count

        from sxc.pages.tickets import render_buy_result
        return sx_response(render_buy_result(entry, created, remaining, cart_count))

    @bp.post("/adjust/")
    @clear_cache(tag="calendars", tag_scope="all")
    async def adjust_quantity():
        """
        Adjust ticket quantity for a calendar entry (+/- pattern).
        Creates or cancels tickets to reach the target count.

        Form fields:
          entry_id       — the calendar entry ID
          ticket_type_id — (optional) specific ticket type
          count          — target quantity of reserved tickets
        """
        form = await request.form

        entry_id_raw = form.get("entry_id", "").strip()
        if not entry_id_raw:
            return await make_response("Entry ID required", 400)
        try:
            entry_id = int(entry_id_raw)
        except ValueError:
            return await make_response("Invalid entry ID", 400)

        # Load entry
        entry = await g.s.scalar(
            select(CalendarEntry)
            .where(
                CalendarEntry.id == entry_id,
                CalendarEntry.deleted_at.is_(None),
            )
            .options(selectinload(CalendarEntry.ticket_types))
        )
        if not entry:
            return await make_response("Entry not found", 404)
        if entry.ticket_price is None:
            return await make_response("Tickets not available for this entry", 400)

        # Ticket type (optional)
        ticket_type_id = None
        tt_raw = form.get("ticket_type_id", "").strip()
        if tt_raw:
            try:
                ticket_type_id = int(tt_raw)
            except ValueError:
                pass

        target = max(int(form.get("count", 0)), 0)
        ident = current_cart_identity()

        current = await get_user_reserved_count(
            g.s, entry_id,
            user_id=ident["user_id"],
            session_id=ident["session_id"],
            ticket_type_id=ticket_type_id,
        )

        if target > current:
            # Need to add tickets
            to_add = target - current
            available = await get_available_ticket_count(g.s, entry_id)
            if available is not None and to_add > available:
                return await make_response(
                    f"Only {available} ticket(s) remaining", 400
                )
            for _ in range(to_add):
                await create_ticket(
                    g.s,
                    entry_id=entry_id,
                    ticket_type_id=ticket_type_id,
                    user_id=ident["user_id"],
                    session_id=ident["session_id"],
                    state="reserved",
                )
        elif target < current:
            # Need to remove tickets
            to_remove = current - target
            for _ in range(to_remove):
                await cancel_latest_reserved_ticket(
                    g.s, entry_id,
                    user_id=ident["user_id"],
                    session_id=ident["session_id"],
                    ticket_type_id=ticket_type_id,
                )

        # Build context for re-rendering the buy form
        ticket_remaining = await get_available_ticket_count(g.s, entry_id)
        ticket_sold_count = await get_sold_ticket_count(g.s, entry_id)
        user_ticket_count = await get_user_reserved_count(
            g.s, entry_id,
            user_id=ident["user_id"],
            session_id=ident["session_id"],
        )

        # Per-type counts for multi-type entries
        user_ticket_counts_by_type = {}
        if entry.ticket_types:
            for tt in entry.ticket_types:
                if tt.deleted_at is None:
                    user_ticket_counts_by_type[tt.id] = await get_user_reserved_count(
                        g.s, entry_id,
                        user_id=ident["user_id"],
                        session_id=ident["session_id"],
                        ticket_type_id=tt.id,
                    )

        # Commit so cart's callback to events sees the updated tickets
        await g.tx.commit()
        g.tx = await g.s.begin()

        # Compute cart count for OOB mini-cart update
        from shared.infrastructure.data_client import fetch_data
        from shared.contracts.dtos import CartSummaryDTO, dto_from_dict
        summary_params = {}
        if ident["user_id"] is not None:
            summary_params["user_id"] = ident["user_id"]
        if ident["session_id"] is not None:
            summary_params["session_id"] = ident["session_id"]
        raw_summary = await fetch_data("cart", "cart-summary", params=summary_params, required=False)
        summary = dto_from_dict(CartSummaryDTO, raw_summary) if raw_summary else CartSummaryDTO()
        cart_count = summary.count + summary.calendar_count + summary.ticket_count

        from sxc.pages.tickets import render_adjust_response
        return sx_response(render_adjust_response(
            entry, ticket_remaining, ticket_sold_count,
            user_ticket_count, user_ticket_counts_by_type, cart_count,
        ))

    return bp
