"""Comment attachments (Interaction Standard addendum): upload + serve, with
the security posture proven — type allow-list, magic-byte agreement,
our-names-only serving (the traversal guard), size cap, and visibility
re-checked through the owning project on every read."""

from app.services import attachments as att
from tests.conftest import as_user
from tests.test_domain import make_project

PNG = b"\x89PNG\r\n\x1a\n" + b"\x00" * 24        # minimal png-shaped bytes
PDF = b"%PDF-1.4\n%fake\n"


def _up(client, iid, name, data, ctype="application/octet-stream"):
    return client.post(f"/api/projects/{iid}/attachments",
                       files={"file": (name, data, ctype)})


def test_upload_and_serve_roundtrip(client, kit):
    iid = make_project(client, kit)
    as_user(client, "cleo")
    r = _up(client, iid, "the header on my phone.png", PNG)
    assert r.status_code == 201, r.text
    a = r.json()
    assert a["is_image"]
    assert a["url"].startswith(f"/api/projects/{iid}/attachments/")
    assert a["markdown"] == f"![the header on my phone.png]({a['url']})"

    got = client.get(a["url"])
    assert got.status_code == 200
    assert got.headers["content-type"].startswith("image/png")
    assert got.headers["x-content-type-options"] == "nosniff"
    assert got.content == PNG

    # a PDF embeds as a LINK, not an image
    p = _up(client, iid, "brand guide.pdf", PDF).json()
    assert not p["is_image"] and p["markdown"].startswith("[brand guide.pdf](")
    assert client.get(p["url"]).headers["content-type"].startswith("application/pdf")


def test_type_and_content_discipline(client, kit):
    iid = make_project(client, kit)
    as_user(client, "cleo")
    # script-capable types never land, whatever they claim to be
    r = _up(client, iid, "logo.svg", b"<svg onload=alert(1)></svg>", "image/svg+xml")
    assert (r.status_code, r.json()["detail"]["error_code"]) == (400, "UNSUPPORTED_TYPE")
    assert _up(client, iid, "page.html", b"<script>", "text/html").status_code == 400
    # the extension must agree with the bytes
    r = _up(client, iid, "notreally.png", b"<script>alert(1)</script>" + b"\x00" * 8)
    assert (r.status_code, r.json()["detail"]["error_code"]) == (400, "CONTENT_MISMATCH")
    # empty and oversize
    assert _up(client, iid, "empty.png", b"").status_code == 400
    r = _up(client, iid, "huge.png", PNG + b"\x00" * att.MAX_BYTES)
    assert (r.status_code, r.json()["detail"]["error_code"]) == (413, "TOO_LARGE")


def test_serving_is_visibility_gated_and_traversal_proof(client, kit):
    iid = make_project(client, kit)
    as_user(client, "cleo")
    a = _up(client, iid, "shot.png", PNG).json()

    # an outsider can neither upload nor read — and learns nothing (404, never 403)
    as_user(client, "nate")
    assert _up(client, iid, "shot.png", PNG).status_code == 404
    assert client.get(a["url"]).status_code == 404

    # only our uuid.ext shape resolves — nothing outside the folder ever does
    as_user(client, "cleo")
    for bad in ("shot.png", a["name"].upper(), "0" * 32 + ".svg", "a" * 32 + ".png"):
        assert client.get(f"/api/projects/{iid}/attachments/{bad}").status_code == 404
    # an encoded slash is decoded BEFORE routing, so a traversal-shaped name
    # never reaches the attachment route at all — it falls through to the
    # SPA shell, which is not a file from the data volume.
    r = client.get(f"/api/projects/{iid}/attachments/..%2F..%2Fapp.db")
    assert r.status_code == 404 or r.headers["content-type"].startswith("text/html")
    assert r.content != PNG
    # the real one still serves for a member
    assert client.get(a["url"]).status_code == 200
