"""Featured at this airport, by family: the best-value comparables here, one row per family.

Mark's "Featured in London" block (the structure proposal's airport page): the best comparisons
at this airport, split by family. The picker is `featured.py`'s, unchanged and run once per
family: comparable only (a bottle this airport is the cheapest of our airports for, sold at two
or more), the saving score, an award and a photo as the tie-breaks, the two champions kept, the
uncorroborated-spread guard. A family with no comparable product here yields no row rather than
a guessed one; a hidden category is never a family here because the rows never carry one.

Pure over the rows the airport page already has (`catalog_queries._airport_rows`), so the
page pays one awards lookup and one summaries fetch for the whole block.
"""

from dataclasses import dataclass

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models import Award
from app.models.hubs import FeaturedFamily
from app.services import featured

#: The families a shopper thinks in, finer than taxonomy's verticals: key, label, categories.
FEATURED_FAMILIES: tuple[tuple[str, str, tuple[str, ...]], ...] = (
    ("spirits", "Spirits", ("Whisky", "Cognac & Brandy", "Tequila & Mezcal", "Rum", "Gin", "Vodka", "Liqueurs & Aperitifs")),
    ("wine", "Wine, champagne and beer", ("Wine", "Champagne & Sparkling", "Beer & Cider")),
    ("perfume", "Perfume", ("Perfume",)),
    ("skincare", "Skincare and makeup", ("Skincare", "Makeup")),
    ("confectionery", "Confectionery", ("Confectionery",)),
    ("tobacco", "Tobacco", ("Tobacco",)),
)
_FAMILY_OF_CATEGORY = {c: key for key, _, cats in FEATURED_FAMILIES for c in cats}
#: Cards per family row: one row of the grid, so the block stays as tall as its families.
PER_FAMILY = 4
#: The same floor the "cheapest here" shelf uses: under this the saving is rounding.
MIN_SAVING_USD = 0.5


def family_of(category: str | None) -> str | None:
    return _FAMILY_OF_CATEGORY.get(category or "")


@dataclass(frozen=True, slots=True)
class Candidate:
    """What the picker needs from one of the airport's rows."""

    product_id: int
    category: str | None
    saving_usd: float
    saving_pct: float
    has_image: bool
    shop_count: int


def candidates(rows) -> list[Candidate]:
    """The airport's rows that are a comparison it wins: sold at two or more of our airports,
    this airport's latest price the lowest we know, the gap against the dearest worth showing."""
    out: list[Candidate] = []
    for r in rows:
        if r.airport_count < 2 or r.here_usd is None or r.cheapest_usd is None:
            continue
        here, cheapest = float(r.here_usd), float(r.cheapest_usd)
        if here > cheapest + 0.005:
            continue
        dearest = float(r.dearest_usd or here)
        saving = dearest - here
        if saving <= MIN_SAVING_USD:
            continue
        out.append(Candidate(
            product_id=r.id, category=r.category, saving_usd=saving,
            saving_pct=saving / dearest if dearest else 0.0,
            has_image=bool(getattr(r, "thumb_url", None)), shop_count=int(r.airport_count),
        ))
    return out


def pick_by_family(
    rows, awarded_ids: set[int], per_family: int = PER_FAMILY
) -> list[tuple[str, str, list[int]]]:
    """`(key, label, product ids)` per family that has a comparison here, in family order,
    the ids in the picker's order. A family with nothing comparable is absent, never padded."""
    by_family: dict[str, list[featured.SavingRecord]] = {}
    for c in candidates(rows):
        key = family_of(c.category)
        if key is None:
            continue
        by_family.setdefault(key, []).append(featured.SavingRecord(
            product_id=c.product_id, saving_usd=c.saving_usd, saving_pct=c.saving_pct,
            awarded=c.product_id in awarded_ids, has_image=c.has_image, shop_count=c.shop_count,
        ))
    out: list[tuple[str, str, list[int]]] = []
    for key, label, _cats in FEATURED_FAMILIES:
        records = by_family.get(key)
        if not records:
            continue
        ids = featured.pick_featured(records, total=per_family)
        if ids:
            out.append((key, label, ids))
    return out


def awarded_among(db: Session, product_ids: list[int]) -> set[int]:
    if not product_ids:
        return set()
    return set(db.scalars(select(Award.product_id).where(Award.product_id.in_(product_ids)).distinct()))


def featured_at(db: Session, rows, fetch_summaries, per_family: int = PER_FAMILY) -> list[FeaturedFamily]:
    """The block for one airport: pick per family, then one summaries fetch for every card.
    `fetch_summaries(ids)` returns the ProductSummary rows for those ids in any order."""
    ids = [c.product_id for c in candidates(rows)]
    picked = pick_by_family(rows, awarded_among(db, ids), per_family)
    wanted = [pid for _, _, family_ids in picked for pid in family_ids]
    if not wanted:
        return []
    by_id = {item.id: item for item in fetch_summaries(wanted)}
    return [
        FeaturedFamily(key=key, label=label, items=[by_id[pid] for pid in family_ids if pid in by_id])
        for key, label, family_ids in picked
        if any(pid in by_id for pid in family_ids)
    ]
