"""
Page-scoped cart queries.

Groups cart items and calendar entries by their owning page (Post),
determined via CartItem.market_place_container_id
(where container_type == "page").
"""
from __future__ import annotations

from collections import defaultdict

from types import SimpleNamespace

from sqlalchemy import select

from shared.models.market import CartItem
from shared.infrastructure.data_client import fetch_data
from shared.contracts.dtos import CalendarEntryDTO, TicketDTO, PostDTO, dto_from_dict
from .identity import current_cart_identity
from .get_cart import _attach_product_namespace, _attach_market_place_namespace


async def get_cart_for_page(session, post_id: int) -> list[CartItem]:
    """Return cart items scoped to a specific page (via denormalized market_place_container_id)."""
    ident = current_cart_identity()

    filters = [
        CartItem.deleted_at.is_(None),
        CartItem.market_place_container_id == post_id,
    ]
    if ident["user_id"] is not None:
        filters.append(CartItem.user_id == ident["user_id"])
    else:
        filters.append(CartItem.session_id == ident["session_id"])

    result = await session.execute(
        select(CartItem)
        .where(*filters)
        .order_by(CartItem.created_at.desc())
    )
    items = list(result.scalars().all())

    for ci in items:
        _attach_product_namespace(ci)
        _attach_market_place_namespace(ci)

    return items


async def get_calendar_entries_for_page(session, post_id: int):
    """Return pending calendar entries (DTOs) scoped to a specific page."""
    ident = current_cart_identity()
    params = {"page_id": post_id}
    if ident["user_id"] is not None:
        params["user_id"] = ident["user_id"]
    if ident["session_id"] is not None:
        params["session_id"] = ident["session_id"]
    raw = await fetch_data("events", "entries-for-page", params=params, required=False) or []
    return [dto_from_dict(CalendarEntryDTO, e) for e in raw]


async def get_tickets_for_page(session, post_id: int):
    """Return reserved tickets (DTOs) scoped to a specific page."""
    ident = current_cart_identity()
    params = {"page_id": post_id}
    if ident["user_id"] is not None:
        params["user_id"] = ident["user_id"]
    if ident["session_id"] is not None:
        params["session_id"] = ident["session_id"]
    raw = await fetch_data("events", "tickets-for-page", params=params, required=False) or []
    return [dto_from_dict(TicketDTO, t) for t in raw]


async def get_cart_grouped_by_page(session) -> list[dict]:
    """
    Load all cart items + calendar entries for the current identity,
    grouped by market_place (one card per market).

    Returns a list of dicts:
        {
            "post": Post | None,
            "page_config": PageConfig | None,
            "market_place": MarketPlace | None,
            "cart_items": [...],
            "calendar_entries": [...],
            "product_count": int,
            "product_total": float,
            "calendar_count": int,
            "calendar_total": float,
            "total": float,
        }

    Calendar entries (no market concept) attach to a page-level group.
    Items without a market_place go in an orphan bucket (post=None).
    """
    from .get_cart import get_cart
    from .calendar_cart import get_calendar_cart_entries, get_ticket_cart_entries
    from .total import total as calc_product_total
    from .calendar_cart import calendar_total as calc_calendar_total, ticket_total as calc_ticket_total

    cart_items = await get_cart(session)
    cal_entries = await get_calendar_cart_entries(session)
    all_tickets = await get_ticket_cart_entries(session)

    # Group cart items by market_place_id
    market_groups: dict[int | None, dict] = {}
    for ci in cart_items:
        mp_id = ci.market_place_id if ci.market_place else None
        if mp_id not in market_groups:
            market_groups[mp_id] = {
                "market_place": ci.market_place,
                "post_id": ci.market_place.container_id if ci.market_place else None,
                "cart_items": [],
                "calendar_entries": [],
                "tickets": [],
            }
        market_groups[mp_id]["cart_items"].append(ci)

    # Attach calendar entries to an existing market group for the same page,
    # or create a page-level group if no market group exists for that page.
    page_to_market: dict[int | None, int | None] = {}
    for mp_id, grp in market_groups.items():
        pid = grp["post_id"]
        if pid is not None and pid not in page_to_market:
            page_to_market[pid] = mp_id

    for ce in cal_entries:
        pid = ce.calendar_container_id or None
        if pid in page_to_market:
            market_groups[page_to_market[pid]]["calendar_entries"].append(ce)
        else:
            # Create a page-level group for calendar-only entries
            key = ("cal", pid)
            if key not in market_groups:
                market_groups[key] = {
                    "market_place": None,
                    "post_id": pid,
                    "cart_items": [],
                    "calendar_entries": [],
                    "tickets": [],
                }
                if pid is not None:
                    page_to_market[pid] = key
            market_groups[key]["calendar_entries"].append(ce)

    # Attach tickets to page groups (via calendar_container_id)
    for tk in all_tickets:
        pid = tk.calendar_container_id or None
        if pid in page_to_market:
            market_groups[page_to_market[pid]]["tickets"].append(tk)
        else:
            key = ("tk", pid)
            if key not in market_groups:
                market_groups[key] = {
                    "market_place": None,
                    "post_id": pid,
                    "cart_items": [],
                    "calendar_entries": [],
                    "tickets": [],
                }
                if pid is not None:
                    page_to_market[pid] = key
            market_groups[key]["tickets"].append(tk)

    # Batch-load Post DTOs and PageConfig objects
    post_ids = list({
        grp["post_id"] for grp in market_groups.values()
        if grp["post_id"] is not None
    })
    posts_by_id: dict[int, object] = {}
    configs_by_post: dict[int, object] = {}

    if post_ids:
        raw_posts = await fetch_data("blog", "posts-by-ids",
                                     params={"ids": ",".join(str(i) for i in post_ids)},
                                     required=False) or []
        for raw_p in raw_posts:
            p = dto_from_dict(PostDTO, raw_p)
            posts_by_id[p.id] = p

        raw_pcs = await fetch_data("blog", "page-configs-batch",
                                   params={"container_type": "page",
                                           "ids": ",".join(str(i) for i in post_ids)},
                                   required=False) or []
        for raw_pc in raw_pcs:
            pc = SimpleNamespace(**raw_pc)
            configs_by_post[pc.container_id] = pc

    # Build result list (markets with pages first, orphan last)
    result = []
    for _key, grp in sorted(
        market_groups.items(),
        key=lambda kv: (kv[1]["post_id"] is None, kv[1]["post_id"] or 0),
    ):
        items = grp["cart_items"]
        entries = grp["calendar_entries"]
        tks = grp["tickets"]
        prod_total = calc_product_total(items) or 0
        cal_total = calc_calendar_total(entries) or 0
        tk_total = calc_ticket_total(tks) or 0
        pid = grp["post_id"]

        result.append({
            "post": posts_by_id.get(pid) if pid else None,
            "page_config": configs_by_post.get(pid) if pid else None,
            "market_place": grp["market_place"],
            "cart_items": items,
            "calendar_entries": entries,
            "tickets": tks,
            "product_count": sum(ci.quantity for ci in items),
            "product_total": prod_total,
            "calendar_count": len(entries),
            "calendar_total": cal_total,
            "ticket_count": len(tks),
            "ticket_total": tk_total,
            "total": prod_total + cal_total + tk_total,
        })

    return result
