"""Tests for related / upsell / cross-sell association discovery and channel mapping."""

from __future__ import annotations

from db.association_rules import (
    KitchenCabinetAssociationRules,
    ProductSnapshot,
    extract_manual_overrides_from_payload,
    shopify_discovery_buckets,
)
from magento.association_sync import associations_from_row, execute_set_associations
from shopify.association_sync import build_discovery_metafields


def _catalog() -> list[ProductSnapshot]:
    return [
        ProductSnapshot("W1", "Anna Caramel Harvest", "Wall Cabinets", name="Wall 1", price=100),
        ProductSnapshot("W2", "Anna Caramel Harvest", "Wall Cabinets", name="Wall 2", price=120),
        ProductSnapshot("B1", "Anna Caramel Harvest", "Base Cabinets", name="Base 1", price=200),
        ProductSnapshot("A1", "Anna Caramel Harvest", "Accessories", name="Acc 1", price=40),
        ProductSnapshot("P1", "Anna Caramel Harvest", "Panels and Fillers", name="Panel 1", price=50),
        ProductSnapshot("M1", "Anna Caramel Harvest", "Mouldings", name="Mould 1", price=30),
        ProductSnapshot("W9", "Other Collection", "Wall Cabinets", name="Other Wall", price=110),
    ]


def test_related_same_collection_and_category():
    rules = KitchenCabinetAssociationRules(cap=5)
    related = rules.related_targets(_catalog()[0], _catalog())
    assert related == ["W2"]


def test_upsell_wall_to_finishing_siblings():
    rules = KitchenCabinetAssociationRules(cap=5)
    upsell = rules.upsell_targets(_catalog()[0], _catalog())
    assert set(upsell) == {"A1", "P1", "M1"}
    assert len(upsell) <= 5


def test_crosssell_wall_to_base_and_tall():
    rules = KitchenCabinetAssociationRules(cap=5)
    cross = rules.crosssell_targets(_catalog()[0], _catalog())
    assert cross == ["B1"]


def test_manual_override_columns_parsed():
    overrides = extract_manual_overrides_from_payload(
        {
            "Related SKUs": "X1, X2",
            "upsell_skus": "Y1|Y2",
            "Cross Sell Products": "Z1",
        }
    )
    assert overrides["related"] == ["X1", "X2"]
    assert overrides["upsell"] == ["Y1", "Y2"]
    assert overrides["crosssell"] == ["Z1"]


def test_shopify_discovery_buckets_map_upsell_to_complementary():
    buckets = shopify_discovery_buckets(
        {"related": ["W2"], "upsell": ["A1", "P1"], "crosssell": ["B1"]}
    )
    assert buckets["related_products"] == ["W2"]
    assert buckets["complementary_products"] == ["A1", "P1", "B1"]


def test_build_discovery_metafields_uses_product_gids():
    metafields = build_discovery_metafields(
        "gid://shopify/Product/1",
        {"related": ["W2"], "upsell": ["A1"], "crosssell": []},
        remote_ids_by_sku={"W2": "gid://shopify/Product/22", "A1": "33"},
    )
    assert metafields[0]["key"] == "related_products"
    assert '"gid://shopify/Product/22"' in metafields[0]["value"]
    assert metafields[1]["key"] == "complementary_products"
    assert '"gid://shopify/Product/33"' in metafields[1]["value"]


def test_magento_execute_set_associations_adds_and_removes():
    class FakeClient:
        def __init__(self):
            self.links = {
                "related": [{"linked_product_sku": "OLD"}],
                "upsell": [],
                "crosssell": [],
            }
            self.assigned = []
            self.removed = []

        def get_product_links(self, sku, link_type):
            return 200, list(self.links.get(link_type) or []), None

        def assign_product_links(self, sku, items):
            self.assigned.append((sku, items))
            return 200, True, None

        def remove_product_link(self, sku, link_type, linked_product_sku):
            self.removed.append((sku, link_type, linked_product_sku))
            return 200, None

    client = FakeClient()
    row = {
        "sku": "W1",
        "association_fields": {"related": ["W2"], "upsell": ["A1"], "crosssell": ["B1"]},
    }
    result = execute_set_associations(client, "W1", row)
    assert result["status"] == "success"
    assert ("W1", "related", "OLD") in client.removed
    assert any(items and items[0]["link_type"] == "related" for _, items in client.assigned)


def test_associations_from_row_accepts_csv_columns():
    parsed = associations_from_row(
        {"related_skus": "A,B", "upsell_skus": "C", "crosssell_skus": ""}
    )
    assert parsed["related"] == ["A", "B"]
    assert parsed["upsell"] == ["C"]
    assert parsed["crosssell"] == []


def test_catalog_index_and_reverse_peers_are_collection_scoped():
    from db.association_rules import KitchenCabinetAssociationRules, build_catalog_index

    catalog = _catalog()
    index = build_catalog_index(catalog)
    rules = KitchenCabinetAssociationRules(cap=5)
    reverse = rules.reverse_peers_for_source(catalog[0], index)  # W1 Wall
    assert "W2" in reverse["related"]
    assert "W9" not in reverse["related"]  # other collection
    # Accessories/Base cross-sell structural cabinets, so they pick up W1 in reverse.
    assert "A1" in reverse["crosssell"]
    assert "B1" in reverse["crosssell"]


def test_discover_supports_offset_limit(monkeypatch):
    from db import master_associations as ma

    catalog = _catalog()

    class FakeSession:
        pass

    monkeypatch.setattr(ma, "_load_catalog_snapshots", lambda session, skus=None: catalog)
    monkeypatch.setattr(ma, "_variant_exclusion_sets", lambda session: {})
    monkeypatch.setattr(ma, "associations_by_source", lambda session, skus: {})

    result = ma.discover_associations(
        FakeSession(),
        dry_run=True,
        bidirectional=False,
        offset=0,
        limit=2,
    )
    assert result["sources"] == 2
    assert result["has_more"] is True
    assert result["total_candidates"] == len(catalog)
    assert result["next_offset"] == 2


def test_discover_passes_target_skus_to_loader(monkeypatch):
    from db import master_associations as ma

    catalog = _catalog()
    seen = {}

    class FakeSession:
        pass

    def fake_loader(session, skus=None):
        seen["skus"] = list(skus or [])
        return catalog

    monkeypatch.setattr(ma, "_load_catalog_snapshots", fake_loader)
    monkeypatch.setattr(ma, "_variant_exclusion_sets", lambda session: {})
    monkeypatch.setattr(ma, "associations_by_source", lambda session, skus: {})

    ma.discover_associations(
        FakeSession(),
        skus=["W1"],
        dry_run=True,
        bidirectional=False,
    )

    assert seen["skus"] == ["W1"]
