"""The line key, the variation vocabulary and the form, pinned on the rows that decided them.

Rian's decision of 12 Sep (one page per real product) turned the 1 Million diagnosis into
a rule set: the fourteen 1 Million rows (ids 7761 to 7805, 14747, 14766, 14767, 17216 to
17219, 17314, 17885 on the 11 Sep staging copy) must land as one house, one line, three
variations, and the set kept apart. Every other case here is a real spelling from the same
copy that a looser rule joined wrongly or a stricter one split.
"""

import pytest

from app.services.lines import (
    brand_words_of,
    canonical_variation,
    display_line_name,
    display_variation,
    form_of,
    line_key,
    line_slug,
    variation_of,
)

# (brand as collected, name as collected, size_ml) for the fourteen 1 Million rows.
ONE_MILLION = [
    ("Paco Rabanne", "1 Million Parfum 5cl", 50),
    ("Paco Rabanne", "1 Million Parfum 10cl", 100),
    ("Paco Rabanne", "1 Million 5cl", 50),
    ("Paco Rabanne", "1 Million 10cl", 100),
    ("Paco Rabanne", "1 Million 20cl", 200),
    ("Rabanne", "Rabanne 1 Million EDT 100ml", 100),
    ("Rabanne", "Rabanne 1 Million Elixir Parfum Intense 50ml", 50),
    ("Rabanne", "Rabanne 1 Million Elixir Parfum Intense 100ml", 100),
    ("Rabanne", "1 Million Elixir Parfum Intense 200 ml", 200),
    ("Rabanne", "1 Million Eau de Toilette 100 ml", 100),
    ("Rabanne", "1 Million Eau de Toilette 200 ml", 200),
    ("Rabanne", "1 Million Eau de Toilette 50 ml", 50),
    ("Rabanne", "1 Million Elixir Eau de Parfum Intense 200 ml", 200),
    ("Rabanne", "Rabanne Set: Duo 1 Million Eau de Toilette 50 ml 100 ml", 50),
]


class TestOneMillion:
    def test_one_line_under_one_house(self):
        keys = {line_key(name, brand=brand, house="Rabanne", vertical="beauty") for brand, name, _ in ONE_MILLION}
        assert keys == {"1 million"}

    def test_three_variations_and_the_rows_that_say_nothing(self):
        canon = [variation_of(name, "beauty")[1] for _, name, _ in ONE_MILLION]
        assert set(canon) == {"parfum", "", "edt", "elixir"}
        assert canon[:5] == ["parfum", "parfum", "", "", ""]
        assert canon[5] == "edt" and canon[6:9] == ["elixir"] * 3 and canon[9:12] == ["edt"] * 3
        assert canon[12] == "elixir", "Elixir Eau de Parfum Intense is the Elixir, not an EDP"

    def test_the_raw_wording_is_kept_beside_the_canonical(self):
        assert variation_of("1 Million Elixir Parfum Intense 200 ml", "beauty") == ("elixir parfum intense", "elixir")
        assert variation_of("1 Million Elixir Eau de Parfum Intense 200 ml", "beauty") == ("elixir edp intense", "elixir")

    def test_the_set_is_kept_apart(self):
        forms = [form_of(name) for _, name, _ in ONE_MILLION]
        assert forms[-1] == "set" and set(forms[:-1]) == {""}


