"""The award picker: one rule per test, each on the shape of a real product.

The first picker chose "best tier, then most recent" over every medal a product ever won.
Elijah Craig Small Batch (NYISC Double Gold 2024, Silver 2025) kept the Double Gold in its
card corner after the 2025 result came in, and a rebuild renumbered every award, so nothing
could be pinned. These tests pin the strategy Adam was given on 2026-09-05
(`notes/awards-strategy-for-adam-2026-09-05.md`).
"""

from dataclasses import dataclass
from datetime import date

import pytest

from app.services.award_picker import (
    AwardKey,
    is_faded,
    pick,
    pin_from_override,
    rank_key,
    retained_awards,
)


@dataclass(frozen=True)
class A:
    competition_slug: str
    year: int
    medal: str
    score: int | None = None
    competition: str = ""

    def __post_init__(self):
        names = {
            "nyisc": "New York International Spirits Competition",
            "nyiwc": "New York International Wine Competition",
            "bisc": "Berlin International Spirits Competition",
            "biwc": "Berlin International Wine Competition",
            "misc": "Melbourne International Spirits Competition",
            "miwc": "Melbourne International Wine Competition",
            "aisc": "Asia International Spirits Competition",
            "aiwc": "Asia International Wine Competition",
        }
        if not self.competition:
            name = names.get(self.competition_slug, self.competition_slug)
            object.__setattr__(self, "competition", name)


# Santa Teresa 1796 Solera Rum 1L as held on 2026-09-05: eight medals, three competitions.
SANTA_TERESA = [
    A("bisc", 2020, "Silver", 92),
    A("bisc", 2021, "Gold", 94),
    A("bisc", 2022, "Silver", 92),
    A("bisc", 2023, "Silver", 92),
    A("bisc", 2024, "Gold", 95),
    A("nyisc", 2024, "Gold", 94),
    A("nyisc", 2025, "Gold", 95),
    A("aisc", 2019, "Silver", 92),
]


def keys(awards):
    return [(a.competition_slug, a.year, a.medal) for a in awards]


class TestRetained:
    def test_latest_result_per_competition(self):
        retained = retained_awards(SANTA_TERESA)
        assert sorted(keys(retained)) == sorted(
            [("bisc", 2024, "Gold"), ("nyisc", 2025, "Gold"), ("aisc", 2019, "Silver")]
        )

    def test_an_older_better_medal_steps_back(self):
        """Elijah Craig: the 2024 Double Gold is not shown once the 2025 Silver exists.
        This is the behaviour the old picker got wrong."""
        awards = [A("nyisc", 2024, "Double Gold", 96), A("nyisc", 2025, "Silver", 93)]
        sel = pick(awards)
        assert keys(sel.retained) == [("nyisc", 2025, "Silver")]
        assert sel.featured is awards[1]

    def test_every_retained_award_is_in_the_shown_list(self):
        """The structured data emits `retained`; one per competition, none lost."""
        sel = pick(SANTA_TERESA)
        assert len(sel.retained) == 3
        assert sel.featured is sel.retained[0]

    def test_a_row_without_a_slug_groups_by_competition_name(self):
        awards = [
            A("", 2023, "Gold", competition="Retailer's Choice"),
            A("", 2024, "Silver", competition="Retailer's Choice"),
        ]
        assert keys(retained_awards(awards)) == [("", 2024, "Silver")]


class TestFeaturedOrder:
    def test_highest_level_leads(self):
        awards = [A("bisc", 2025, "Silver", 93), A("nyisc", 2025, "Double Gold", 96)]
        assert pick(awards).featured is awards[1]

    def test_tie_on_level_goes_to_the_more_recent_year(self):
        sel = pick(SANTA_TERESA)
        assert (sel.featured.competition_slug, sel.featured.year) == ("nyisc", 2025)

    def test_recency_beats_airport_affinity(self):
        awards = [A("nyisc", 2025, "Gold", 95), A("aisc", 2024, "Gold", 96)]
        assert pick(awards, visitor_airports=["HKG"]).featured is awards[0]

    def test_tie_on_level_and_year_goes_to_the_visitors_airports(self):
        berlin, new_york = A("bisc", 2024, "Gold", 95), A("nyisc", 2024, "Gold", 95)
        assert pick([berlin, new_york], visitor_airports=["JFK"]).featured is new_york
        assert pick([berlin, new_york], visitor_airports=["BER"]).featured is berlin
        assert pick([berlin, new_york], visitor_airports=["lhr", "jfk"]).featured is new_york

    def test_asia_medal_for_hong_kong_singapore_seoul(self):
        asia, new_york = A("aisc", 2024, "Gold", 94), A("nyisc", 2024, "Gold", 94)
        for code in ("HKG", "SIN", "ICN"):
            assert pick([asia, new_york], visitor_airports=[code]).featured is asia

    def test_affinity_then_score(self):
        """Same level and year, visitor through JFK: the New York medal wins even with the
        lower judges' score; with no airports the higher score wins."""
        berlin, new_york = A("bisc", 2024, "Gold", 96), A("nyisc", 2024, "Gold", 94)
        assert pick([berlin, new_york], visitor_airports=["JFK"]).featured is new_york
        assert pick([berlin, new_york]).featured is berlin

    def test_full_tie_is_alphabetical_by_competition_and_stable(self):
        """No airports, same level, year and score: Berlin before New York, whatever the
        input order, so the same bottle always shows the same medal."""
        berlin, new_york = A("bisc", 2024, "Gold", 94), A("nyisc", 2024, "Gold", 94)
        assert pick([new_york, berlin]).featured is berlin
        assert pick([berlin, new_york]).featured is berlin

    def test_an_airport_no_competition_calls_home_changes_nothing(self):
        berlin, new_york = A("bisc", 2024, "Gold", 94), A("nyisc", 2024, "Gold", 94)
        assert pick([new_york, berlin], visitor_airports=["LHR", "EZE"]).featured is berlin

    def test_unknown_medal_words_rank_after_bronze(self):
        awards = [
            A("", 2025, "Best in Show", competition="Retailer"),
            A("nyisc", 2024, "Bronze", 86),
        ]
        assert pick(awards).featured is awards[1]
        assert rank_key(awards[0])[0] > rank_key(awards[1])[0]

    def test_no_awards(self):
        sel = pick([])
        assert sel.featured is None and sel.retained == [] and not sel.pin_stale


