"""Reading prices off the Extime (Paris) storefront.

Three of this source's traps are pinned here, because each one fails quietly
rather than loudly:

* its parking spaces and sightseeing tours wear the same product URL shape as
  a bottle, so only the sitemap shard says what a URL is;
* the offer node is a single-element LIST, so the obvious lookup returns
  nothing;
* the data payload arrives in fragments that must be decoded before they are
  joined, and a bottle's size exists nowhere else on the page.
"""

import json
from pathlib import Path

from app.services.collectors.extime import (
    Extime,
    slug_stem,
    capacity_ml,
    jsonld_offer,
    parse_jsonld_product,
    product_urls,
    rsc_page_object,
    size_label,
    variation_price,
)

FIXTURES = Path(__file__).parent / "fixtures"
PAGE = (FIXTURES / "extime_product.html").read_text()
URL = "https://www.extime.com/en/paris/product/single-malt-whisky-ten-years-old-107222991"


def shard(*locs: str) -> str:
    body = "".join(f"<url><loc>{u}</loc><lastmod>2026-09-01</lastmod></url>" for u in locs)
    return f'<?xml version="1.0"?><urlset>{body}</urlset>'


class TestUrlSelection:
    def test_a_product_url_is_kept(self):
        assert product_urls(shard(URL)) == [URL]

    def test_a_listing_page_is_not_a_product(self):
        listing = "https://www.extime.com/en/paris/shopping/beverage/whisky"
        assert product_urls(shard(listing)) == []

    def test_duplicates_collapse_but_order_holds(self):
        other = "https://www.extime.com/en/paris/product/another-thing-107222992"
        assert product_urls(shard(URL, other, URL)) == [URL, other]

    def test_a_short_id_is_collected_rather_than_silently_dropped(self):
        """Ids are 8 or 9 digits today. A shorter one is a surprise, not a reason to lose a price."""
        short = "https://www.extime.com/en/paris/product/some-bottle-123456"
        assert product_urls(shard(short)) == [short]

    def test_a_parking_space_is_indistinguishable_by_url(self):
        """The reason shard provenance is the classifier: this passes the shape test."""
        parking = "https://www.extime.com/en/paris/product/parking-cdg-p1-premium-83985201"
        assert product_urls(shard(parking)) == [parking]


class TestProductFamilies:
    """Each variant has its own URL, and every one renders the whole family."""

    def test_variants_of_one_product_share_a_family(self):
        base = "https://www.extime.com/en/paris/product/sauvage-elixir-"
        assert slug_stem(base + "106282591") == slug_stem(base + "104036920") == "sauvage-elixir"

    def test_different_products_do_not(self):
        base = "https://www.extime.com/en/paris/product/"
        assert slug_stem(base + "terre-dhermes-107222991") != slug_stem(base + "jadore-107222992")

    def test_a_url_without_an_id_has_no_family(self):
        assert slug_stem("https://www.extime.com/en/paris/shopping/beverage") is None


class TestJsonLd:
    def test_the_product_block_is_found_among_others(self):
        product = parse_jsonld_product(PAGE)
        assert product is not None and product["name"] == "Single Malt Whisky Ten Years Old"

    def test_offers_is_a_single_element_list_not_an_object(self):
        """product["offers"]["lowPrice"] raises on every page this site serves."""
        product = parse_jsonld_product(PAGE)
        assert isinstance(product["offers"], list)
        assert jsonld_offer(product)["lowPrice"] == 62.5

    def test_a_page_without_a_product_block_yields_nothing(self):
        assert parse_jsonld_product("<html><body>no markup here</body></html>") is None


class TestStreamedPayload:
    def test_fragments_are_decoded_before_they_are_joined(self):
        """The object spans three pushes; joining raw source would decode to nothing."""
        record = rsc_page_object(PAGE)
        assert record is not None
        assert record["main_offer"]["gtin"] == "5010494195125"

    def test_a_page_with_no_payload_is_survivable(self):
        assert rsc_page_object("<html><body></body></html>") is None


