"""A shop's options arrive as fields, never glued into the name (Stream K3.1, identity rules v6).

What it cost: the Shopify collector glued the variant title into the name ("ROUGE ALLURE 3.5 gr
/ 99 Pirate"), a name rule then had to read the shade back off the " / " tail, and until it did
one lipstick was 59 product lines (CHANEL Rouge Allure, 15 Sep). The durable form is that the
shade never touches a name rule: `RawListing.options` carries `(the shop's name, the value)`,
ingest writes each as `option:<name>` (certain, identity), and the quantity option feeds the typed
quantity. The fragments below are the real shapes from the Attenza shops' `/products.json`.
Pure logic and the SQLite kit; no network.
"""

from __future__ import annotations

from sqlalchemy import select

from app import cli
from app.models import Brand, Listing, ProductVariant, RawRecord, Retailer, Shop
from app.services import ingest, keying, product_lines
from app.services.collectors.base import RawListing, option_attributes, quantity_of, quantity_options
from app.services.collectors.extime import published_options as extime_options
from app.services.collectors.shopify import SHOPS, ShopifyCollector, published_options
from tests.test_decided import db  # noqa: F401  (the SQLite kit)

PTY = next(ShopifyCollector(shop, slug, name, op) for shop, slug, name, op in SHOPS if shop.code == "PTY")


def rouge_allure(variant_id: int, shade: str) -> tuple[dict, dict]:
    product = {"id": 1, "title": "ROUGE ALLURE", "vendor": "CHANEL", "handle": "rouge-allure", "product_type": "Maquillaje",
               "tags": ["Labios"], "options": [{"name": "Tamaño", "position": 1, "values": ["3.5 gr"]},
                                                {"name": "Color", "position": 2, "values": ["99 Pirate", "104 Passion"]}]}
    variant = {"id": variant_id, "title": f"3.5 gr / {shade}", "option1": "3.5 gr", "option2": shade, "option3": None,
               "price": "48.00", "available": True, "sku": "", "barcode": ""}
    return product, variant


RINGCONN = ({"id": 2, "title": "RingConn Gen 2 Air", "vendor": "RingConn", "handle": "ringconn-gen-2-air", "product_type": "",
             "tags": [], "options": [{"name": "Talla", "position": 1}, {"name": "Color", "position": 2}]},
            {"id": 77, "title": "6.0 / Plateado", "option1": "6.0", "option2": "Plateado", "price": "199.00", "available": True})


class TestTheCollectorCarriesFields:
    def test_the_name_is_the_title_and_the_options_are_fields(self):
        listing = PTY.listing(*rouge_allure(11, "99 Pirate"))
        assert listing.name == "ROUGE ALLURE"
        assert listing.options == [("Tamaño", "3.5 gr"), ("Color", "99 Pirate")]
        assert quantity_options(listing.options) == ["3.5 gr"]
        assert option_attributes(listing.options) == {"option:color": "99 Pirate"}, "the size option is the quantity, not an identity attribute"
        q = quantity_of(listing)
        assert (q.value, q.unit, q.state) == (3.5, "g", "stated")

    def test_a_fresh_fragment_never_reaches_the_tail_reader(self):
        listing = PTY.listing(*rouge_allure(11, "99 Pirate"))
        assert " / " not in listing.name
        assert product_lines._read_tail(product_lines._fold(listing.name)) == ("", "")
        assert product_lines.attribute_of(listing.name, "beauty", category="Makeup") == ("", "")

    def test_the_placeholder_option_is_no_option(self):
        product = {"title": "Grey Goose Vodka 1L", "vendor": "Grey Goose", "options": [{"name": "Title", "position": 1}]}
        assert published_options(product, {"title": "Default Title", "option1": "Default Title"}) == []

    def test_a_skin_type_is_the_shops_option_like_any_other(self):
        """v5 kept "/ Seca" in the line by a word list of skin types; the shop published it as the
        option `Tipo`, so it is certain and it tells the variants apart."""
        product = {"title": "Galatée Confort", "options": [{"name": "Tamaño", "position": 1}, {"name": "Tipo", "position": 2}]}
        options = published_options(product, {"title": "400ml / Seca", "option1": "400ml", "option2": "Seca"})
        assert option_attributes(options) == {"option:tipo": "Seca"}

    def test_extime_carries_the_variation_label_and_it_is_the_quantity(self):
        assert extime_options({"name": "50 ml", "capacity": 50, "capacity_unit": "ml"}) == [("variation", "50 ml")]
        assert option_attributes([("variation", "50 ml")]) == {}
        # The name Extime builds already carries the size label: the figure is read once, not as a set of two.
        raw = RawListing(source_sku="x::50", name="Paradoxe 50 ml", price=90.0, currency="EUR", shop_code="CDG",
                         options=[("variation", "50 ml")])
        q = quantity_of(raw)
        assert (q.value, q.unit, q.form) == (50.0, "ml", "single")


