"""Medal matching precision.

A medal on the wrong bottle is worse than a medal missing. An earlier matcher
pooled brand and product words together and put one row in three on the wrong
bottle; every case here is one of those real mismatches.
"""

import pytest

from app.services.awards_import import _GENERIC_BRAND, _score_candidate, award_tokens

MIN = 0.75


def score(winner: str, brand: str, product: str) -> float:
    """Score a competition entry against a retail product, as the importer does."""
    brand_tokens = award_tokens(brand)
    return _score_candidate(
        award_tokens(winner), brand_tokens, brand_tokens - _GENERIC_BRAND, award_tokens(product)
    )


class TestRejectsWrongBottles:
    def test_varietal_words_alone_never_carry_a_medal(self):
        """Wine entries are named 'Brand - Varietal'. Sharing 'pinot noir' with the
        brand entirely absent used to score 0.75 and win."""
        assert score("Lost Farm Pinot Noir", "Lost Farm", "Chard Farm Finla Mor Pinot Noir") == 0
        assert score("Kooyonga Creek Shiraz", "Kooyonga Creek", "Jacob's Creek Shiraz") == 0

    def test_generic_producer_words_are_not_a_brand_match(self):
        """'Creek', 'Estate', 'Bay' are too common to prove identity on their own."""
        assert score(
            "Forty Creek Double Barrel Reserve", "Forty Creek", "Jacob's Creek Double Barrel Shiraz"
        ) == 0

    def test_a_one_word_entry_must_match_the_whole_product(self):
        """'Tiger Gin' tokenises to {tiger, gin}; against a beer it must fail. An
        earlier floor let single-token entries match any product of that brand,
        which put eleven gin medals on a 24-pack of lager."""
        assert score("Tiger Gin", "Tiger Gin", "Tiger Beer 24x330ml") == 0

    @pytest.mark.parametrize(
        ("winner", "brand", "product"),
        [
            # Expression words distinguish siblings in a range.
            ("Captain Morgan Original Spiced", "Captain Morgan", "Captain Morgan Black Spiced Rum"),
            ("Bumbu Original", "Bumbu", "Bumbu Rum XO 40% 0.7L"),
            ("Glenmorangie Original", "Glenmorangie", "Glenmorangie Signet 70cl"),
            ("Proper No. Twelve Apple", "Proper No. Twelve", "Proper No. Twelve Irish Whiskey 1L"),
        ],
    )
    def test_contrasting_expressions_do_not_match(self, winner, brand, product):
        assert score(winner, brand, product) < MIN

    def test_age_statements_are_a_veto(self):
        """An 8-year medal on the 12-year bottle is a different product."""
        assert score(
            "Appleton Estate Reserve 8 Year Old", "Appleton Estate", "Appleton Estate 12 Year Old"
        ) == 0

    def test_a_vintage_on_one_side_only_is_a_veto(self):
        assert score("Glenmorangie Original", "Glenmorangie", "Glenmorangie Original 10 Years") == 0


class TestAcceptsRightBottles:
    @pytest.mark.parametrize(
        ("winner", "brand", "product"),
        [
            ("Johnnie Walker Blue Label", "Johnnie Walker",
             "Johnnie Walker Blue Label Blended Scotch Whisky 1L"),
            # A number both sides share is fine -- it is a mismatch that vetoes.
            ("Santa Teresa 1796", "Santa Teresa", "Santa Teresa 1796 Solera Rum 1L"),
            ("Koskenkorva Vodka", "Koskenkorva", "Koskenkorva Vodka 1L"),
            ("Tiger Gin", "Tiger Gin", "Tiger Gin 70cl"),
            # Retailers abbreviate and add category words; extras on one side only
            # must not block an otherwise clean match.
            ("Glenmorangie The Original", "Glenmorangie", "Glenmorangie The Original 70cl"),
        ],
    )
    def test_matches(self, winner, brand, product):
        assert score(winner, brand, product) >= MIN