class TestPin:
    def test_a_pin_on_a_retained_medal_is_featured(self):
        sel = pick(SANTA_TERESA, pin=AwardKey("bisc", 2024, "Gold"))
        assert sel.pin_applied and not sel.pin_stale
        assert (sel.featured.competition_slug, sel.featured.year) == ("bisc", 2024)
        assert sel.retained[0] is sel.featured and len(sel.retained) == 3

    def test_a_pin_on_a_hidden_older_medal_shows_it_alongside_the_newer_result(self):
        awards = [A("nyisc", 2024, "Double Gold", 96), A("nyisc", 2025, "Silver", 93)]
        sel = pick(awards, pin=AwardKey("nyisc", 2024, "Double Gold"))
        assert sel.featured is awards[0]
        assert keys(sel.retained) == [("nyisc", 2024, "Double Gold"), ("nyisc", 2025, "Silver")]

    def test_a_stale_pin_falls_back_to_the_picker_and_says_so(self):
        """The natural key survives a rebuild; a pin that matches nothing (the medal was
        corrected, or the product merged) must never blank the corner."""
        sel = pick(SANTA_TERESA, pin=AwardKey("bisc", 2023, "Gold"))
        assert sel.pin_stale and not sel.pin_applied
        assert (sel.featured.competition_slug, sel.featured.year) == ("nyisc", 2025)
        assert sel.retained == pick(SANTA_TERESA).retained

    def test_pin_matching_is_case_insensitive_on_medal_and_slug(self):
        sel = pick(SANTA_TERESA, pin=AwardKey("NYISC", 2025, "gold"))
        assert sel.pin_applied

    def test_a_pin_beats_aging_on_a_listing(self):
        awards = [A("nyisc", 2023, "Double Gold", 96), A("bisc", 2025, "Bronze", 86)]
        sel = pick(awards, pin=AwardKey("nyisc", 2023, "Double Gold"), listing=True,
                   aging=True, today=date(2026, 9, 5))
        assert sel.featured is awards[0]

    def test_pin_on_a_product_with_no_awards_is_stale(self):
        assert pick([], pin=AwardKey("nyisc", 2025, "Gold")).pin_stale


class TestAging:
    TODAY = date(2026, 9, 5)

    def test_older_than_a_year_counts_in_judging_years(self):
        assert is_faded(A("nyisc", 2024, "Gold"), self.TODAY)
        assert not is_faded(A("nyisc", 2025, "Gold"), self.TODAY)
        assert not is_faded(A("nyisc", 2026, "Gold"), self.TODAY)

    def test_off_by_default(self):
        """Adam's switch; the module ships with it off."""
        awards = [A("aisc", 2019, "Double Gold", 96), A("nyisc", 2025, "Gold", 95)]
        assert pick(awards, listing=True, today=self.TODAY).featured is awards[0]

    def test_on_a_listing_a_faded_medal_does_not_take_the_corner(self):
        awards = [A("aisc", 2019, "Double Gold", 96), A("nyisc", 2025, "Gold", 95)]
        sel = pick(awards, listing=True, aging=True, today=self.TODAY)
        assert sel.featured is awards[1]
        # The page and the structured data still carry it.
        assert len(sel.retained) == 2

    def test_the_product_page_ignores_aging(self):
        awards = [A("aisc", 2019, "Double Gold", 96), A("nyisc", 2025, "Gold", 95)]
        assert pick(awards, listing=False, aging=True, today=self.TODAY).featured is awards[0]

    def test_all_faded_means_no_corner_medal_but_a_full_page(self):
        awards = [A("aisc", 2019, "Silver", 92), A("bisc", 2021, "Gold", 94)]
        sel = pick(awards, listing=True, aging=True, today=self.TODAY)
        assert sel.featured is None
        assert len(sel.retained) == 2


class TestPinFromOverride:
    @pytest.mark.parametrize(
        ("value", "expected"),
        [
            (
                {"competition_slug": "nyisc", "year": 2025, "medal": "Gold"},
                AwardKey("nyisc", 2025, "Gold"),
            ),
            (
                {"competition_slug": "NYISC", "year": "2025", "medal": "Gold"},
                AwardKey("nyisc", 2025, "Gold"),
            ),
            (
                {"competition_slug": "nyisc", "year": None, "medal": None},
                AwardKey("nyisc", None, None),
            ),
            ({"competition_slug": "", "year": 2025, "medal": "Gold"}, None),
            ({"year": 2025, "medal": "Gold"}, None),
            ({"competition_slug": "nyisc", "year": "twenty", "medal": "Gold"}, None),
            ("nyisc-2025-gold", None),
            (None, None),
        ],
    )
    def test_shapes(self, value, expected):
        assert pin_from_override(value) == expected