class TestBeautyLines:
    def test_audience_is_part_of_the_line_and_its_spellings_agree(self):
        men = line_key("Eternity For Men Eau de Toilette 100 ml", brand="Calvin Klein", vertical="beauty")
        assert men == "eternity men"
        assert line_key("Calvin Klein Eternity Men EDT 100ml", brand="Calvin Klein", vertical="beauty") == men
        assert line_key("Eternity pour Homme", brand="Calvin Klein", vertical="beauty") == men
        assert line_key("Light Blue Capri In Love Male", brand="Dolce & Gabbana", vertical="beauty") == \
            line_key("Light Blue Capri In Love Pour Homme", brand="Dolce & Gabbana", vertical="beauty")
        assert line_key("Eternity for Her Eau de Parfum 100 ml", brand="Calvin Klein", vertical="beauty") == "eternity women"
        assert line_key("Calvin Klein Eternity Women EDP 100ml", brand="Calvin Klein", vertical="beauty") == "eternity women"

    def test_a_bare_number_is_the_line_not_a_size(self):
        assert line_key("Cheirosa '62 Body Mist 90 ml", brand="Sol de Janeiro", vertical="beauty") == "cheirosa 62"
        assert line_key("Cheirosa 76 Perfume Mist", brand="Sol De Janeiro", vertical="beauty") == "cheirosa 76"
        assert line_key("N°5 L'EAU 100ml", brand="CHANEL", vertical="beauty") == "no 5 leau"
        assert line_key("Chanel No. 5 Eau de Parfum 100ml", brand="Chanel", vertical="beauty") == "no 5"

    def test_brand_words_leave_the_line_but_never_empty_it(self):
        assert line_key("Flower by Kenzo Eau de Parfum 100 ml", brand="Kenzo", vertical="beauty") == "flower"
        assert line_key("Kenzo Flower", brand="Kenzo", vertical="beauty") == "flower"
        assert line_key("Lancôme La Vie Est Belle", brand="Lancôme", vertical="beauty") == "vie est belle"
        assert line_key("Nina Ricci Ricci Ricci", brand="Nina Ricci", vertical="beauty") == "ricci ricci"
        assert line_key("Nina Ricci Nina 5cl", brand="Nina Ricci", vertical="beauty") == "nina"

    def test_the_house_name_is_stripped_too_when_the_brand_text_is_an_alias(self):
        assert line_key("Rabanne Invictus Aqua 100ml Eau de Toilette", brand="Paco Rabanne", house="Rabanne", vertical="beauty") == "invictus aqua"
        assert line_key("Invictus Aqua", brand="Paco Rabanne", house="Rabanne", vertical="beauty") == "invictus aqua"

    def test_format_and_edition_words_leave_the_line(self):
        assert line_key("Sauvage Eau de Toilette Spray 100ml Travel Exclusive", brand="Dior", vertical="beauty") == "sauvage"
        assert line_key("Fame Refill 200ml", brand="Rabanne", vertical="beauty") == "fame"
        assert line_key("FAME Couture limited-edition 80ml", brand="Rabanne", vertical="beauty") == "fame couture"

    def test_flankers_the_vocabulary_does_not_know_stay_in_the_line(self):
        """"Aromatic Essence" is not a variation word: the join is a person's, in the queue."""
        assert line_key("Eternity Aromatic Essence for Men EDP Intense 100ml", brand="Calvin Klein", vertical="beauty") == "eternity aromatic essence men"
        assert line_key("Euphoria Bold Elixir Parfum Intense 100ml", brand="Calvin Klein", vertical="beauty") == "euphoria bold"