class TestCapacity:
    def test_a_hundred_centilitres_is_a_litre(self):
        """The bug this guards: 100cl read as 100ml, filing a litre as a miniature."""
        assert capacity_ml(100, "cl") == 1000

    def test_units_the_feed_actually_uses(self):
        assert capacity_ml(70, "cl") == 700
        assert capacity_ml(1, "l") == 1000
        assert capacity_ml(750, "ml") == 750

    def test_an_unknown_or_absurd_capacity_is_no_capacity(self):
        assert capacity_ml(5, "kg") is None
        assert capacity_ml(None, "cl") is None
        assert capacity_ml(9000, "l") is None

    def test_labels_read_the_way_a_shopper_says_them(self):
        assert size_label(1000) == "1L"
        assert size_label(700) == "70cl"


class TestPricing:
    def test_the_duty_free_price_is_the_one_we_publish(self):
        """Duty-paid is higher and would flatter every other shop we compare against."""
        variation = {"duty_free": {"price": 62.5}, "duty_paid": {"price": 75}}
        assert variation_price(variation) == (62.5, None)

    def test_a_crossed_out_price_becomes_the_was_price(self):
        variation = {"duty_free": {"price": 40.0, "price_crossed": 50.0}}
        assert variation_price(variation) == (40.0, 50.0)

    def test_a_was_price_below_the_price_is_not_a_saving(self):
        variation = {"duty_free": {"price": 50.0, "price_crossed": 40.0}}
        assert variation_price(variation) == (50.0, None)

    def test_a_variation_with_no_price_yields_none(self):
        assert variation_price({"duty_free": {}}) == (None, None)


class TestListings:
    def test_the_real_shape_of_a_product_page(self):
        rows = Extime()._listings(URL, PAGE)
        assert len(rows) == 1
        row = rows[0]
        assert row.name == "Single Malt Whisky Ten Years Old"
        assert row.brand == "Ardbeg"
        assert row.gtin == "5010494195125"
        assert row.price == 62.5 and row.currency == "EUR"
        assert row.in_stock is True
        assert row.shop_code == "CDG"
        assert row.feed_categories[:2] == ["Beverage", "Spirits"]

    def test_size_comes_from_the_payload_when_the_name_is_silent(self):
        """"Single Malt Whisky Ten Years Old" says nothing about being a litre."""
        row = Extime()._listings(URL, PAGE)[0]
        assert row.quantity_ml == 1000

    def _page_with_variations(self, variations: list[dict]) -> str:
        record = rsc_page_object(PAGE)
        record["variations"] = variations
        blob = '5:["$","$L2f",null,{"page":' + json.dumps(record, separators=(",", ":")) + "}]"
        head = PAGE.split("<body>")[0]
        push = f'<script>self.__next_f.push({json.dumps([1, blob])})</script>'
        return f"{head}<body>{push}</body></html>"

    def test_each_size_becomes_its_own_row(self):
        """One bottle must never be priced from a cheaper sibling of another size."""
        page = self._page_with_variations([
            {"sku": "A", "capacity": 70, "capacity_unit": "cl", "duty_free": {"price": 40.0}},
            {"sku": "A", "capacity": 1, "capacity_unit": "l", "duty_free": {"price": 55.0}},
        ])
        rows = Extime()._listings(URL, page)
        assert sorted((r.quantity_ml, r.price) for r in rows) == [(700, 40.0), (1000, 55.0)]
        assert {r.source_sku for r in rows} == {"76594::700", "76594::1000"}

    def test_sizes_do_not_share_a_barcode(self):
        """A shared barcode would merge the sizes back into one product."""
        page = self._page_with_variations([
            {"sku": "A", "capacity": 70, "capacity_unit": "cl", "duty_free": {"price": 40.0}},
            {"sku": "A", "capacity": 1, "capacity_unit": "l", "duty_free": {"price": 55.0}},
        ])
        assert all(r.gtin is None for r in Extime()._listings(URL, page))

    def test_a_multi_size_page_with_no_prices_is_skipped_not_guessed(self):
        page = self._page_with_variations([
            {"sku": "A", "capacity": 70, "capacity_unit": "cl"},
            {"sku": "A", "capacity": 1, "capacity_unit": "l"},
        ])
        assert Extime()._listings(URL, page) == []

    def test_a_single_variant_falls_back_to_the_advertised_price(self):
        page = self._page_with_variations([{"sku": "A", "capacity": 70, "capacity_unit": "cl"}])
        rows = Extime()._listings(URL, page)
        assert len(rows) == 1 and rows[0].price == 62.5


