"""Photo magique : garde-fous (consentement, quota, thème) et génération simulée via MockTransport."""
import io
import types

import httpx
import pytest
from PIL import Image

import ia
from core import config
from conftest import DEMO_CODE, NOCONSENT_CODE, SCHOOL, family_dir


def _jpeg() -> bytes:
    buf = io.BytesIO()
    Image.new("RGB", (800, 600), (200, 40, 40)).save(buf, "JPEG")
    return buf.getvalue()


@pytest.fixture
def storyface(monkeypatch):
    """Active l'IA et remplace storyface.ai par un faux serveur local."""
    calls = {"generate": 0}

    def handler(request: httpx.Request) -> httpx.Response:
        path = request.url.path
        if path.endswith("/api/v2/themes"):
            return httpx.Response(200, json={"themes": [{"id": "91", "name": "Astronaut", "name_fr": "Astronaute"}]})
        if path.endswith("/picasso-fast-seed"):
            calls["generate"] += 1
            return httpx.Response(200, json={"result_url": "https://cdn.test/result.jpg"})
        if path.endswith("/result.jpg"):
            return httpx.Response(200, content=_jpeg())
        return httpx.Response(404)

    real = httpx.AsyncClient

    def factory(*a, **kw):
        kw["transport"] = httpx.MockTransport(handler)
        return real(*a, **kw)

    monkeypatch.setattr(config, "STORYFACE_API_KEY", "sf_test")
    monkeypatch.setattr(ia, "httpx", types.SimpleNamespace(AsyncClient=factory, HTTPError=httpx.HTTPError))
    monkeypatch.setitem(ia._themes_cache, "themes", [])
    monkeypatch.setitem(ia._themes_cache, "at", 0.0)
    yield calls
    for f in family_dir(SCHOOL, DEMO_CODE).glob("ia_*"):
        f.unlink()


def _body(code=DEMO_CODE, photo="portrait.jpg", theme="91"):
    return {"family_code": code, "photo_filename": photo, "theme_id": theme}


async def test_ia_disabled_without_api_key(client):
    assert (await client.post("/api/portal/magic", json=_body())).status_code == 503


async def test_ia_requires_consent(client, storyface):
    from core.db import get_db
    with get_db() as conn:
        conn.execute("UPDATE families SET consent_at=NULL WHERE code=?", (NOCONSENT_CODE,))
        conn.commit()
    r = await client.post("/api/portal/magic", json=_body(NOCONSENT_CODE, "solo.jpg"))
    assert r.status_code == 403
    assert storyface["generate"] == 0


async def test_ia_rejects_unknown_family_photo_and_theme(client, storyface):
    assert (await client.post("/api/portal/magic", json=_body("ZZZ-0000"))).status_code == 404
    assert (await client.post("/api/portal/magic", json=_body(photo="../../x.jpg"))).status_code == 404
    assert (await client.post("/api/portal/magic", json=_body(theme="999"))).status_code == 400
    assert storyface["generate"] == 0


async def test_ia_generates_a_watermarked_preview_and_saves_the_original(client, storyface):
    r = await client.post("/api/portal/magic", json=_body())
    assert r.status_code == 200, r.text
    photo = r.json()["photo"]
    assert photo["filename"].startswith("ia_91_")
    assert photo["url"].startswith("/api/media/preview/")          # jamais l'original
    assert (family_dir(SCHOOL, DEMO_CODE) / photo["filename"]).exists()
    assert (await client.get(photo["url"])).status_code == 200
    # elle apparaît dans la galerie avec l'étiquette du thème
    gallery = (await client.get(f"/api/portal/photos?code={DEMO_CODE}")).json()
    assert any(p["filename"] == photo["filename"] and "Astronaute" in p["child_name"] for p in gallery["photos"])


async def test_ia_quota_per_family_is_enforced(client, storyface, admin_headers):
    await client.put("/api/admin/settings", json={"settings": {"ai_max_per_family": 1}}, headers=admin_headers)
    try:
        assert (await client.post("/api/portal/magic", json=_body())).status_code == 200
        assert (await client.post("/api/portal/magic", json=_body())).status_code == 403
        assert storyface["generate"] == 1
    finally:
        await client.put("/api/admin/settings", json={"settings": {"ai_max_per_family": 5}}, headers=admin_headers)


async def test_ia_can_be_disabled_per_school(client, storyface, admin_headers):
    await client.put("/api/admin/settings", json={"settings": {"ai_enabled": False}}, headers=admin_headers)
    try:
        assert (await client.post("/api/portal/magic", json=_body())).status_code == 403
    finally:
        await client.put("/api/admin/settings", json={"settings": {"ai_enabled": True}}, headers=admin_headers)


async def test_ia_is_rate_limited_per_ip(client, storyface):
    statuses = [(await client.post("/api/portal/magic", json=_body(theme="999"))).status_code for _ in range(25)]
    assert 429 in statuses


async def test_generated_photo_can_be_ordered_as_magic_and_downloaded(client, storyface):
    from conftest import insert_order, item
    import uuid
    photo = (await client.post("/api/portal/magic", json=_body())).json()["photo"]
    order = {
        "family_code": DEMO_CODE, "customer_name": "Test", "customer_email": "t@example.com", "total": 9.0,
        "items": [{"photo_filename": photo["filename"], "format_id": "magic-digital", "format_label": "Numérique magique",
                   "quantity": 1, "unit_price": 9.0, "is_magic": True, "job_label": "Astronaute"}],
    }
    r = await client.post("/api/portal/orders", json=order)
    assert r.status_code == 200, r.text
    from core.db import get_db
    with get_db() as conn:
        conn.execute("UPDATE orders SET payment_status='paid' WHERE id=?", (r.json()["order_id"],))
        conn.commit()
    orders = (await client.get(f"/api/portal/orders?code={DEMO_CODE}")).json()["orders"]
    dl = next(o for o in orders if o["id"] == r.json()["order_id"])["downloads"]
    assert len(dl) == 1 and (await client.get(dl[0]["url"])).status_code == 200
