"""Storefront product → license tier mapping.

The mapping lives in ``server/config/products.yaml`` (gitignored
for secrets it isn't — it's a routine catalog file) so adding a
new SKU is one yaml edit plus a container restart. The lookup is
``(source, product_id) -> (tier, years)``.

Cached at module import. The runtime cost of reloading on every
webhook would be trivial, but caching keeps the hot path
allocation-free and makes the "edit yaml, restart api" idiom
explicit — operators always know exactly when their changes go
live.
"""

from __future__ import annotations

from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from typing import Optional

import yaml


@dataclass(frozen=True)
class ProductMapping:
    tier: str
    years: int


def _config_path() -> Path:
    """Resolve the products config.

    Container layout puts the config at ``/app/config/products.yaml``
    (the Dockerfile COPYs ``server/config`` to ``/app/config``).
    For local pytest runs we walk up from this file to ``server/``.
    """
    in_container = Path("/app/config/products.yaml")
    if in_container.exists():
        return in_container
    return Path(__file__).resolve().parent.parent / "config" / "products.yaml"


@lru_cache(maxsize=1)
def _table() -> dict[tuple[str, str], ProductMapping]:
    raw = yaml.safe_load(_config_path().read_text(encoding="utf-8")) or {}
    table: dict[tuple[str, str], ProductMapping] = {}
    for source, entries in raw.items():
        for entry in entries or []:
            key = (source, str(entry["product_id"]))
            table[key] = ProductMapping(
                tier=entry["tier"],
                years=int(entry.get("years", 1)),
            )
    return table


def lookup(source: str, product_id: str) -> Optional[ProductMapping]:
    """Return the mapping for *(source, product_id)*, or None if unmapped.

    Returning None (rather than raising) lets the webhook layer
    decide whether to surface the failure as an audit row vs a
    user-visible error — we want unmapped sales to be logged, not
    to crash the handler and trigger Gumroad retry storms.
    """
    return _table().get((source, product_id))


def reload_for_tests() -> None:
    """Drop the cache. Tests that mutate the yaml call this."""
    _table.cache_clear()