class TestTokenising:
    def test_keeps_expression_words_the_catalogue_drops(self):
        """The catalogue-wide stopword list deletes 'original' and 'reserve'.
        For medals those words ARE the identity, so this tokeniser keeps them."""
        tokens = award_tokens("Glenmorangie The Original Reserve")
        assert "original" in tokens and "reserve" in tokens

    def test_keeps_single_digit_ages(self):
        assert "8" in award_tokens("Appleton Estate 8 Year Old")

    def test_normalises_whisky_spelling(self):
        assert award_tokens("Irish Whiskey") == award_tokens("Irish Whisky")

    def test_drops_size_and_strength(self):
        tokens = award_tokens("Bumbu Rum 40% 0.7L")
        assert not {"40", "0", "7"} & tokens


class TestBrandGate:
    """The 2026-09-05 review of the live rows: one shared brand word, often the category
    word an entrant writes into the brand field, satisfied the gate and put medals on
    the wrong producer's bottle. Every case here is a row that was live."""

    @pytest.mark.parametrize(
        ("winner", "brand", "product"),
        [
            ("Le Grand Courtage - Brut Rosé", "Le Grand Courtage", "Jeeper Grand Rose Brut 75cl"),
            ("Champagne Telmont - Reserve Rosé", "Champagne Telmont",
             "Charles Heidsieck Rose Reserve Champagne 75cl"),
            ("Victor Hugo - Cabernet Sauvignon", "Victor Hugo",
             "Jacob's Creek St. Hugo Cabernet Sauvignon 750ml"),
            ("Highland Green Blended Malt Scotch Whisky", "Highland Green",
             "Johnnie Walker Island Green Blended Malt Scotch Whisky 1L"),
        ],
    )
    def test_one_brand_word_out_of_two_is_not_a_match(self, winner, brand, product):
        assert score(winner, brand, product) == 0

    @pytest.mark.parametrize(
        ("winner", "brand", "product"),
        [
            ("Perfetto Blood Orange Gin", "Perfetto Gin",
             "Beefeater Blood Orange London Dry Gin 1l"),
            ("Zaya Rum Gran Reserva", "Zaya Rum", "Bacardi Gran Reserva Diez Rum 1l"),
            ("Mosgaard Whisky Oloroso Cask Single Malt Whisky", "Mosgaard Whisky",
             "Tamnavulin Oloroso Sherry Cask Single Malt Scotch Whisky 1L"),
        ],
    )
    def test_category_words_in_a_brand_are_not_brand_evidence(self, winner, brand, product):
        assert score(winner, brand, product) == 0

    def test_a_brand_of_only_generic_words_matches_nothing(self):
        """'J Vineyards' is 'vineyards' once the single letter is dropped; its Double Gold
        sat on a Wente Vineyards Chardonnay."""
        assert score("J Vineyards - Chardonnay", "J Vineyards",
                     "Wente Vineyards Riva Ranch Chardonnay 750ml") == 0

    @pytest.mark.parametrize(
        ("winner", "brand", "product"),
        [
            ("Casa Cinzano Cinzano Vermouth Rosso", "Casa Cinzano", "Cinzano Vermouth Rosso 1L"),
            ("Bodegas Campo Viejo - Gran Reserva", "Bodegas Campo Viejo",
             "Campo Viejo Gran Reserva 750ml"),
            ("Ron Santa Teresa Santa Teresa 1796", "Ron Santa Teresa",
             "Santa Teresa 1796 Solera Rum 1L"),
            ("West Cork Distillers Bourbon Cask", "West Cork Distillers",
             "West Cork Bourbon Cask Irish Whiskey 70cl"),
            ("Forty Creek Canadian Whisky: Barrel Select Whisky", "Forty Creek Canadian Whisky",
             "Forty Creek Barrel Select Whisky 1l"),
        ],
    )
    def test_producer_type_words_still_let_the_real_brand_through(self, winner, brand, product):
        assert score(winner, brand, product) >= MIN


