"""What a merge inherits: the barcode and the image, never invented (Stream M, task M7).

Over the in-memory SQLite, as the alias tests. Pinned: a survivor without a barcode gains
the merged row's and is marked as having done so; a survivor with its own keeps it; two
different barcodes reach a merge only with a person's note, and the record says which won,
which was dropped and why; two rows without a barcode merge into one without a barcode;
the image passes with its provenance and a survivor's own image is kept; since Stream AW3.2
the picture goes through `imagery.set_image`, so a supplied picture on the survivor is kept
against a fetched one and a supplied picture on the merged row replaces a fetched one.
"""

from __future__ import annotations

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

from app.models import Account, Award, Base, Brand, Listing, Shop, Suggestion, ProductVariant, ProductLine, Merge, Retailer
from app.models.decisions import LEDGER_TABLES
from app.models.places import Place, ShopPlace
from app.services import imagery, merge_session, merges

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


@pytest.fixture
def db():
    engine = create_engine("sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool)
    Base.metadata.create_all(engine, tables=TABLES)
    factory = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False, future=True)
    with factory() as session:
        session.add(Account(id=1, username="rian", display_name="rian"))
        yield session


def product(db, pid, name, **kw):
    row = ProductVariant(id=pid, name=name, brand="Dior", vertical="beauty", quantity_ml=100, match_key=f"k{pid}", attributes={}, **kw)
    db.add(row)
    db.flush()
    return row


class TestBarcode:
    def test_a_survivor_without_a_barcode_gains_the_merged_rows_and_is_marked(self, db):
        keeper = product(db, 1, "Sauvage 100ml")
        other = product(db, 2, "Sauvage EDT 100ml", gtin="3348901250412")
        assert merges.merge_product_variants(db, keeper, [other], reason="confirmed", merged_by=1) == 1
        assert (keeper.gtin, keeper.gtin_source) == ("3348901250412", "merge")
        assert other.gtin is None and other.merged_into_id == 1
        record = db.scalar(select(Merge))
        assert record.detail["barcode"] == {"gained": "3348901250412", "from": 2}

    def test_a_survivor_with_its_own_keeps_it_and_two_different_ones_are_recorded_with_why(self, db):
        keeper = product(db, 1, "Sauvage 100ml", gtin="3348901250412")
        other = product(db, 2, "Sauvage EDT 100ml", gtin="3348901250429")
        merges.merge_product_variants(db, keeper, [other], reason="confirmed", merged_by=1, note="the shop page shows 0412")
        assert (keeper.gtin, keeper.gtin_source) == ("3348901250412", None)
        record = db.scalar(select(Merge))
        assert record.detail["barcode"] == {"kept": "3348901250412", "dropped": "3348901250429", "why": "the shop page shows 0412"}
        assert record.detail["note"] == "the shop page shows 0412"

    def test_two_rows_without_a_barcode_never_gain_one(self, db):
        keeper, other = product(db, 1, "Sauvage 100ml"), product(db, 2, "Sauvage 100 ml")
        merges.merge_product_variants(db, keeper, [other], reason="confirmed", merged_by=1)
        assert keeper.gtin is None and keeper.gtin_source is None
        assert "barcode" not in db.scalar(select(Merge)).detail

    def test_the_session_refuses_two_barcodes_without_a_note(self, db):
        product(db, 1, "Sauvage 100ml", gtin="3348901250412")
        product(db, 2, "Sauvage EDT 100ml", gtin="3348901250429")
        db.add(Suggestion(level="product", left_id=1, right_id=2, reason="gtin_differs", score=0.5))
        db.commit()
        with pytest.raises(merge_session.Refused) as refused:
            merge_session.confirm(db, 1, decided_by=1)
        assert refused.value.code == "NOTE_REQUIRED"
        result = merge_session.confirm(db, 1, decided_by=1, keep="left", note="the shop page shows 0412")
        assert result["applied"]["rows_merged"] == 1
        assert db.get(Suggestion, 1).decision == "same"