class TestTheKey:
    def test_two_shades_are_one_product_line_and_two_variants(self, db):  # noqa: F811
        pirate, passion = PTY.listing(*rouge_allure(11, "99 Pirate")), PTY.listing(*rouge_allure(12, "104 Passion"))
        a, b = ingest._resolve_product(db, pirate), ingest._resolve_product(db, passion)
        db.flush()
        assert a.id != b.id and a.product_line_id is not None and a.product_line_id == b.product_line_id
        assert a.match_key == "chanel|rouge-allure|option:color=99-pirate|3.5g"
        assert b.match_key == "chanel|rouge-allure|option:color=104-passion|3.5g"
        assert a.attributes["option:color"] == "99 Pirate"
        assert ingest._resolve_product(db, PTY.listing(*rouge_allure(11, "99 Pirate"))).id == a.id, "the next sighting lands on it"

    def test_the_ring_from_panama(self, db):  # noqa: F811
        """No category, no quantity, two options: two attributes, the `n/a` quantity slot (which
        equals itself), its own product line, and nothing guessed: not filed under "liquor"."""
        listing = PTY.listing(*RINGCONN)
        assert listing.vertical is None and listing.quantity_ml is None
        ring = ingest._resolve_product(db, listing)
        db.flush()
        assert ring.match_key == "ringconn|gen-2-air|option:color=plateado;option:talla=6-0|n/a"
        assert {k: v for k, v in ring.attributes.items() if k.startswith("option:")} == {"option:talla": "6.0", "option:color": "Plateado"}
        assert ring.vertical == "unknown" and ring.category is None and ring.quantity_state != "stated"
        assert ring.product_line_id is not None
        again = ingest._resolve_product(db, PTY.listing(*RINGCONN))
        assert again.id == ring.id, "n/a equals itself: the same ring lands on its variant"
        _, key_now = None, keying.product_key(ring, keying.load_maps(db))[0]
        assert key_now == ring.match_key, "rederive computes the key ingest did"
        other_size = ({**RINGCONN[0]}, {**RINGCONN[1], "id": 78, "title": "7.0 / Plateado", "option1": "7.0"})
        assert ingest._resolve_product(db, PTY.listing(*other_size)).id != ring.id

    def test_an_option_is_never_enriched_onto_a_variant_another_listing_made(self, db):  # noqa: F811
        """The key reads the options, so they are written at the variant's creation only: a key
        that moved would strand the first listing's next sighting."""
        first = RawListing(source_sku="h1", name="Rouge Allure 3.5g", price=40.0, currency="EUR", shop_code="S", brand="CHANEL",
                           gtin="3145891740103", vertical="beauty")
        made = ingest._resolve_product(db, first)
        db.flush()
        attenza = PTY.listing(*rouge_allure(11, "99 Pirate"))
        attenza.gtin = "3145891740103"
        assert ingest._resolve_product(db, attenza).id == made.id
        assert not any(k.startswith("option:") for k in (made.attributes or {}))


class TestBackfillOptions:
    def test_a_glued_variant_is_renamed_given_its_options_and_meets_its_next_sighting(self, db):  # noqa: F811
        retailer = Retailer(slug="attenza", name="Attenza")
        brand = Brand(slug="chanel", name="CHANEL")
        db.add_all([retailer, brand])
        db.flush()
        shop = Shop(retailer_id=retailer.id, code="PTY", iata="PTY", name="Panama", currency="USD")
        old = ProductVariant(name="ROUGE ALLURE 3.5 gr / 99 Pirate", brand="CHANEL", brand_id=brand.id, vertical="beauty",
                             category="Makeup", match_key="pending", attributes={"attribute": "99 pirate", "attribute_kind": "color"},
                             quantity_value=3.5, quantity_unit="g", quantity_state="stated", form="single")
        db.add_all([shop, old])
        db.flush()
        listing = Listing(variant_id=old.id, shop_id=shop.id, source_sku="11")
        db.add(listing)
        db.flush()
        product, variant = rouge_allure(11, "99 Pirate")
        db.add(RawRecord(listing_id=listing.id, payload={"product": product, "variant": variant}, parser_version="shopify/2026-09-05"))
        db.commit()
        assert cli.backfill_options(db).startswith("options: 1 variant(s) named by their product title, 1 given")
        assert cli.backfill_options(db).startswith("options: 0 variant(s)")
        db.refresh(old)
        assert old.name == "ROUGE ALLURE" and old.attributes["option:color"] == "99 Pirate"
        keying.invalidate()
        key, attributes = keying.product_key(old, keying.load_maps(db))
        assert key == "chanel|rouge-allure|option:color=99-pirate|3.5g"
        assert "attribute" not in attributes, "the tail-read shade is gone; the option field carries it"
        old.match_key, old.attributes = key, attributes
        db.commit()
        keying.invalidate()
        assert ingest._resolve_product(db, PTY.listing(product, variant)).id == old.id, "no second variant is minted"
        assert db.scalar(select(ProductVariant.id).where(ProductVariant.id != old.id)) is None

    def test_a_name_stored_before_the_change_falls_back_to_its_tail(self):
        """No fragment to read the options from: the " / " tail is still the shop's option."""
        key = product_lines.product_line_key("ROUGE ALLURE 3.5 gr / 99 Pirate", listed_brand="CHANEL", vertical="beauty")
        assert key == "rouge allure"
        assert product_lines.identity_slot("ROUGE ALLURE 3.5 gr / 99 Pirate", "beauty", category="Makeup") == "tail=99-pirate"
