import pytest
from fastapi.testclient import TestClient

from app.main import app

client = TestClient(app)
TENANT = "org_8c32bf3e-6a18-4739-9b1c-94c0cf11125f"


@pytest.mark.parametrize("path", [
    "/catalog/pair", "/catalog/categories", "/catalog/products",
    "/catalog/pairings", "/catalog/pairings/anchors",
    "/catalog/pairings/decide",
])
def test_routes_are_registered(path):
    assert path in set(app.openapi()["paths"])


def test_pair_requires_a_tenant():
    assert client.post("/catalog/pair", data={}).status_code == 422


def test_pair_returns_the_report(monkeypatch):
    # wait=true keeps the pre-job-polling contract: synchronous, report inline.
    from app.api import catalog
    monkeypatch.setattr(catalog, "pair_tenant", lambda t: {
        "products": 218, "pairs": 900, "servable": 700, "queued": 200,
        "by_type": {"similar": 400}})
    resp = client.post("/catalog/pair", data={"tenant_id": TENANT, "wait": "true"})
    assert resp.status_code == 200
    assert resp.json()["queued"] == 200


def test_a_job_failure_returns_502_with_no_detail(monkeypatch):
    # wait=true keeps the pre-job-polling contract: synchronous, 502 on failure.
    from app.api import catalog

    def boom(tenant_id):
        raise RuntimeError("embedding service unavailable")

    monkeypatch.setattr(catalog, "pair_tenant", boom)
    resp = client.post("/catalog/pair", data={"tenant_id": TENANT, "wait": "true"})
    assert resp.status_code == 502
    assert "unavailable" not in resp.text
    assert "RuntimeError" in resp.text


def test_categories_are_listed(monkeypatch):
    from app.api import catalog
    monkeypatch.setattr(catalog, "list_categories", lambda t: [
        {"category": "smartphones", "products": 16}])
    resp = client.get("/catalog/categories", params={"tenant_id": TENANT})
    assert resp.json()[0]["products"] == 16


def test_products_are_filterable_by_category(monkeypatch):
    from app.api import catalog
    seen = {}
    monkeypatch.setattr(catalog, "list_products",
                        lambda t, category=None, limit=50, offset=0:
                        seen.update(category=category) or {"products": [],
                                                           "total": 0})
    client.get("/catalog/products",
               params={"tenant_id": TENANT, "category": "smartphones"})
    assert seen["category"] == "smartphones"


def test_a_missing_product_is_404(monkeypatch):
    from app.api import catalog
    monkeypatch.setattr(catalog, "get_product", lambda t, k: None)
    resp = client.get("/catalog/products/nope", params={"tenant_id": TENANT})
    assert resp.status_code == 404


def test_a_product_key_containing_a_colon_survives_the_path(monkeypatch):
    # Every key in this system looks like "shopify:123".
    from app.api import catalog
    seen = {}
    monkeypatch.setattr(catalog, "get_product",
                        lambda t, k: seen.update(key=k) or {"product_key": k})
    client.get("/catalog/products/shopify:123", params={"tenant_id": TENANT})
    assert seen["key"] == "shopify:123"


def test_pairings_are_grouped_by_type(monkeypatch):
    from app.api import catalog
    monkeypatch.setattr(catalog, "pairings_for", lambda t, k: {
        "similar": [{"neighbor_key": "b", "score": 0.9, "servable": True,
                     "reasons": ["same category"]}],
        "complement": [], "upsell": []})
    resp = client.get("/catalog/products/a/pairings", params={"tenant_id": TENANT})
    assert set(resp.json()) == {"similar", "complement", "upsell"}


def test_the_pending_queue_is_reachable(monkeypatch):
    # Registered before /catalog/products/{key} could shadow it.
    from app.api import catalog
    monkeypatch.setattr(catalog, "list_pairings",
                        lambda t, limit=100, anchor_key=None, status="pending", category=None, score=None: {"pairings": [], "dropped": 0})
    assert client.get("/catalog/pairings",
                      params={"tenant_id": TENANT}).status_code == 200


def test_decisions_are_accepted_as_a_batch(monkeypatch):
    from app.api import catalog
    seen = {}
    monkeypatch.setattr(catalog, "record",
                        lambda t, decisions: seen.update(n=len(decisions))
                        or len(decisions))
    resp = client.post("/catalog/pairings/decide", json={
        "tenant_id": TENANT,
        "decisions": [{"anchor_key": "a", "neighbor_key": f"b{i}",
                       "pair_type": "similar", "decision": "approved"}
                      for i in range(3)]})
    assert resp.status_code == 200
    assert seen["n"] == 3


def test_an_invalid_decision_is_rejected_with_422(monkeypatch):
    from app.api import catalog

    def boom(tenant_id, decisions):
        raise ValueError("maybe is not a decision")

    monkeypatch.setattr(catalog, "record", boom)
    resp = client.post("/catalog/pairings/decide", json={
        "tenant_id": TENANT,
        "decisions": [{"anchor_key": "a", "neighbor_key": "b",
                       "pair_type": "similar", "decision": "maybe"}]})
    assert resp.status_code == 422


def test_the_admin_page_is_served():
    resp = client.get("/catalog/admin")
    assert resp.status_code == 200
    assert resp.headers["content-type"].startswith("text/html")
    assert "<html" in resp.text.lower()