class TestImage:
    def test_the_image_passes_with_its_provenance_and_an_own_image_is_kept(self, db):
        keeper = product(db, 1, "Sauvage 100ml")
        other = product(db, 2, "Sauvage EDT 100ml", image_url="https://img/x.jpg", thumb_url="https://img/x_t.jpg",
                        image_source=imagery.PUBLIC_OFF_BARCODE, image_level="variant")
        merges.merge_product_variants(db, keeper, [other], reason="confirmed", merged_by=1)
        assert (keeper.image_url, keeper.thumb_url, keeper.image_source, keeper.image_level) == (
            "https://img/x.jpg", "https://img/x_t.jpg", imagery.PUBLIC_OFF_BARCODE, "variant")
        assert keeper.image_set_at is not None
        assert db.scalar(select(Merge)).detail["image"] == {"from": 2, "source": imagery.PUBLIC_OFF_BARCODE}
        own = product(db, 3, "Sauvage Parfum 100ml", image_url="https://img/own.jpg", image_source=imagery.PUBLIC_OFF_NAME)
        third = product(db, 4, "Sauvage Parfum 100 ml", image_url="https://img/other.jpg", image_source=imagery.PUBLIC_OFF_BARCODE)
        merges.merge_product_variants(db, own, [third], reason="confirmed", merged_by=1)
        assert (own.image_url, own.image_source) == ("https://img/own.jpg", imagery.PUBLIC_OFF_NAME)

    def test_a_legacy_source_on_the_merged_row_arrives_in_the_vocabulary(self, db):
        # The state between the deploy and `backfill image_sources`: a merge must not raise on
        # the old string, and the survivor gets the vocabulary's word.
        keeper = product(db, 1, "Sauvage 100ml")
        other = product(db, 2, "Sauvage EDT 100ml", image_url="https://img/x.jpg", image_source="Open Food Facts (name)")
        merges.merge_product_variants(db, keeper, [other], reason="confirmed", merged_by=1)
        assert (keeper.image_url, keeper.image_source, keeper.image_level) == ("https://img/x.jpg", imagery.PUBLIC_OFF_NAME, "variant")

    def test_a_supplied_picture_on_the_survivor_is_kept_and_one_on_the_merged_row_wins(self, db):
        admin = "admin:william-grant-via-adam"
        keeper = product(db, 1, "Glenfiddich 12 70cl", image_url="/uploads/images/variant/1-abcd1234.webp",
                         image_source=admin, image_level="variant", image_licence="Brand-supplied")
        other = product(db, 2, "Glenfiddich 12 700ml", image_url="https://img/off.jpg", image_source=imagery.PUBLIC_OFF_BARCODE, image_level="variant")
        merges.merge_product_variants(db, keeper, [other], reason="confirmed", merged_by=1)
        assert (keeper.image_url, keeper.image_source, keeper.image_licence) == ("/uploads/images/variant/1-abcd1234.webp", admin, "Brand-supplied")
        assert "image" not in db.scalar(select(Merge).where(Merge.from_id == 2)).detail
        fetched = product(db, 3, "Glenfiddich 18 70cl", image_url="https://img/off18.jpg", image_source=imagery.PUBLIC_OFF_NAME, image_level="variant")
        supplied = product(db, 4, "Glenfiddich 18 700ml", image_url="/uploads/images/variant/4-ef567890.webp", thumb_url="/uploads/images/variant/4-ef567890-thumb.webp",
                           image_source=admin, image_level="variant", image_attribution="William Grant & Sons")
        merges.merge_product_variants(db, fetched, [supplied], reason="confirmed", merged_by=1)
        assert (fetched.image_url, fetched.thumb_url, fetched.image_source, fetched.image_attribution) == (
            "/uploads/images/variant/4-ef567890.webp", "/uploads/images/variant/4-ef567890-thumb.webp", admin, "William Grant & Sons")
        assert db.scalar(select(Merge).where(Merge.from_id == 4)).detail["image"] == {"from": 4, "source": admin}
