"""Walking-skeleton + API smoke tests — no CLAP/torch required.

Cover the seams that must hold under the PLATFORM contract (SRE update): routes
are served at root (the gateway owns the /audio/v1 prefix), and auth is the
gateway-injected X-Tenant / X-Scopes identity (with a local-dev bearer fallback).
The emotion inference itself is monkeypatched — here we assert routing, auth
gating, the cache, and the deploy seams (/separate 503, /metrics). Env/DB/path
setup is in conftest.py.

    cd armada/api && ~/.virtualenvs/douanier/bin/python -m pytest tests/ -q
"""
from fastapi.testclient import TestClient

import app as APP
from engines import ears

client = TestClient(APP.app)

# the local-dev fallback: a bearer matching DOUANIER_DEV_TOKEN (conftest) → wildcard.
DEV = {"Authorization": "Bearer test-secret"}
# how the platform gateway actually calls us: injected identity headers.
GW = {"X-Tenant": "hexa", "X-Scopes": "emotion"}


def test_healthz_ok():
    r = client.get("/healthz")
    assert r.status_code == 200
    body = r.json()
    assert body["service"] == "douanier" and body["api"] == "audio"
    assert "emotion" in body["engines"] and "separate" in body["engines"]


def test_me_via_gateway_headers():
    r = client.get("/me", headers={"X-Tenant": "hexa", "X-Scopes": "emotion separate"})
    assert r.status_code == 200
    body = r.json()
    assert body["account"] == "hexa"
    assert set(body["scopes"]) == {"emotion", "separate"}


def test_me_via_dev_bearer_fallback():
    r = client.get("/me", headers=DEV)
    assert r.status_code == 200 and r.json()["account"] == "dev"


def test_me_requires_identity():
    assert client.get("/me").status_code == 401


def test_emotion_requires_identity():
    r = client.post("/analyze/emotion", files={"file": ("c.wav", b"RIFF....", "audio/wav")})
    assert r.status_code == 401


def test_emotion_rejects_bad_bearer():
    r = client.post("/analyze/emotion",
                    files={"file": ("c.wav", b"RIFF....", "audio/wav")},
                    headers={"Authorization": "Bearer nope"})
    assert r.status_code == 401


def test_emotion_scope_enforced():
    # an authenticated tenant WITHOUT the emotion scope is 403, not 401.
    r = client.post("/analyze/emotion",
                    files={"file": ("c.wav", b"RIFF....", "audio/wav")},
                    headers={"X-Tenant": "hexa", "X-Scopes": "features"})
    assert r.status_code == 403


def test_emotion_happy_path_and_cache(monkeypatch):
    # Stub the heavy engine call — we're testing the route/contract + cache, not the model.
    fake = {"valence": 0.42, "arousal": -0.10, "top": [["warm", 0.5]], "confidence": 0.2}
    calls = {"n": 0}

    def _fake(_p):
        calls["n"] += 1
        return fake
    monkeypatch.setattr(ears, "emotion_of", _fake)

    payload = b"UNIQUE-cache-probe-bytes-9f3a"   # undecodable → raw_ content id
    files = {"file": ("c.wav", payload, "audio/wav")}

    r1 = client.post("/analyze/emotion", files=files, headers=GW)
    assert r1.status_code == 200
    assert r1.headers["X-Douanier-Cache"] == "miss"
    assert r1.json()["emotion"]["valence"] == 0.42
    assert r1.json()["content_id"].startswith(("cid_", "raw_"))

    r2 = client.post("/analyze/emotion", files=files, headers=GW)
    assert r2.headers["X-Douanier-Cache"] == "hit"
    assert calls["n"] == 1            # engine ran ONCE; 2nd served from cache


def test_emotion_too_large(monkeypatch):
    monkeypatch.setattr(APP.config, "MAX_UPLOAD_MB", 0)  # everything is too large
    r = client.post("/analyze/emotion",
                    files={"file": ("c.wav", b"x" * 1024, "audio/wav")}, headers=GW)
    assert r.status_code == 413


def test_separate_is_503_until_gpu():
    r = client.post("/separate", headers={"X-Tenant": "hexa", "X-Scopes": "separate"})
    assert r.status_code == 503
    assert r.json()["detail"]["error"] == "compute_unavailable"


def test_metrics_exposes_prometheus_text():
    r = client.get("/metrics")
    assert r.status_code == 200
    assert "douanier_up 1" in r.text
    assert "douanier_requests_total" in r.text


def test_openapi_advertises_public_root_path():
    # root_path=/audio/v1 → clients see the real public paths in the spec.
    spec = client.get("/openapi.json").json()
    assert spec.get("servers") == [{"url": "/audio/v1"}]
