"""Protection des photos : aperçus filigranés signés, originaux réservés aux commandes payées."""
import io
import re
import uuid

from PIL import Image

from conftest import DEMO_CODE, OTHER_CODE, SCHOOL, family_dir, insert_order, item


async def _first_photo(client, code=DEMO_CODE, name="portrait.jpg"):
    photos = (await client.get(f"/api/portal/photos?code={code}")).json()["photos"]
    return next(p for p in photos if p["filename"] == name)


async def test_originals_are_not_served_as_static_files(client):
    for path in (f"/photos/{DEMO_CODE}/portrait.jpg", f"/thumbs/{DEMO_CODE}/portrait.webp"):
        assert (await client.get(path)).status_code == 404


async def test_preview_is_a_reduced_webp_not_the_original(client):
    p = await _first_photo(client)
    r = await client.get(p["url"])
    assert r.status_code == 200
    assert r.headers["content-type"] == "image/webp"
    img = Image.open(io.BytesIO(r.content))
    assert max(img.size) <= 1000          # l'original fait 1200 px
    from core import config
    original = (family_dir(SCHOOL, DEMO_CODE) / "portrait.jpg").read_bytes()
    assert r.content != original


async def test_preview_is_actually_watermarked(client):
    """Un aplat uni (l'original) doit présenter des variations une fois filigrané."""
    p = await _first_photo(client)
    img = Image.open(io.BytesIO((await client.get(p["url"])).content)).convert("RGB")
    assert len(set(img.getdata())) > 20


async def test_preview_rejects_missing_tampered_or_expired_signature(client):
    p = await _first_photo(client)
    base = p["url"].split("?")[0]
    assert (await client.get(base)).status_code == 422
    assert (await client.get(p["url"].replace("sig=", "sig=00"))).status_code == 403
    assert (await client.get(re.sub(r"exp=\d+", "exp=1", p["url"]))).status_code == 403


async def test_preview_signature_is_bound_to_the_file(client):
    p = await _first_photo(client)
    other = p["url"].replace("portrait.jpg", "fratrie.jpg")
    assert (await client.get(other)).status_code == 403


async def test_preview_cannot_escape_the_family_folder(client):
    p = await _first_photo(client)
    url = p["url"].replace("/portrait.jpg", "/..%2F..%2Ftest.db")
    assert (await client.get(url)).status_code in (403, 404)


def _paid_digital_order(code=DEMO_CODE, school=SCHOOL, photo="portrait.jpg", fmt="digital", price=7.0, **kw):
    oid = str(uuid.uuid4())
    insert_order(oid, code, school, [item(fmt=fmt, photo=photo, price=price)], **kw)
    return oid


async def _downloads(client, oid, code=DEMO_CODE):
    orders = (await client.get(f"/api/portal/orders?code={code}")).json()["orders"]
    return next(o for o in orders if o["id"] == oid)["downloads"]


async def test_paid_digital_order_gets_a_working_download_link(client):
    oid = _paid_digital_order()
    dl = await _downloads(client, oid)
    assert len(dl) == 1
    r = await client.get(dl[0]["url"])
    assert r.status_code == 200
    assert "attachment" in r.headers["content-disposition"]
    assert Image.open(io.BytesIO(r.content)).size == (1200, 800)   # l'original complet


async def test_unpaid_order_has_no_download(client):
    oid = _paid_digital_order(payment_status="unpaid")
    assert await _downloads(client, oid) == []


async def test_print_only_order_has_no_download(client):
    oid = _paid_digital_order(fmt="print-10x15", price=9.0)
    assert await _downloads(client, oid) == []


async def test_pack_includes_the_digital_file(client):
    oid = _paid_digital_order(fmt="pack", price=14.0)
    assert len(await _downloads(client, oid)) == 1


async def test_download_link_dies_if_order_is_cancelled_or_unpaid_afterwards(client):
    from core.db import get_db
    oid = _paid_digital_order()
    url = (await _downloads(client, oid))[0]["url"]
    with get_db() as conn:
        conn.execute("UPDATE orders SET status='cancelled' WHERE id=?", (oid,))
        conn.commit()
    assert (await client.get(url)).status_code == 403


async def test_download_signature_cannot_be_reused_for_another_file(client):
    oid = _paid_digital_order()
    url = (await _downloads(client, oid))[0]["url"]
    assert (await client.get(url.replace("portrait.jpg", "fratrie.jpg"))).status_code == 403


async def test_download_is_limited_to_files_in_the_order(client):
    """Même avec une signature valide, un fichier absent de la commande est refusé."""
    from services import media_service
    oid = _paid_digital_order()
    exp_url = media_service.download_url(oid, "fratrie.jpg")
    assert (await client.get(exp_url)).status_code == 403


async def test_download_expired_link_rejected(client):
    oid = _paid_digital_order()
    url = (await _downloads(client, oid))[0]["url"]
    assert (await client.get(re.sub(r"exp=\d+", "exp=1", url))).status_code == 403


async def test_multi_photo_order_gets_a_zip_with_every_original(client):
    import zipfile
    oid = str(uuid.uuid4())
    insert_order(oid, DEMO_CODE, SCHOOL, [item(fmt="digital", photo="portrait.jpg", price=7.0),
                                          item(fmt="digital", photo="fratrie.jpg", price=7.0)])
    orders = (await client.get(f"/api/portal/orders?code={DEMO_CODE}")).json()["orders"]
    order = next(o for o in orders if o["id"] == oid)
    assert order["zip_url"]
    r = await client.get(order["zip_url"])
    assert r.status_code == 200
    names = sorted(zipfile.ZipFile(io.BytesIO(r.content)).namelist())
    assert names == ["fratrie.jpg", "portrait.jpg"]


async def test_zip_is_refused_for_unpaid_orders_and_bad_signatures(client):
    from services import media_service
    oid = _paid_digital_order(payment_status="unpaid")
    assert (await client.get(media_service.download_zip_url(oid))).status_code == 403
    paid = _paid_digital_order()
    assert (await client.get(f"/api/portal/download-zip/{paid}?exp=9999999999&sig=bad")).status_code == 403
