"""The 2026-09-04 audit's defect list (build plan §3a), each as the record that failed.

Every case here was measured on the live catalogue before it was fixed, and
each one had been published: a wrong-size product, a stock flag nobody
observed, a currency the shop disagreed with, a run that looked live for
ten days, a photo of the wrong age statement.
"""

from datetime import UTC, datetime, timedelta

import pytest

from app.services.collectors.dubai import in_stock_of
from app.services.collectors.extime import checked_capacity_ml, implied_quantity_ml
from app.services.images import numbers_agree
from app.services.ingest import STUCK_AFTER, RejectedListing, is_stuck, mark_stuck, sync_shop_currency
from app.services.normalize import name_tokens


class TestDubaiStock:
    def test_active_is_not_stock(self):
        """7,445 observations said in stock on the strength of `active: true`."""
        assert in_stock_of({"id": "1", "active": True, "displayName": "Chivas 12 1L"}) is None

    @pytest.mark.parametrize(("item", "expected"), [
        ({"stockStatus": "IN_STOCK"}, True),
        ({"x_stockStatus": "OUT_OF_STOCK"}, False),
        ({"orderableQuantity": 0}, False),
        ({"orderableQuantity": 12}, True),
        ({"inStock": False}, False),
    ])
    def test_a_real_stock_field_is_read(self, item, expected):
        assert in_stock_of(item) is expected


class TestExtimeCapacityCrossCheck:
    def test_a_declared_capacity_that_a_per_litre_price_confirms_is_kept(self):
        """62.50 for a bottle priced at 62.50 per litre is a litre; the
        declared 100cl (which once read as 100 ml elsewhere) stands."""
        attribute = {"capacity": 100, "capacity_unit": "cl",
                     "price_per_quantity": {"price": 62.5, "quantity": 1, "unit": "l"}}
        assert implied_quantity_ml(attribute, 62.5) == 1000
        assert checked_capacity_ml(attribute, 62.5) == 1000

    def test_a_contradicted_capacity_becomes_unknown_not_guessed(self):
        attribute = {"capacity": 100, "capacity_unit": "cl",
                     "price_per_quantity": "89.29 €/L"}
        assert implied_quantity_ml(attribute, 62.5) == 700
        assert checked_capacity_ml(attribute, 62.5) is None

    def test_net_weight_is_a_second_reading_too(self):
        attribute = {"capacity": 70, "capacity_unit": "cl", "net_weight": "700 ml"}
        assert checked_capacity_ml(attribute, 40.0) == 700

    def test_no_second_reading_means_the_declaration_stands(self):
        assert checked_capacity_ml({"capacity": 70, "capacity_unit": "cl"}, 40.0) == 700

    def test_the_700_750_sloppiness_is_tolerated(self):
        attribute = {"capacity": 75, "capacity_unit": "cl", "net_weight": {"value": 700, "unit": "ml"}}
        assert checked_capacity_ml(attribute, 40.0) == 750


class TestImageProvenance:
    def test_a_different_age_statement_vetoes_a_name_match(self):
        """Glenfiddich 18's photo cleared the word-overlap bar for the 12."""
        assert not numbers_agree(name_tokens("Glenfiddich 12 Year Old"), name_tokens("Glenfiddich 18 Year Old"))
        assert numbers_agree(name_tokens("Glenfiddich 12 Year Old"), name_tokens("Glenfiddich 12"))

    def test_sizes_never_veto_because_they_are_stripped_first(self):
        assert numbers_agree(name_tokens("Hendrick's Gin 1L"), name_tokens("Hendrick's Gin 70cl"))


class TestSizeVetoRejects:
    def test_a_veto_is_a_rejection_with_the_record_in_it(self):
        """Two 7,000 ml Glenfiddichs were once created from a '0.70cl' misread
        because the veto re-routed the listing to brand+name+size."""
        exc = RejectedListing("gtin_size_veto", "gtin=5010327000176 product=1(700ml) listing=x(7000ml)")
        assert exc.reason == "gtin_size_veto" and "7000ml" in str(exc)


class TestOperationalRows:
    def test_a_stale_shop_currency_follows_the_collector(self):
        class Loc:
            code = "MEX"
            currency = "MXN"
        loc = Loc()
        assert sync_shop_currency(loc, "USD") and loc.currency == "USD"
        assert not sync_shop_currency(loc, "USD")

    def test_a_run_running_for_a_day_is_stuck_and_marked_error(self):
        """Eight runs sat 'running' from 25 Aug to 4 Sep."""
        class Run:
            status = "running"
            started_at = datetime.now(UTC) - STUCK_AFTER - timedelta(minutes=1)
            finished_at = None
            error = None
        run = Run()
        assert is_stuck(run, datetime.now(UTC))
        mark_stuck(run, "test")
        assert run.status == "error" and run.finished_at is not None and "stuck" in run.error
        # boot=None is the timer on its own. Left to itself `is_stuck` also reads /proc for the
        # container's start, and this run is only two hours old: on a host that rebooted an hour
        # ago it is dead by that rule and the assertion below would depend on the machine.
        fresh = Run(); fresh.started_at = datetime.now(UTC) - timedelta(hours=2)
        assert not is_stuck(fresh, datetime.now(UTC), boot=None)
        assert is_stuck(fresh, datetime.now(UTC), boot=datetime.now(UTC) - timedelta(hours=1))
