"""The vetoes on the automatic merge, and the person's right to overrule them (Stream K3.4).

Plan W9 and rian's question of 16 Sep: "for the 40% vs 43% in the case of a typo, can I still
correct the incorrect one or am I stuck with a split that I can't fix?" A stated attribute that
differs vetoes the AUTOMATIC merge in every category; a Keep separate is honoured by every machine
path; the veto never binds a person, and the person's merge holds across the next sighting of
either key, where the differing ABV is part of the key. The SQLite kit; no network.
"""

from __future__ import annotations

from sqlalchemy import select

from app.models import Brand, Merge, ProductVariant
from app.models.catalog import IDENTITY_RULES_VERSION
from app.services import ingest, keying, merges
from app.services.collectors.base import RawListing
from tests.test_decided import db  # noqa: F401  (the SQLite kit)


def variant(db, name: str, *, abv=None, attributes=None, key="glen-x|12yo||1000ml", **kw) -> ProductVariant:  # noqa: F811
    brand = db.scalar(select(Brand).where(Brand.slug == "glen-x"))
    if brand is None:
        brand = Brand(slug="glen-x", name="Glen X")
        db.add(brand)
        db.flush()
    row = ProductVariant(name=name, brand="Glen X", brand_id=brand.id, vertical="liquor", match_key=key, abv=abv,
                         attributes=attributes or {}, quantity_ml=1000, quantity_value=1000, quantity_unit="ml",
                         quantity_state="stated", form="single", identity_rules_version=IDENTITY_RULES_VERSION, **kw)
    db.add(row)
    db.flush()
    return row


class TestTheAutomaticMergeIsVetoed:
    def test_forty_against_forty_three_never_folds(self, db):  # noqa: F811
        a, b = variant(db, "Glen X 12 Years Old 1L", abv=40), variant(db, "Glen X 12 Year Old 1L", abv=43)
        assert merges.identity_differs(a, b) == "abv"
        counts = merges.merge_duplicates(db)
        assert counts["merged_rows"] == 0 and counts["candidate_groups"] == 1
        assert a.merged_into_id is None and b.merged_into_id is None

    def test_numbers_compare_as_numbers_and_a_silent_side_is_no_disagreement(self, db):  # noqa: F811
        a, b, c = variant(db, "Glen X 12 Years Old 1L", abv=40), variant(db, "Glen X 12 YO 1L", abv=40.0), variant(db, "Glen X 12y 1L")
        assert merges.identity_differs(a, b) is None and merges.identity_differs(a, c) is None
        assert merges.merge_duplicates(db)["merged_rows"] == 2

    def test_every_identity_kind_is_read_not_abv_alone(self, db):  # noqa: F811
        a = variant(db, "Glen X 12 Years Old 1L", attributes={"age": 12})
        b = variant(db, "Glen X 12 Years Old 1L", attributes={"age": "15"})
        c = variant(db, "Glen X Ring", attributes={"option:talla": "6.0"})
        d = variant(db, "Glen X Ring", attributes={"option:talla": "7.0"})
        assert merges.identity_differs(a, b) == "age" and merges.identity_differs(c, d) == "option:talla"

    def test_a_pair_a_person_kept_separate_is_never_folded(self, db):  # noqa: F811
        a, b = variant(db, "Glen X 12 Years Old 1L"), variant(db, "Glen X 12 Year Old 1L")
        maps = keying.load_maps(db)
        maps.separated = {frozenset((a.id, b.id))}
        assert "kept_separate" in merges._conflicts([a, b], maps)
        assert merges.merge_duplicates(db, maps=maps)["merged_rows"] == 0


class TestThePersonMayOverrule:
    def test_a_persons_merge_across_a_stated_difference_holds_at_the_next_sighting_of_either_key(self, db):  # noqa: F811
        """One shop typed 43% on a 40% bottle. The keys differ (`abv=40`, `abv=43`), so no rule
        ever joins them; rian confirms them the same. The forwarding row keeps the 43 key, and the
        shop's next "43%" sighting follows it to the survivor instead of minting the split again."""
        right = variant(db, "Glen X 12 Years Old 40% 1L", abv=40, key="pending")
        typo = variant(db, "Glen X 12 Years Old 43% 1L", abv=43, key="pending")
        maps = keying.load_maps(db)
        merges.rekey_product_variants(db, [right, typo], maps)
        assert (right.match_key, typo.match_key) == ("glen-x|12yo|abv=40|1000ml", "glen-x|12yo|abv=43|1000ml")
        assert merges.merge_duplicates(db)["merged_rows"] == 0, "a machine never joins them"
        assert merges.merge_product_variants(db, right, [typo], reason="confirmed", merged_by=1, note="a typo at one shop") == 1
        db.commit()
        keying.invalidate()
        merges.rekey_product_variants(db, list(db.scalars(select(ProductVariant))), keying.load_maps(db))
        db.commit()
        assert typo.merged_into_id == right.id and typo.match_key == "glen-x|12yo|abv=43|1000ml", "the forwarding row keeps its own key"
        before = db.scalar(select(ProductVariant.id).order_by(ProductVariant.id.desc()))
        for sku, name, abv in (("s43", "Glen X 12 Years Old 43% 1L", 43.0), ("s40", "Glen X 12 Years Old 40% 1L", 40.0)):
            raw = RawListing(source_sku=sku, name=name, price=50.0, currency="EUR", shop_code="S1", brand="Glen X",
                             vertical="liquor", abv=abv, quantity_ml=1000)
            assert ingest._resolve_product(db, raw).id == right.id, name
        assert db.scalar(select(ProductVariant.id).order_by(ProductVariant.id.desc())) == before, "no new variant"
        assert db.scalar(select(Merge.reason).where(Merge.from_id == typo.id)) == "confirmed"

    def test_the_confirm_route_has_no_attribute_refusal(self):
        """The veto lives in `merges._conflicts`, which only the automatic paths read."""
        import inspect

        from app.services import merge_session

        source = inspect.getsource(merge_session)
        assert "identity_differs" not in source and "_conflicts" not in source
