from app.services.pairing.job import MAX_NEIGHBORS_PER_TYPE, merchant_pairs, score_pair


def p(key, category="phones", accessory=False, price=50000, relations=None,
      attrs=None):
    return {"product_key": key, "name": key, "category": category,
            "is_accessory": accessory, "price_cents": price, "rating": 4.0,
            "attributes": [{"key": "color", "value": "black"}]
                          if attrs is None else attrs,
            "tenant_relations": relations or {}}


VECTORS = {"a": [1.0, 0.0], "b": [1.0, 0.0], "case": [0.7, 0.7]}


def test_one_pair_can_carry_several_types():
    # The same two products can be both similar and an upsell.
    results = score_pair(p("a", price=50000), p("b", price=90000), VECTORS)
    assert {r["pair_type"] for r in results} >= {"similar", "upsell"}


def test_an_unrelated_pair_yields_nothing():
    # Different category, no price signal in common, and no shared colour --
    # score_complement's "outfit" branch only fires on colour compatibility
    # (>= 0.7), so a pair with no attributes at all has nothing to key on.
    assert score_pair(p("a", "phones", attrs=[]),
                      p("b", "groceries", attrs=[]), VECTORS) == []


def test_every_result_carries_the_full_shape():
    for result in score_pair(p("a", price=50000), p("b", price=90000), VECTORS):
        assert set(result) >= {"anchor_key", "neighbor_key", "pair_type",
                               "score", "confidence", "source", "reasons"}


def test_a_missing_vector_does_not_crash_the_pair():
    # An embedding failure degrades to attribute-only scoring, not to an abort.
    assert isinstance(score_pair(p("x"), p("y"), {}), list)


def test_merchant_relations_become_high_confidence_complements():
    products = [p("a", relations={"complementary": ["b"]}), p("b")]
    pairs = merchant_pairs(products)
    assert len(pairs) == 1
    assert pairs[0]["source"] == "merchant"
    assert pairs[0]["confidence"] == 1.0
    assert pairs[0]["pair_type"] == "complement"


def test_a_merchant_relation_to_a_missing_product_is_dropped():
    # The referenced product may have been deleted or filtered as ineligible.
    assert merchant_pairs([p("a", relations={"complementary": ["ghost"]})]) == []


def test_neighbors_are_capped_per_type():
    assert MAX_NEIGHBORS_PER_TYPE == 8
