"""/sources (#29) + artifact store (#27): URL validation, the fetch handler
(yt-dlp seam mocked), idempotent born-done fast path, and artifact serving."""
import wave
from pathlib import Path

import pytest
from fastapi.testclient import TestClient

import app
import cache
import config
import db
import jobs
import worker
from engines import sources as sources_eng

client = TestClient(app.app)
GW = {"X-Tenant": "hexa", "X-Scopes": "api:audio:*"}


@pytest.fixture(autouse=True)
def _clean():
    db.init()
    with db.cursor() as c:
        c.execute("DELETE FROM jobs")
        c.execute("DELETE FROM cache")
        c.execute("DELETE FROM aliases")
    yield


def _write_wav(path: Path, seconds=0.2, sr=44100):
    """A tiny silent stereo WAV so soundfile.info / content_id work without network."""
    n = int(seconds * sr)
    with wave.open(str(path), "wb") as w:
        w.setnchannels(2)
        w.setsampwidth(2)
        w.setframerate(sr)
        w.writeframes(b"\x00\x00\x00\x00" * n)


@pytest.fixture
def fake_fetch(monkeypatch):
    """Replace the yt-dlp network call with a synthetic WAV writer."""
    def _fake(url, outdir):
        p = Path(outdir) / "src.wav"
        _write_wav(p)
        return p
    monkeypatch.setattr(sources_eng, "_fetch_audio_wav", _fake)
    return _fake


# ── URL validation ───────────────────────────────────────────────────────────
@pytest.mark.parametrize("url", [
    "https://www.youtube.com/watch?v=abc123",
    "http://soundcloud.com/artist/track",
])
def test_normalize_accepts_public_http(url):
    assert sources_eng.normalize_url(url).startswith(("http://", "https://"))


def test_normalize_lowercases_host_and_drops_fragment():
    out = sources_eng.normalize_url("https://YouTube.com/watch?v=x#t=30")
    assert out == "https://youtube.com/watch?v=x"


@pytest.mark.parametrize("bad", [
    "file:///etc/passwd",
    "ftp://example.com/x",
    "http://localhost/x",
    "http://127.0.0.1/x",
    "http://10.0.0.5/x",
    "http://169.254.169.254/latest/meta-data",   # the classic cloud-metadata SSRF
    "",
])
def test_normalize_rejects_ssrf_and_nonhttp(bad):
    with pytest.raises(sources_eng.SourceError):
        sources_eng.normalize_url(bad)


# ── the worker handler (fetch seam mocked) ──────────────────────────────────────
def test_handler_fetches_stores_and_aliases(fake_fetch):
    jid = jobs.enqueue("hexa", "sources",
                       {"url": "https://youtube.com/watch?v=abc"})["job_id"]
    worker.run_one(jobs.claim())

    done = jobs.get(jid)
    assert done["status"] == "done"
    res = done["result"]
    assert res["content_id"].startswith(("cid_", "raw_"))
    assert res["artifact_ref"] == f"{res['content_id']}/source.wav"
    assert res["sample_rate"] == 44100 and res["channels"] == 2

    # alias registered → URL resolves to the content id; artifact on disk
    assert cache.resolve_alias("https://youtube.com/watch?v=abc") == res["content_id"]
    assert (config.ARTIFACTS_ROOT / res["content_id"] / "source.wav").is_file()


# ── submit endpoint ──────────────────────────────────────────────────────────
def test_submit_returns_202_pending_job():
    r = client.post("/sources", headers=GW,
                    json={"url": "https://youtube.com/watch?v=zzz"})
    assert r.status_code == 202
    body = r.json()
    assert body["type"] == "sources" and body["status"] == "pending"


def test_submit_rejects_bad_url_with_422():
    r = client.post("/sources", headers=GW, json={"url": "file:///etc/passwd"})
    assert r.status_code == 422


def test_submit_is_idempotent_born_done_after_first_fetch(fake_fetch):
    url = "https://youtube.com/watch?v=dedupe"
    # first submit → run it through the worker
    first = client.post("/sources", headers=GW, json={"url": url}).json()
    worker.run_one(jobs.claim())
    assert jobs.get(first["job_id"])["status"] == "done"

    # second submit of the same URL → born done immediately, no new pending job
    second = client.post("/sources", headers=GW, json={"url": url}).json()
    assert second["status"] == "done"
    assert second["result"]["cached"] is True
    assert jobs.claim() is None            # nothing queued for the worker


# ── artifact serving (#27) ──────────────────────────────────────────────────────
def test_artifact_download_and_guards(fake_fetch):
    jobs.enqueue("hexa", "sources", {"url": "https://youtube.com/watch?v=art"})
    worker.run_one(jobs.claim())
    cid = cache.resolve_alias("https://youtube.com/watch?v=art")

    r = client.get(f"/artifacts/{cid}/source.wav", headers=GW)
    assert r.status_code == 200 and r.content[:4] == b"RIFF"   # a real WAV

    assert client.get(f"/artifacts/{cid}/missing.wav", headers=GW).status_code == 404
    # path traversal is refused (resolve() rejects → 404, never escapes the root)
    assert client.get("/artifacts/..%2f..%2fetc/passwd", headers=GW).status_code == 404


def test_artifact_uses_x_accel_when_enabled(fake_fetch, monkeypatch):
    jobs.enqueue("hexa", "sources", {"url": "https://youtube.com/watch?v=accel"})
    worker.run_one(jobs.claim())
    cid = cache.resolve_alias("https://youtube.com/watch?v=accel")

    monkeypatch.setattr(config, "X_ACCEL", True)
    r = client.get(f"/artifacts/{cid}/source.wav", headers=GW)
    assert r.status_code == 200
    assert r.headers["X-Accel-Redirect"] == f"{config.X_ACCEL_LOCATION}/{cid}/source.wav"


# ── healthz reflects yt-dlp presence ────────────────────────────────────────────
def test_healthz_reports_sources_engine():
    eng = client.get("/healthz").json()["engines"]
    assert "sources" in eng and isinstance(eng["sources"]["available"], bool)