class TestDrinkLines:
    @pytest.mark.parametrize("name", [
        "Chivas Regal 12 Year Old Blended Scotch Whisky 1L", "Chivas Regal 12 Years Old 1L",
        "Chivas Regal 12YO 40% 0.5L", "Chivas Regal 12y 40% 1L", "Chivas Regal 12y 40% 2x1L",
        "Chivas Regal 12 yo Blended Scotch Whisky 1L", "Chivas Regal 12 Years Old Triple Pack 3x1L",
        "12 Year Old Blended Scotch Whisky Scotland 1L",
    ])
    def test_every_spelling_of_an_age_is_one_line(self, name):
        assert line_key(name, brand="Chivas Regal", vertical="liquor") == "12"

    def test_a_12_and_an_18_are_different_lines(self):
        assert line_key("Glenfiddich 12 Year Old Highland Single Malt 750ml", brand="Glenfiddich", vertical="liquor") == "12"
        assert line_key("Glenfiddich 18 Year Old Port Cask Finish 70cl", brand="Glenfiddich", vertical="liquor") == "18 port cask finish"
        assert line_key("Glenfiddich 18 Years Old Perpetual Collection Vat 04 70cl", brand="Glenfiddich", vertical="liquor") != "18"

    def test_expression_words_stay_and_category_words_go(self):
        assert line_key("Absolut Original Vodka Sweden 50cl", brand="Absolut", vertical="liquor") == "original"
        assert line_key("Absolut Vodka 1L", brand="Absolut", vertical="liquor") == ""
        assert line_key("Bombay Sapphire London Dry Gin 1L", brand="Bombay Sapphire", vertical="liquor") == ""
        assert line_key("Bombay Sapphire Gin 1L", brand="Bombay Sapphire", vertical="liquor") == ""
        assert line_key("Johnnie Walker Blue Label 1L", brand="Johnnie Walker", vertical="liquor") == "blue label"
        assert line_key("Hennessy XO Cognac 70cl", brand="Hennessy", vertical="liquor") == "xo"
        assert line_key("Hennessy X.O 70cl", brand="Hennessy", vertical="liquor") == "xo"
        assert line_key("Hennessy V.S.O.P. Privilège 1L", brand="Hennessy", vertical="liquor") == "vsop privilege"
        assert line_key("Hennessy VSOP 1L", brand="Hennessy", vertical="liquor") == "vsop"

    def test_a_cask_keeps_the_category_word_that_names_it(self):
        assert line_key("Chivas Extra 13 Rum Cask Selection Blended Whisky 1L", brand="Chivas Regal", vertical="liquor") == "extra 13 rum cask selection"
        assert line_key("Chivas Regal Extra 13 Years Old Blended Scotch Whisky Sherry Cask Selection 1000 ml", brand="Chivas Regal", vertical="liquor") == "extra 13 sherry cask selection"
        assert line_key("Glenfiddich 18 Year Old Port Cask Finish 70cl", brand="Glenfiddich", vertical="liquor") == "18 port cask finish"

    def test_only_the_brands_last_word_leaves_the_middle_of_a_name(self):
        """The second "paris" in PSG's "paris" cap tells it from the plain cap."""
        psg = "Paris Saint-Germain - PSG"
        assert line_key('Paris saint-germain - "paris" cap', brand=psg) != line_key("Paris saint germain - cap", brand=psg)
        assert line_key("Bleu de Chanel Parfum 100ml", brand="Chanel", vertical="beauty") == "bleu"

    def test_a_drink_never_loses_its_concentration_words(self):
        """Talisker's "Eau de Vie" is not a fragrance; drinks strip no juice words."""
        assert line_key("Talisker 10 Eau de Vie 70cl", brand="Talisker", vertical="liquor") == "10 eau vie"

    def test_packaging_shorthand_goes(self):
        assert line_key("Chivas Regal 18YO W.C.48% 1L G", brand="Chivas Regal", vertical="liquor") == "18 wc"
        assert line_key("Glenfiddich 30 Year Old 70cl", brand="Glenfiddich", vertical="liquor") == "30"
        assert line_key("Glenfid 25yo 43% 0.7L GP", brand="Glenfiddich", vertical="liquor") == "glenfid 25"


class TestVariation:
    def test_drinks_have_none(self):
        assert variation_of("Talisker 10 Eau de Vie 70cl", "liquor") == ("", "")
        assert variation_of("Sauvage Eau de Parfum", None) == ("", "")

    @pytest.mark.parametrize(("name", "raw", "canonical"), [
        ("J'adore Eau de Parfum 10cl", "edp", "edp"),
        ("Polo Red Eau de Toilette 125 ml", "edt", "edt"),
        ("Dior Homme Cologne 12.5cl", "edc", "edc"),
        ("Cheirosa '76 Perfume Mist 90 ml", "parfum mist", "parfum"),
        ("Burberry Goddess Parfum 100 ml", "parfum", "parfum"),
        ("Invictus Intense Eau de Toilette 100 ml", "intense edt", "edt intense"),
        ("Bleu de Chanel Parfum", "parfum", "parfum"),
        ("Sauvage 100ml", "", ""),
        ("Absolue Rich Cream Refill 60ml", "absolu", "absolu"),
        ("Gucci Guilty Absolu de Parfum Pour Homme 60ml", "absolu", "absolu"),
        ("Gucci Guilty Elixir de Parfum Pour Homme 60 ml", "elixir", "elixir"),
    ])
    def test_the_words_found_and_what_they_mean(self, name, raw, canonical):
        assert variation_of(name, "beauty") == (raw, canonical)

    def test_display_wording(self):
        assert display_variation("edt intense") == "Eau de Toilette Intense"
        assert display_variation("elixir") == "Elixir"
        assert canonical_variation("edp intense elixir") == "elixir"


