"""Natural keys round-trip and resolve by the detail, never by a local id (Stream K2, spec §2,
tests 12 and 13). What it cost: `propose --file` carried staging's row ids and could not load
on production (plan W13); a replay that matched by id would land decisions on the wrong rows
the moment the two hosts' sequences diverge. SQLite kit, no network."""
from __future__ import annotations

import uuid

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool

from app.models import (Account, Base, Brand, LEDGER_TABLES, Listing, ProductLine, ProductVariant, Redirect, Retailer, Shop,
                        Suggestion, AttributeAlias)
from app.models.places import Place, ShopPlace
from app.services.decisions import natural_keys as nk

TABLES = [Account.__table__, Brand.__table__, ProductLine.__table__, ProductVariant.__table__, AttributeAlias.__table__,
          Retailer.__table__, Shop.__table__, Listing.__table__, Suggestion.__table__, Place.__table__, ShopPlace.__table__,
          *LEDGER_TABLES]


@pytest.fixture
def db():
    engine = create_engine("sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool)
    Base.metadata.create_all(engine, tables=TABLES)
    with sessionmaker(bind=engine, autoflush=False, expire_on_commit=False)() as s:
        yield s


def world(db):
    chanel = Brand(slug="chanel", name="CHANEL")
    retailer = Retailer(slug="attenza", name="Attenza")
    db.add_all([chanel, retailer]); db.flush()
    pty = Shop(retailer_id=retailer.id, code="PTY1", iata="PTY", name="Attenza Panama", currency="USD")
    cdg = Shop(retailer_id=retailer.id, code="CDG-1", iata="CDG", name="Extime", currency="EUR")
    db.add_all([pty, cdg]); db.flush()
    line = ProductLine(brand_id=chanel.id, key="rouge allure", slug="chanel-rouge-allure", name="Rouge Allure")
    db.add(line); db.flush()
    a = ProductVariant(name="Rouge Allure 3.5 g / 99 Pirate", brand="CHANEL", brand_id=chanel.id, vertical="beauty",
                       match_key="k1", product_line_id=line.id, gtin="3145891612301", gtin_source="collected")
    b = ProductVariant(name="Rouge Allure 3.5 g / 99 Pirate", brand="CHANEL", brand_id=chanel.id, vertical="beauty", match_key="k2", product_line_id=line.id)
    db.add_all([a, b]); db.flush()
    db.add_all([Listing(variant_id=a.id, shop_id=pty.id, source_sku="12345"), Listing(variant_id=b.id, shop_id=cdg.id, source_sku="778:2")])
    db.flush()
    return chanel, line, a, b, pty, cdg


class TestRoundTrip:
    def test_every_entity_builds_and_parses_back(self, db):
        chanel, line, a, b, pty, cdg = world(db)
        for row, kind in ((chanel, "brand"), (line, "product_line"), (a, "product_variant")):
            key, detail = nk.build(row)
            assert nk.parse(key)[0] == kind, key
        listing = db.query(Listing).filter_by(source_sku="778:2").one()
        key, detail = nk.build(listing)
        assert key == "listing:attenza/CDG-1/778:2" and nk.parse(key) == ("listing", {"retailer": "attenza", "code": "CDG-1", "sku": "778:2"})
        wording = AttributeAlias(vertical="beauty", raw="elixir parfum intense", canonical="elixir", kind="concentration")
        key, detail = nk.build(wording)
        assert key == "wording:beauty|elixir parfum intense" and nk.parse(key)[1] == {"vertical": "beauty", "raw": "elixir parfum intense"}
        pair, detail = nk.pair_key("product", a.id, b.id, sides=(f"variant:{b.uid}", f"variant:{a.uid}"))
        kind, parts = nk.parse(pair)
        assert kind == "suggestion" and parts["level"] == "product" and parts["left"] < parts["right"]
        assert detail["left"] == parts["left"]

    def test_a_component_carrying_its_delimiter_is_refused(self, db):
        chanel, line, a, b, pty, cdg = world(db)
        bad = Listing(variant_id=a.id, shop_id=pty.id, source_sku="a/b")
        with pytest.raises(ValueError):
            nk.build(bad)
        with pytest.raises(ValueError):
            nk.parse("listing:attenza/PTY1")
        with pytest.raises(ValueError):
            nk.parse("nonsense")
        assert nk.parse("line:new:chanel-rouge-allure") == ("product_line", {"new": True, "slug": "chanel-rouge-allure"})


class TestResolution:
    def test_a_variant_resolves_by_uid_then_own_barcode_then_agreeing_listings_through_a_chain(self, db):
        chanel, line, a, b, pty, cdg = world(db)
        assert nk.resolve(db, "product_variant", f"variant:{a.uid}", {}) is a
        # No uid match (another host's row): the own barcode finds it; a barcode gained by merge does not count.
        assert nk.resolve(db, "product_variant", f"variant:{uuid.uuid4()}", {"gtin": "3145891612301"}) is a
        # Listings through a two-hop chain: b merged into a, a into c -> c.
        c = ProductVariant(name="Rouge Allure", brand="CHANEL", brand_id=chanel.id, vertical="beauty", match_key="k3", product_line_id=line.id)
        db.add(c); db.flush()
        b.merged_into_id, a.merged_into_id = a.id, c.id
        db.flush()
        found = nk.resolve(db, "product_variant", f"variant:{uuid.uuid4()}", {"listings": ["listing:attenza/CDG-1/778:2", "listing:attenza/PTY1/12345"]})
        assert found is c
        parked = nk.resolve(db, "product_variant", f"variant:{uuid.uuid4()}", {"listings": ["listing:attenza/PTY1/nope"]})
        assert isinstance(parked, nk.Parked) and parked.code == "LISTING_MISSING"

    def test_listings_on_two_live_variants_park_as_a_split(self, db):
        chanel, line, a, b, pty, cdg = world(db)
        parked = nk.resolve(db, "product_variant", f"variant:{uuid.uuid4()}", {"listings": ["listing:attenza/CDG-1/778:2", "listing:attenza/PTY1/12345"]})
        assert isinstance(parked, nk.Parked) and parked.code == "VARIANT_SPLIT" and sorted(parked.candidates) == sorted([a.id, b.id])

    def test_a_line_resolves_by_uid_then_slug_when_brand_and_key_agree_else_parks(self, db):
        chanel, line, a, b, pty, cdg = world(db)
        assert nk.resolve(db, "product_line", f"line:{line.uid}", {}) is line
        detail = {"brand_slug": "chanel", "slug": "chanel-rouge-allure", "key": "rouge allure", "name": "Rouge Allure"}
        assert nk.resolve(db, "product_line", f"line:{uuid.uuid4()}", detail) is line
        mismatch = nk.resolve(db, "product_line", f"line:{uuid.uuid4()}", {**detail, "key": "rouge allure velvet", "name": "Velvet"})
        assert isinstance(mismatch, nk.Parked) and mismatch.code == "LINE_MISMATCH"
        other = Brand(slug="dior", name="Dior"); db.add(other); db.flush()
        wrong_brand = nk.resolve(db, "product_line", f"line:{uuid.uuid4()}", {**detail, "brand_slug": "dior"})
        assert isinstance(wrong_brand, nk.Parked) and wrong_brand.code == "LINE_MISMATCH"
        # A new line's key finds the row holding the slug.
        assert nk.resolve(db, "product_line", "line:new:chanel-rouge-allure", {}) is line

    def test_redirects_are_read_last(self, db):
        chanel, line, a, b, pty, cdg = world(db)
        db.add(Redirect(from_slug="chanel-rouge-allure-old", kind="product_line", to_slug="chanel-rouge-allure")); db.flush()
        detail = {"brand_slug": "chanel", "slug": "chanel-rouge-allure-old", "key": "rouge allure"}
        assert nk.resolve(db, "product_line", f"line:{uuid.uuid4()}", detail) is line

    def test_a_wording_and_a_pair_are_minted_only_when_asked(self, db):
        chanel, line, a, b, pty, cdg = world(db)
        parked = nk.resolve(db, "attribute_wording", "wording:beauty|elixir parfum intense", {"kind": "concentration"})
        assert isinstance(parked, nk.Parked) and parked.code == "WORDING_MISSING"
        row = nk.resolve(db, "attribute_wording", "wording:beauty|elixir parfum intense", {"kind": "concentration"}, mint=True)
        assert isinstance(row, AttributeAlias) and row.canonical == "elixir" and row.kind == "concentration"
        pair = f"pair:product:variant:{a.uid}||variant:{b.uid}"
        assert isinstance(nk.resolve(db, "suggestion", pair, {}), nk.Parked)
        minted = nk.resolve(db, "suggestion", pair, {}, mint=True)
        assert isinstance(minted, Suggestion) and (minted.left_id, minted.right_id) == (min(a.id, b.id), max(a.id, b.id)) and minted.reason == "decided"
        assert nk.resolve(db, "suggestion", pair, {}) is minted
