"""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 order is `featured.py`'s v3, unchanged and run once per
family (Stream AW2): comparable only (a bottle this airport is the cheapest of our airports for,
sold at two or more), the saving's standing, the picture tier, an award on a real saving, the
pins, the two champions kept, the freshness and stock gates, 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`, which carry
the featured columns of `_summary_base`), so the page pays one awards lookup, one pictures
lookup and one summaries fetch for the whole block.
"""

from dataclasses import dataclass
from datetime import UTC, datetime

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 = featured.REAL_SAVING_USD


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


@dataclass(frozen=True, slots=True)
class Candidate:
    """What the order needs from one of the airport's rows: the v2 fields, and v3's read with
    a default where a row does not carry the column (hand-seeded rows in the tests)."""

    variant_id: int
    category: str | None
    saving_usd: float
    saving_pct: float
    has_image: bool
    shop_count: int
    picture_tier: int = 0
    product_line_id: int | None = None
    brand_id: int | None = None
    is_exclusive: bool = False
    pinned: bool = False
    line_pinned: bool = False
    age_days: int | None = 0
    oldest_age_days: int | None = 0
    in_stock: bool = True
    sized: bool = True
    name: str = ""

    def record(self, awarded: bool) -> featured.SavingRecord:
        return featured.SavingRecord(
            variant_id=self.variant_id, saving_usd=self.saving_usd, saving_pct=self.saving_pct, awarded=awarded,
            has_image=self.has_image, shop_count=self.shop_count, picture_tier=self.picture_tier,
            product_line_id=self.product_line_id, brand_id=self.brand_id, category=self.category,
            is_exclusive=self.is_exclusive, pinned=self.pinned, line_pinned=self.line_pinned, age_days=self.age_days,
            oldest_age_days=self.oldest_age_days, in_stock=self.in_stock, sized=self.sized, name=self.name,
        )


def _days_since(when, now: datetime) -> int | None:
    if when is None:
        return None
    if when.tzinfo is None:
        when = when.replace(tzinfo=UTC)
    return max(0, (now - when).days)


def candidates(rows, tiers: dict[int, int] | None = None, now: datetime | None = None) -> 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.
    `tiers` is the picture tier per variant id (`pictures_of`); absent, a set `thumb_url` reads
    as v2's photo. A row without the featured columns reads as fresh, in stock and sized."""
    now = now or datetime.now(UTC)
    tiers = tiers or {}
    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
        has_columns = hasattr(r, "newest_observed_at")
        out.append(Candidate(
            variant_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),
            picture_tier=int(tiers.get(r.id, 0)),
            product_line_id=getattr(r, "product_line_id", None), brand_id=getattr(r, "brand_id", None),
            is_exclusive=bool(getattr(r, "is_exclusive", False)),
            pinned=bool(getattr(r, "pinned", False)), line_pinned=bool(getattr(r, "line_pinned", False)),
            age_days=_days_since(getattr(r, "newest_observed_at", None), now) if has_columns else 0,
            oldest_age_days=_days_since(getattr(r, "oldest_observed_at", None), now) if has_columns else 0,
            in_stock=(getattr(r, "cheapest_available", None) is not None) if has_columns else True,
            sized=(getattr(r, "quantity_value", None) is not None) if has_columns else True,
            name=getattr(r, "name", None) or "",
        ))
    return out


def pick_by_family(
    rows, awarded_ids: set[int], per_family: int = PER_FAMILY, tiers: dict[int, int] | None = None,
    now: datetime | None = None,
) -> list[tuple[str, str, list[int]]]:
    """`(key, label, product ids)` per family that has a comparison here, in family order,
    the ids in the v3 order. A family with nothing comparable is absent, never padded."""
    by_family: dict[str, list[featured.SavingRecord]] = {}
    for c in candidates(rows, tiers, now):
        key = family_of(c.category)
        if key is None:
            continue
        by_family.setdefault(key, []).append(c.record(c.variant_id in awarded_ids))
    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, variant_ids: list[int]) -> set[int]:
    if not variant_ids:
        return set()
    return set(db.scalars(select(Award.variant_id).where(Award.variant_id.in_(variant_ids)).distinct()))


def tiers_of(db: Session, rows) -> dict[int, int]:
    """The picture tier per row through the one cascade (`catalog_queries.pictures_of`), two
    queries for the whole block; a session without `scalars` (the tests' stub) gives none."""
    from app.services.catalog_queries import pictures_of

    if not hasattr(db, "scalars"):
        return {}
    return {vid: (picture.tier if picture is not None else 0) for vid, picture in pictures_of(db, rows).items()}


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.variant_id for c in candidates(rows)]
    picked = pick_by_family(rows, awarded_among(db, ids), per_family, tiers=tiers_of(db, rows))
    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)
    ]