class TestForm:
    @pytest.mark.parametrize(("name", "form"), [
        ("Good Girl Gift Set 80ml + 10ml", "set"), ("Bottled Gift Set", "set"), ("Boss Bottled 10cl", ""),
        ("Fame Refill 200ml", "refill"), ("Absolue Rich Cream Refill 60ml", "refill"),
        ("Chivas Regal 12y 40% 2x1L", "pack"), ("Absolut Twin Pack 2x1L With Free Absolut Raspberri 375ml", "pack"),
        ("1 Million 10cl", ""),
    ])
    def test_sets_refills_and_packs_never_share_a_row_with_the_bottle(self, name, form):
        assert form_of(name) == form


class TestDisplay:
    def test_the_most_common_spelling_gives_the_name_as_the_shop_wrote_it(self):
        from collections import Counter
        samples = Counter({"1 Million Eau de Toilette 100 ml": 5, "1 million 10cl": 1})
        assert display_line_name("1 million", samples, "Rabanne", vertical="beauty") == "1 Million"
        ck = brand_words_of("Calvin Klein")
        assert display_line_name("eternity men", ["Eternity For Men Eau de Toilette 100 ml"], "Calvin Klein", brand_words=ck, vertical="beauty") == "Eternity For Men"
        assert display_line_name("", ["Absolut Vodka 1L"], "Absolut") == "Absolut"
        assert display_line_name("xo", ["Hennessy XO Cognac 70cl"], "Hennessy", brand_words=brand_words_of("Hennessy"), vertical="liquor") == "XO"
        assert display_line_name("blue label", [], "Johnnie Walker") == "Blue Label"
        assert display_line_name("15", ["Dewar's 15y 40% 1L"], "Dewar's", brand_words=brand_words_of("Dewar's"), vertical="liquor") == "15"

    def test_articles_apostrophes_and_accents_survive_in_the_name(self):
        """The key folds them so shops agree; the name a person reads keeps them."""
        cases = [
            ("men", "Jean Paul Gaultier Le Male EDT 125ml", "Jean Paul Gaultier", "Le Male"),
            ("vie est belle", "Lancôme La Vie Est Belle Eau de Parfum 50 ml", "Lancôme", "La Vie Est Belle"),
            ("linterdit", "L'Interdit Eau de Parfum 80ml", "Givenchy", "L'Interdit"),
            ("terre dhermes", "Terre d'Hermès Parfum 75ml", "HERMÈS", "Terre d'Hermès"),
            ("women", "Narciso Rodriguez For Her Eau de Parfum 100 ml", "Narciso Rodriguez", "For Her"),
            ("men", "Dior Homme Parfum 100ml", "Dior", "Homme"),
            ("re nutriv ultimate lift cream", "Estée Lauder Re-Nutriv Ultimate Lift Cream 50ml", "Estée Lauder", "Re-Nutriv Ultimate Lift Cream"),
            ("flower", "Flower by Kenzo Eau de Parfum 100 ml", "Kenzo", "Flower"),
            ("gentleman society", "Givenchy Gentleman Society Extrême Eau de Parfum 100ml", "Givenchy", "Gentleman Society"),
            ("guilty men", "Gucci Guilty Absolu de Parfum Pour Homme 60ml", "Gucci", "Guilty Pour Homme"),
            ("guilty men", "Gucci Guilty Elixir de Parfum Pour Homme 60 ml", "Gucci", "Guilty Pour Homme"),
            ("ricci ricci", "Nina Ricci Ricci Ricci EDP 80ml", "Nina Ricci", "Ricci Ricci"),
        ]
        for key, sample, brand, expected in cases:
            assert line_key(sample, brand=brand, vertical="beauty") == key, sample
            assert display_line_name(key, [sample], brand, brand_words=brand_words_of(brand), vertical="beauty") == expected, sample

    def test_slugs(self):
        assert line_slug("rabanne", "1 million") == "rabanne-1-million"
        assert line_slug("absolut", "") == "absolut"