class TestCapacityCrossCheckLiveShape:
    """The first audit (2026-09-05) found "CHEIROSA 76" stored as 7,624 ml and
    "DELICIA DRENCH 59" as 5,924 ml. The names were innocent: the page itself
    declares `capacity: 7624, capacity_unit: "ml"` (a retailer typing "76" and
    "240" together). The page also prices it per 100 ml, which implies 240 ml;
    the cross-check existed but did not know this shape, so it never fired.
    `extime_cheirosa_76.html` is that page, trimmed to facts."""

    PAGE = (FIXTURES / "extime_cheirosa_76.html").read_text()
    URL = "https://www.extime.com/en/paris/product/cheirosa-76-111521603"

    def test_the_declared_capacity_is_refused_when_the_unit_price_disagrees(self):
        from app.services.collectors.extime import checked_capacity_ml, implied_quantity_ml
        variation = rsc_page_object(self.PAGE)["variations"][0]
        assert variation["capacity"] == 7624
        assert implied_quantity_ml(variation, 29.56) == 240
        assert checked_capacity_ml(variation, 29.56) is None

    def test_the_row_carries_no_size_rather_than_a_seven_litre_mist(self):
        rows = Extime()._listings(self.URL, self.PAGE)
        assert len(rows) == 1
        row = rows[0]
        assert row.quantity_ml is None
        assert row.name == "CHEIROSA 76" and row.gtin == "0810912036253"
        assert row.vertical == "beauty" and row.price == 29.56 and row.was_price == 32.84

    def test_a_weight_in_grams_implies_no_volume(self):
        from app.services.collectors.extime import implied_quantity_ml
        assert implied_quantity_ml({"net_weight": 120, "net_weight_unit": "g"}, 29.56) is None
        assert implied_quantity_ml({"net_weight": 24, "net_weight_unit": "cl"}, 29.56) == 240

    def test_the_reference_quantity_is_read_in_any_unit(self):
        from app.services.collectors.extime import implied_quantity_ml
        tier = {"price": 62.5, "price_per_quantity": 62.5, "price_per_quantity_type": "L"}
        assert implied_quantity_ml({"duty_free": tier}, 62.5) == 1000
        tier = {"price": 40.0, "price_per_quantity": 5.714, "price_per_quantity_type": "10cl"}
        assert implied_quantity_ml({"duty_free": tier}, 40.0) == 700


class TestQuantity:
    """The variation's own structured quantity (identity rules v4): the declared capacity
    when it states one, and the net weight only when nothing about the size is stated
    anywhere -- capacity included."""

    def test_a_declared_capacity_wins_over_a_net_weight_never_grams(self):
        from app.services.collectors.extime import quantity_of_variation

        variation = {"capacity": 100, "capacity_unit": "ml", "net_weight": 86, "net_weight_unit": "g"}
        quantity, text, from_net_weight = quantity_of_variation(variation, "Some Fragrance")
        assert (quantity.value, quantity.unit) == (100, "ml")
        assert from_net_weight is False

    def test_no_capacity_and_no_stated_size_falls_back_to_net_weight(self):
        """Rows from a real page: no capacity field on the variation, and the product name
        ("Single Malt Whisky Ten Years Old") states no size either."""
        variation = {"sku": "A", "net_weight": 72, "net_weight_unit": "g", "duty_free": {"price": 40.0}}
        page = TestListings()._page_with_variations([variation])
        rows = Extime()._listings(URL, page)
        assert len(rows) == 1
        row = rows[0]
        assert (row.quantity.value, row.quantity.unit) == (72, "g")
        assert row.quantity_text == "72 g"
        assert row.raw["quantity_source"] == "extime.net_weight"
