"""Where a listing lands under identity rules v6 (Stream K3.5).

A listing already placed stays placed while the shop's words for it are unchanged; a new listing
lands by barcode, else by the certain key, else becomes its own variant on its own new product
line and is OFFERED to the nearest approved line as a proposal, never joined to it. What it would
have cost without the first rule (rehearsed 17 Sep on the staging copy): 699 listings that reached
their variant through a word list would each have left for a fresh variant at their next sighting,
taking 114 comparisons with them, silently. The SQLite kit; no network.
"""

from __future__ import annotations

from sqlalchemy import select

from app.models import Brand, Listing, ProductLine, ProductVariant, Proposal, ProposalPass, Retailer, Shop
from app.services import ingest, keying, merges, overrides, proposal_rules
from app.services.collectors.base import RawListing
from tests.test_decided import RIAN, db  # noqa: F401  (the SQLite kit)


def chivas(db):  # noqa: F811
    retailer, brand = Retailer(slug="r", name="R"), Brand(slug="chivas-regal", name="Chivas Regal")
    db.add_all([retailer, brand])
    db.flush()
    shop = Shop(retailer_id=retailer.id, code="S1", iata="AAA", name="Shop", currency="EUR")
    line = ProductLine(brand_id=brand.id, key="12yo", slug="chivas-regal-12yo", name="12 Years Old")
    db.add_all([shop, line])
    db.flush()
    variant = ProductVariant(name="Chivas Regal 12 Years Old 1L", brand="Chivas Regal", brand_id=brand.id, vertical="liquor",
                             product_line_id=line.id, match_key="chivas-regal|12yo||1000ml", attributes={}, quantity_ml=1000,
                             quantity_value=1000, quantity_unit="ml", quantity_state="stated", form="single")
    db.add(variant)
    db.flush()
    return shop, line, variant


def tile(sku: str, name: str) -> RawListing:
    return RawListing(source_sku=sku, name=name, price=40.0, currency="EUR", shop_code="S1", brand="Chivas Regal",
                      vertical="liquor", quantity_ml=1000)


class TestAPlacedListingStaysPlaced:
    def test_a_listing_a_word_list_once_joined_does_not_leave_at_its_next_sighting(self, db):  # noqa: F811
        """"12 Year Old Blended Scotch Whisky" reached the variant under v5 because a list deleted
        the category words; under the certain key it keys apart. It stays: the rules place new
        listings, and a change of rules never un-places an old one."""
        shop, _line, variant = chivas(db)
        variant.name_key = None
        db.add(Listing(variant_id=variant.id, shop_id=shop.id, source_sku="old"))
        db.commit()
        raw = tile("old", "Chivas Regal 12 Years Old 1L")
        assert ingest._resolve_product(db, raw, shop).id == variant.id
        # the same SKU under words that are no longer the variant's own falls through to the key
        moved = ingest._resolve_product(db, tile("old", "Chivas Regal Mizunara 1L"), shop)
        assert moved.id != variant.id

    def test_contradicting_evidence_still_wins(self, db):  # noqa: F811
        shop, _line, variant = chivas(db)
        variant.gtin = "5000299225004"
        db.add(Listing(variant_id=variant.id, shop_id=shop.id, source_sku="old"))
        db.commit()
        raw = tile("old", "Chivas Regal 12 Years Old 1L")
        raw.gtin = "5000299212318"
        assert ingest._resolve_product(db, raw, shop).id != variant.id


class TestANewListing:
    def test_it_joins_a_decided_variant_by_the_certain_key(self, db):  # noqa: F811
        """A person moved the variant to another product line. Its key stays the rules', so a new
        listing stating exactly the same lands on it: that is not a guess."""
        shop, line, variant = chivas(db)
        other = ProductLine(brand_id=line.brand_id, key="the chivas range", slug="chivas-regal-range", name="Range")
        db.add(other)
        db.flush()
        overrides.decide(db, "product", str(variant.id), "product_line_id", other.id, set_by=RIAN, collected_value=line.id)
        variant.product_line_id = other.id
        db.commit()
        keying.invalidate()
        merges.rekey_product_variants(db, [variant], keying.load_maps(db))
        assert variant.match_key == "chivas-regal|12yo||1000ml" and variant.product_line_id == other.id
        assert ingest._resolve_product(db, tile("new", "Chivas Regal 12 YO 1L"), shop).id == variant.id

    def test_it_never_joins_an_approved_line_by_a_word_list_and_is_offered_to_it_instead(self, db):  # noqa: F811
        shop, line, variant = chivas(db)
        overrides.decide(db, "line", str(line.id), "name", "12 Years Old", set_by=RIAN, collected_value=line.name)  # an approved line
        db.commit()
        keying.invalidate()
        arrival = ingest._resolve_product(db, tile("new", "Chivas Regal 12 Year Old Blended Scotch Whisky 1L"), shop)
        db.flush()
        assert arrival.id != variant.id and arrival.product_line_id != line.id, "its own variant on its own new product line"
        assert db.get(ProductLine, arrival.product_line_id).key == "12yo blended scotch whisky"
        db.add(Listing(variant_id=arrival.id, shop_id=shop.id, source_sku="new"))
        db.flush()
        nearest, score = proposal_rules.nearest_approved_line(db, arrival)
        assert nearest.id == line.id and score == 0.25
        assert proposal_rules.propose_arrival(db, arrival) is True
        proposal = db.scalar(select(Proposal))
        assert (proposal.entity_type, proposal.field, proposal.entity_id) == ("product_variant", "product_line", arrival.id)
        assert proposal.value == f"line:{line.uid}" and proposal.status == "open" and "addition to 12 Years Old" in proposal.reason
        assert db.scalar(select(ProposalPass.name)) == "arrival:6" and db.scalar(select(ProposalPass.kind)) == "arrival"
        assert arrival.product_line_id != line.id, "a proposal applies nothing"

    def test_an_unapproved_line_is_offered_nothing(self, db):  # noqa: F811
        shop, _line, _variant = chivas(db)
        arrival = ingest._resolve_product(db, tile("new", "Chivas Regal 12 Year Old Blended Scotch Whisky 1L"), shop)
        db.flush()
        assert proposal_rules.nearest_approved_line(db, arrival) == (None, 0.0)