class TestAgeSuffix:
    def test_an_age_glued_to_its_suffix_is_still_a_number(self):
        """'Highland Park 18yr old' in the brand field never met 'Highland Park 18 Year Old'
        because '18yr' was one word."""
        assert score("Highland Park 18yr old Highland Park 18 Year Old", "Highland Park 18yr old",
                     "Highland Park 18 Year Old Viking Pride Travel Edition 700ml") >= MIN

    def test_a_suffixed_age_on_one_side_only_is_a_veto(self):
        """The ageless entry 'Eminente Reserva' sat on the 7-year bottle; the same rule that
        keeps an 8-year medal off the 12-year bottle applies once the 7 is visible."""
        assert score("Eminente Reserva", "Eminente", "Emin. Reserva 7yo 41.3% 0.7L") == 0
        assert "7" in award_tokens("Emin. Reserva 7yo 41.3% 0.7L")


class TestExpressionMarkerVeto:
    """A shorter entry on a longer variant (running list, 5 Sep; rows live in 0.33.0):
    one-sided extras were always allowed, so BACARDÍ Reserva Ocho's NYISC Silver sat on the
    Rye Cask Finish because the plain Ocho was not in the catalogue, and an Angel's Envy
    Cask Strength Double Gold could land on the standard port-finish bottle. A word that
    marks an expression now vetoes from either side; category and size words still do not."""

    @pytest.mark.parametrize(
        ("winner", "brand", "product"),
        [
            ("BACARDÍ Reserva Ocho", "BACARDI", "Bacardi Reserva Ocho Rye Cask Finish Rum 1l"),
            ("Angel’s Envy Cask Strength Kentucky Straight Bourbon Whiskey Finished in Port Wine Barrels",
             "Angel's Envy",
             "Angel’s Envy Kentucky straight bourbon whiskey finished in port wine barrels 750 ml"),
            ("Johnnie Walker", "Johnnie Walker", "Johnnie Walker Blue Label Blended Scotch Whisky 1L"),
            ("Bulleit Bourbon", "Bulleit", "Bulleit Rye Frontier Whiskey 1L"),
        ],
    )
    def test_a_one_sided_expression_marker_is_a_veto(self, winner, brand, product):
        assert score(winner, brand, product) == 0

    @pytest.mark.parametrize(
        ("winner", "brand", "product"),
        [
            # The same marker on both sides is the same bottle.
            ("Angel’s Envy Kentucky Straight Bourbon Finished in Port Wine Barrels", "Angel's Envy",
             "Angel’s Envy Kentucky straight bourbon whiskey finished in port wine barrels 750 ml"),
            ("Johnnie Walker Blue Label", "Johnnie Walker", "Johnnie Walker Blue Label Blended Scotch Whisky 1L"),
            # Category and size words are still free on either side.
            ("BACARDÍ Reserva Ocho", "BACARDI", "Bacardi Reserva Ocho Rum 1L"),
            ("Bulleit Bourbon", "Bulleit", "Bulleit Bourbon Frontier Whiskey 1L"),
            # Duty-free packaging words are not an expression.
            ("Highland Park 18 Year Old", "Highland Park",
             "Highland Park 18 Year Old Viking Pride Travel Edition 700ml"),
        ],
    )
    def test_shared_markers_and_category_words_still_match(self, winner, brand, product):
        assert score(winner, brand, product) >= MIN


class TestApostrophes:
    def test_a_typographic_apostrophe_reads_like_a_straight_one(self):
        """The winners file writes "Angel’s Envy" with a curly apostrophe and the brand
        field "Angel's Envy" with a straight one; the curly one was dropped as non-ASCII,
        so the tokens were "angels" against "angel" and the brand gate refused every
        Angel's Envy entry, medals and all, until 10 Sep."""
        assert award_tokens("Angel’s Envy") == award_tokens("Angel's Envy") == {"angel", "envy"}
