"""The attribute registry and the one accessor (Stream K2; plan W7, W8; spec test 19). What it
cost: a hand-kept guard list and a hand-kept enrich list that agreed by luck, and a new kind
meaning a column and a migration. Pure logic plus the SQLite kit; no network."""
from __future__ import annotations

from types import SimpleNamespace

import pytest

from app.services import attributes as A
from app.services.collectors.base import RawListing
from app.services.ingest import enrich_from_raw
from app.services.quantity import from_stored


def variant(**kw):
    base = dict(id=1, name="Blue Label 1L", vertical="liquor", category="Whisky", attributes={}, abv=None, country_of_origin=None,
                is_exclusive=False, quantity_value=1000.0, quantity_unit="ml", pack_count=None, pack_unit_value=None, form="single",
                set_contents=None, quantity_state="stated", quantity_ml=1000, quantity_stated_value=None, quantity_stated_unit=None,
                brand="Johnnie Walker", product_line_id=None)
    base.update(kw)
    return SimpleNamespace(**base)


class TestTheRegistry:
    def test_every_kind_names_its_storage_and_the_marked_kinds_read_the_k1_shape(self):
        for kind in A.KINDS.values():
            assert kind.storage in A.Storage and kind.identity in ("always", "never") and kind.display in ("picked", "shown", "fact")
            if kind.storage is A.Storage.COLUMN:
                assert kind.column
        v = variant(vertical="beauty", category="Perfume", attributes={"attribute": "edp intense", "attribute_kind": "concentration"})
        assert A.get(v, "concentration") == "edp intense" and A.get(v, "color") is None
        assert [a.kind for a in A.of(v)] == ["quantity", "concentration"]

    def test_set_writes_the_storage_the_registry_names_and_validates(self):
        v = variant()
        A.set(v, "abv", "40")
        assert v.abv == 40
        A.set(v, "age", 12)
        assert v.attributes == {"age": 12}
        A.set(v, "cask", "Sherry Oak")
        assert v.attributes["cask"] == "Sherry Oak"
        A.set(v, "concentration", " EDP Intense ")
        assert v.attributes["attribute"] == "edp intense" and v.attributes["attribute_kind"] == "concentration"
        A.set(v, "concentration", None)
        assert "attribute" not in v.attributes and "attribute_kind" not in v.attributes
        with pytest.raises(ValueError):
            A.set(v, "quantity", {"value": 70, "unit": "cl"})
        A.set(v, "quantity", {"value": 700, "unit": "ml"})
        assert (v.quantity_value, v.quantity_unit, v.quantity_ml, v.quantity_state) == (700.0, "ml", 700, "stated")
        with pytest.raises(ValueError):
            A.set(v, "abv", "forty")
        with pytest.raises(KeyError):
            A.get(v, "no_such_kind")

    def test_a_shop_option_is_a_kind_on_sight_certain_identity_and_picked(self):
        kind = A.register_option("Talla")
        assert kind.name == "option:talla" and kind.certain and kind.identity == "always" and kind.display == "picked"
        v = variant(attributes={"option:talla": "6.0", "option:color": "Plateado"})
        A.set(v, "option:talla", "7.0")
        assert v.attributes["option:talla"] == "7.0"
        kinds = [a.kind for a in A.of(v)]
        assert "option:talla" in kinds and "option:color" in kinds

    def test_the_identity_part_is_sorted_by_kind_and_the_quantity_slot_knows_n_a_from_unknown(self, monkeypatch):
        v = variant(attributes={"cask": "Sherry", "age": 12, "edition": "Gold"})
        assert A.identity_part(v) == "age=12|cask=sherry|edition=gold"
        assert A.quantity_slot(v) == "1000ml"
        assert A.quantity_slot(variant(quantity_state="none", quantity_value=None, quantity_unit=None, quantity_ml=None)) == "unknown"
        monkeypatch.setattr(A, "QUANTITY_NOT_APPLICABLE", frozenset({("accessories", "Watches")}))
        assert A.quantity_slot(variant(vertical="accessories", category="Watches", quantity_state="none")) == "n/a"
        assert A.kinds_for("liquor", "Whisky") and all(k.name != "concentration" for k in A.kinds_for("liquor", "Whisky"))

    def test_guarded_columns_are_derived_from_the_registry(self):
        assert A.GUARDED_COLUMNS["attribute:quantity"] == A.QUANTITY_COLUMNS == A.GUARDED_COLUMNS["quantity"]
        assert A.GUARDED_COLUMNS["attribute:abv"] == frozenset({"abv"})
        assert A.GUARDED_COLUMNS["attribute:concentration"] == frozenset()
        assert A.guarded_for({"name": object(), "attribute:country_of_origin": object()}) == frozenset({"name", "country_of_origin"})


class TestEnrichNeverWritesOverAGuardedColumn:
    def test_a_decided_empty_abv_survives_a_sighting_that_states_43(self):
        v = variant(abv=None)
        raw = RawListing(source_sku="x", name="Blue Label 1L", price=1.0, currency="EUR", shop_code="S", brand="JW", abv=43.0, country_of_origin="UK")
        q = from_stored(1000, "ml", state="stated")
        enrich_from_raw(v, raw, q, {"attribute:abv": SimpleNamespace(value=None, effect="set")})
        assert v.abv is None and v.country_of_origin == "UK"
        enrich_from_raw(v, raw, q, {})
        assert v.abv == 43.0

    def test_a_decided_quantity_is_never_refilled(self):
        v = variant(quantity_state="none", quantity_value=None, quantity_unit=None, quantity_ml=None)
        raw = RawListing(source_sku="x", name="Blue Label", price=1.0, currency="EUR", shop_code="S", brand="JW", quantity_ml=750)
        q = from_stored(750, "ml", state="stated")
        enrich_from_raw(v, raw, q, {"attribute:quantity": object()})
        assert v.quantity_state == "none" and v.quantity_ml is None
        enrich_from_raw(v, raw, q, {})
        assert v.quantity_state == "stated" and v.quantity_ml == 750