class TestVariationKinds:
    """Stream L: a variation carries a kind; "shade" is a shop word for the kind color, read
    for Makeup on the separator the Heinemann-family shops actually use (the text after the
    last " / "); the beauty branch of the line rules is untouched, so a confectionery flavor
    never routes a bar through it."""

    def test_the_kinds_and_the_two_meanings_are_decoupled(self):
        from app.services.lines import BEAUTY_VERTICALS, VARIATION_KINDS, VARIATION_RULES, VARIATION_VERTICALS, variation_kind_of
        assert VARIATION_KINDS == ("concentration", "color", "flavor", "age", "cask", "edition")
        assert BEAUTY_VERTICALS == {"beauty"} and "confectionery" in VARIATION_VERTICALS
        assert set(VARIATION_RULES) == {("beauty", None), ("beauty", "Makeup"), ("confectionery", None)}
        assert variation_kind_of("1 Million Parfum 5cl", "beauty") == "concentration"

    @pytest.mark.parametrize(("name", "canonical"), [
        ("Joli Blush Blusher 6 gr / 02 Cheeky Pink", "02 cheeky pink"),
        ("SYNCHRO SKIN Self-Refreshing Foundation 30 ml / 220 Linen", "220 linen"),
        ("SYNCHRO SKIN Self-Refreshing Foundation 30 ml / 230 Alder", "230 alder"),
        ("Colour Riche Lipstick 3.6 gr / 800 Fairest Nude", "800 fairest nude"),
        ("Foundation 30 ml / 110N", "110n"),
    ])
    def test_a_makeup_shade_is_a_color(self, name, canonical):
        """15139, 15329, 15330 on the 14 Sep copy."""
        from app.services.lines import variation_kind_of
        assert variation_of(name, "beauty", category="Makeup") == (canonical, canonical)
        assert variation_kind_of(name, "beauty", category="Makeup") == "color"

    @pytest.mark.parametrize(("name", "category"), [
        ("Powder Brush N°13", "Makeup"), ("Chanel N°5 EDP", "Makeup"),
        ("Purifying Gentle Foaming Cleanser 125ml / Grasa", "Skincare"), ("1 Million Parfum 5cl", "Perfume"),
        ("Colour Riche Lipstick 3.6 gr / 800 Fairest Nude", None),
    ])
    def test_no_color_without_the_separator_or_outside_makeup(self, name, category):
        """11106, 15910; Skincare's " / Grasa" tails are skin types; the gate is the category."""
        from app.services.lines import variation_kind_of
        assert variation_kind_of(name, "beauty", category=category) != "color"

    def test_the_line_keeps_the_shade_tonight(self):
        """The shade fold into one line is rian's open decision (makeup shades); until then
        the line key is the v3 one and the kind is metadata beside it."""
        assert line_key("Colour Riche Lipstick 3.6 gr / 800 Fairest Nude", brand="L'Oréal", vertical="beauty") == "colour riche lipstick 800 fairest nude"

    def test_a_confectionery_flavor_and_an_unchanged_line_key(self):
        from app.services.lines import variation_kind_of
        assert variation_of("Dark Chocolate 70%", "confectionery") == ("dark", "dark")
        assert variation_kind_of("Milk Chocolate", "confectionery") == "flavor"
        assert variation_of("Excellence 100g", "confectionery") == ("", "")
        # Pinned before and after the flavor rule: the line keeps every flavor word.
        assert line_key("Lindt Excellence Dark Chocolate 70% 100g", brand="Lindt", vertical="confectionery") == "excellence dark chocolate"
        assert line_key("Lindt Lindor Milk Chocolate Truffles 200g", brand="Lindt", vertical="confectionery") == "lindor milk chocolate truffles"

    def test_the_one_million_fixture_keeps_its_three_concentrations(self):
        canon = {variation_of(name, "beauty", category="Perfume")[1] for _, name, _ in ONE_MILLION}
        assert canon == {"parfum", "", "edt", "elixir"}

    def test_gram_packs_and_counts_leave_the_line(self):
        assert line_key("Macarons 12 x 20g", brand="Ladurée", vertical="confectionery") == "macarons"
        assert line_key("Biscuits X20 Pieces", brand="Walkers", vertical="confectionery") == "biscuits"
        assert line_key("Advanced Ceramide Capsules 90 caps", brand="Elizabeth Arden", vertical="beauty") == "advanced ceramide capsules"