def test_the_admin_page_needs_no_tenant():
    # The tenant is chosen in the page, not in the URL, so the page itself is
    # static and cacheable.
    assert client.get("/catalog/admin").status_code == 200


def test_a_missing_page_file_is_a_clear_500(monkeypatch):
    from app.api import catalog
    monkeypatch.setattr(catalog, "ADMIN_PAGE",
                        catalog.ADMIN_PAGE.parent / "does-not-exist.html")
    resp = client.get("/catalog/admin")
    assert resp.status_code == 500
    assert "admin page" in resp.text.lower()


CSV_BODY = (
    "Handle,Title,Body (HTML),Vendor,Type,Tags,Published,"
    "Option1 Name,Option1 Value,Option2 Name,Option2 Value,"
    "Variant SKU,Variant Inventory Qty,Variant Price,"
    "Variant Compare At Price,Image Src,Status\n"
    "shirt,Shirt,A shirt.,Brand,Shirts,tag,TRUE,Size,M,,,SKU,5,19.00,,"
    "https://x.example/s.jpg,active\n")


def test_the_import_route_is_registered():
    assert "/catalog/import/csv" in set(app.openapi()["paths"])


def test_import_does_not_require_a_currency(monkeypatch):
    # Consistent with connecting a product API, where currency is optional.
    from app.api import catalog
    seen = {}
    monkeypatch.setattr(catalog, "import_csv",
                        lambda t, text, ref, cur, dry_run=False:
                        seen.update(currency=cur) or {"imported": 1})

    resp = client.post("/catalog/import/csv",
                       data={"tenant_id": TENANT},
                       files={"file": ("p.csv", CSV_BODY, "text/csv")})
    assert resp.status_code == 200
    assert seen["currency"] is None


def test_import_returns_the_report(monkeypatch):
    from app.api import catalog
    monkeypatch.setattr(catalog, "import_csv",
                        lambda *a, **kw: {"imported": 1, "products": 1,
                                          "errors": [], "dry_run": False})
    resp = client.post("/catalog/import/csv",
                       data={"tenant_id": TENANT, "currency": "USD"},
                       files={"file": ("p.csv", CSV_BODY, "text/csv")})
    assert resp.status_code == 200
    assert resp.json()["imported"] == 1


def test_the_uploaded_filename_becomes_the_source_ref(monkeypatch):
    from app.api import catalog
    seen = {}
    monkeypatch.setattr(catalog, "import_csv",
                        lambda t, text, ref, cur, dry_run=False:
                        seen.update(ref=ref) or {"imported": 0})
    client.post("/catalog/import/csv",
                data={"tenant_id": TENANT, "currency": "USD"},
                files={"file": ("spring-2026.csv", CSV_BODY, "text/csv")})
    assert seen["ref"] == "spring-2026.csv"


def test_dry_run_is_passed_through(monkeypatch):
    from app.api import catalog
    seen = {}
    monkeypatch.setattr(catalog, "import_csv",
                        lambda t, text, ref, cur, dry_run=False:
                        seen.update(dry=dry_run) or {"imported": 0})
    client.post("/catalog/import/csv",
                data={"tenant_id": TENANT, "currency": "USD", "dry_run": "true"},
                files={"file": ("p.csv", CSV_BODY, "text/csv")})
    assert seen["dry"] is True


def test_a_malformed_file_is_a_422_not_a_500(monkeypatch):
    # A bad spreadsheet is the merchant's problem to fix, not a server fault.
    from app.api import catalog
    from app.services.catalog.csv_source import CsvFormatError

    def boom(*a, **kw):
        raise CsvFormatError("missing required column: Handle")

    monkeypatch.setattr(catalog, "import_csv", boom)
    resp = client.post("/catalog/import/csv",
                       data={"tenant_id": TENANT, "currency": "USD"},
                       files={"file": ("p.csv", "nonsense", "text/csv")})
    assert resp.status_code == 422
    assert "Handle" in resp.text


def test_the_sample_csv_is_downloadable():
    resp = client.get("/catalog/sample.csv")
    assert resp.status_code == 200
    assert "Handle,Title" in resp.text
    assert "attachment" in resp.headers.get("content-disposition", "")


def test_the_anchor_list_accepts_every_filter(monkeypatch):
    from app.api import catalog
    seen = {}
    monkeypatch.setattr(catalog, "list_anchors",
                        lambda t, limit=50, status="pending", category=None,
                        score=None: seen.update(status=status, category=category,
                                                score=score)
                        or {"anchors": [], "total": 0})

    resp = client.get("/catalog/pairings/anchors",
                      params={"tenant_id": TENANT, "status": "approved",
                              "category": "smartphones", "score": "high"})
    assert resp.status_code == 200
    assert seen == {"status": "approved", "category": "smartphones",
                    "score": "high"}


def test_a_bad_filter_is_422_not_500(monkeypatch):
    # A typo in a query string is the caller's mistake, not a server fault.
    from app.api import catalog

    def reject(*a, **kw):
        raise ValueError("unknown status: maybe")

    monkeypatch.setattr(catalog, "list_anchors", reject)
    resp = client.get("/catalog/pairings/anchors",
                      params={"tenant_id": TENANT, "status": "maybe"})
    assert resp.status_code == 422
