"""
conftest.py — configuration pytest partagée.

CRITIQUE : redirige DB_PATH / PHOTOS_DIR / THUMBS_DIR / INCOMING_DIR / LOG_DIR
vers un dossier temporaire AVANT le premier `import main` — sinon les tests
tourneraient sur la vraie base de données et les vraies photos des familles
(le genre d'accident à l'origine de l'incident du 2026-09-03).

Ces variables d'environnement doivent être définies au chargement du module
(pas dans une fixture) car `core.config` les lit à l'import.

Jeu de données de test (créé une fois par session, sans aucune donnée de démo) :
  école « ecole-test »  : famille TST-1001 (consentement donné, 2 photos) et
                          famille CON-3003 (SANS consentement, 1 photo), photos/ecole-test/cam1/CODE/
                          famille OLD-7007 (consentement donné, 1 photo) dans l'ANCIEN layout photos/OLD-7007/
  école « autre-ecole » : famille OTH-2002 (consentement donné, 1 photo)
"""
import os
import shutil
import tempfile
from pathlib import Path

_TMP_DIR = Path(tempfile.mkdtemp(prefix="meeconic_test_"))

os.environ["APP_ENV"]      = "test"
os.environ["DB_PATH"]      = str(_TMP_DIR / "test.db")
os.environ["PHOTOS_DIR"]   = str(_TMP_DIR / "photos")
os.environ["THUMBS_DIR"]   = str(_TMP_DIR / "thumbs")
os.environ["INCOMING_DIR"] = str(_TMP_DIR / "incoming")
os.environ["LOG_DIR"]      = str(_TMP_DIR / "logs")
os.environ["ORGANIZER_ENABLED"]  = "false"
os.environ["BACKGROUND_TASKS_ENABLED"] = "false"
os.environ["EMAIL_ENABLED"]      = "false"
os.environ["STRIPE_SECRET_KEY"]  = ""        # commandes créées sans appel Stripe réel
os.environ["STRIPE_WEBHOOK_SECRET"] = ""
os.environ["STORYFACE_API_KEY"]  = ""
os.environ["SENTRY_DSN"]         = ""
os.environ["ALERT_EMAIL"]        = ""
os.environ["ADMIN_PASSWORD"]     = "test-admin-pw"
os.environ["ADMIN_JWT_SECRET"]   = "test-jwt-secret-not-for-prod-0123456789"
os.environ["PHOTO_RETENTION_DAYS"] = "60"

import pytest
import pytest_asyncio
from httpx import AsyncClient, ASGITransport
from PIL import Image

import main as main_module  # noqa: E402  (import après config de l'environnement, volontaire)
from core import config  # noqa: E402
from core.db import get_db  # noqa: E402
from core.timeutil import utcnow_iso  # noqa: E402
from services import school_service  # noqa: E402

ADMIN_PASSWORD = os.environ["ADMIN_PASSWORD"]
SCHOOL = "ecole-test"
OTHER_SCHOOL = "autre-ecole"
DIRECTOR_PASSWORD = "director-pw-test"
OTHER_DIRECTOR_PASSWORD = "other-director-pw"

DEMO_CODE = "TST-1001"      # école-test, consentement donné
NOCONSENT_CODE = "CON-3003" # école-test, sans consentement
OTHER_CODE = "OTH-2002"     # autre-ecole
LEGACY_CODE = "OLD-7007"    # ecole-test, photos dans l'ancien layout photos/{CODE}/
PHOTOGRAPHER = "cam1"


def family_dir(school: str, code: str, photographer: str = PHOTOGRAPHER) -> Path:
    """Dossier d'une famille dans le layout actuel : photos/{école}/{photographe}/{CODE}/"""
    return config.PHOTOS_DIR / school / photographer / code


def _make_photo(folder: Path, name: str, color=(120, 80, 200)) -> Path:
    path = folder / name
    path.parent.mkdir(parents=True, exist_ok=True)
    Image.new("RGB", (1200, 800), color).save(path, "JPEG")
    return path


def _make_family(conn, code: str, school: str, name: str, consent: bool, photos: list[str], legacy: bool = False):
    conn.execute(
        "INSERT OR REPLACE INTO families (code, family_name, email, created_at, school_id, consent_at, consent_ip) "
        "VALUES (?,?,?,?,?,?,?)",
        (code, name, f"{name.lower()}@example.com", utcnow_iso(), school, utcnow_iso() if consent else None,
         "127.0.0.1" if consent else None))
    conn.execute("DELETE FROM students WHERE family_code=?", (code,))
    conn.execute("INSERT INTO students (family_code, first_name, last_name, class_name) VALUES (?,?,?,?)",
                 (code, "Alex", name, "CE1 A"))
    folder = config.PHOTOS_DIR / code if legacy else family_dir(school, code)
    for p in photos:
        _make_photo(folder, p)


def _bootstrap():
    main_module.init_all()
    if not school_service.get_school(SCHOOL):
        school_service.create_school("École Test", slug=SCHOOL, director_password=DIRECTOR_PASSWORD)
    if not school_service.get_school(OTHER_SCHOOL):
        school_service.create_school("Autre École", slug=OTHER_SCHOOL, director_password=OTHER_DIRECTOR_PASSWORD)
    with get_db() as conn:
        _make_family(conn, DEMO_CODE, SCHOOL, "Testeur", True, ["portrait.jpg", "fratrie.jpg"])
        _make_family(conn, NOCONSENT_CODE, SCHOOL, "Consent", False, ["solo.jpg"])
        _make_family(conn, OTHER_CODE, OTHER_SCHOOL, "Autrui", True, ["autre.jpg"])
        _make_family(conn, LEGACY_CODE, SCHOOL, "Ancien", True, ["vieille.jpg"], legacy=True)
        conn.commit()


_bootstrap()


def pytest_sessionfinish(session, exitstatus):
    """Nettoie le dossier temporaire une fois tous les tests terminés."""
    import logging
    logging.shutdown()
    shutil.rmtree(_TMP_DIR, ignore_errors=True)


@pytest.fixture(autouse=True)
def _reset_rate_limits():
    """Le rate limiter est persistant (SQLite) : on le vide entre les tests,
    sinon un test qui déclenche un 429 pollue les suivants."""
    from core.rate_limit import reset_all
    reset_all()
    yield
    reset_all()


@pytest.fixture(scope="session")
def app():
    return main_module.app


@pytest.fixture
def demo_code() -> str:
    return DEMO_CODE


@pytest_asyncio.fixture
async def client(app):
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as c:
        yield c


@pytest_asyncio.fixture
async def admin_token(client) -> str:
    r = await client.post("/api/admin/login", json={"password": ADMIN_PASSWORD})
    assert r.status_code == 200, r.text
    return r.json()["token"]


@pytest_asyncio.fixture
async def admin_headers(admin_token) -> dict:
    """Opérateur travaillant sur l'école de test."""
    return {"Authorization": f"Bearer {admin_token}", "X-School": SCHOOL}


@pytest_asyncio.fixture
async def other_admin_headers(admin_token) -> dict:
    return {"Authorization": f"Bearer {admin_token}", "X-School": OTHER_SCHOOL}


@pytest_asyncio.fixture
async def director_headers(client) -> dict:
    r = await client.post("/api/admin/login", json={"password": DIRECTOR_PASSWORD, "school": SCHOOL})
    assert r.status_code == 200, r.text
    return {"Authorization": f"Bearer {r.json()['token']}"}


def insert_order(order_id: str, code: str, school: str, items: list[dict], payment_status="paid",
                 status="pending", pi_id=None, created_at=None, total=None) -> None:
    """Insère directement une commande (évite de passer par Stripe dans les tests)."""
    import json
    total = total if total is not None else sum(i["unit_price"] * i["quantity"] for i in items)
    with get_db() as conn:
        conn.execute(
            """INSERT INTO orders (id, family_code, customer_name, customer_email, total, status, items_json,
               created_at, payment_status, stripe_payment_intent_id, school_id)
               VALUES (?,?,?,?,?,?,?,?,?,?,?)""",
            (order_id, code, "Client Test", "client@example.com", total, status, json.dumps(items),
             created_at or utcnow_iso(), payment_status, pi_id, school))
        conn.commit()


def item(fmt="print-10x15", photo="portrait.jpg", price=9.0, qty=1, magic=False, label="Tirage", job=None) -> dict:
    return {"photo_filename": photo, "format_id": fmt, "format_label": label, "quantity": qty,
            "unit_price": price, "is_magic": magic, "job_label": job}


# ─── Imágenes con QR reales para probar el lector ─────────────────────────────

def qr_image(text: str, size=(1600, 1200), qr_px: int = 420, blur: int = 0, contrast: float = 1.0):
    """Foto sintética (gris, con ruido suave) con un QR REAL de `text` pegado en el centro."""
    import io
    import numpy as np
    import segno
    buf = io.BytesIO()
    segno.make(text, error="m", micro=False).save(buf, kind="png", scale=12, border=4)
    qr = Image.open(io.BytesIO(buf.getvalue())).convert("RGB").resize((qr_px, qr_px), Image.NEAREST)
    rng = np.random.default_rng(7)
    base = (rng.normal(150, 12, (size[1], size[0], 3))).clip(0, 255).astype("uint8")
    img = Image.fromarray(base)
    img.paste(qr, ((size[0] - qr_px) // 2, (size[1] - qr_px) // 2))
    if contrast != 1.0:
        from PIL import ImageEnhance
        img = ImageEnhance.Contrast(img).enhance(contrast)
    if blur:
        from PIL import ImageFilter
        img = img.filter(ImageFilter.GaussianBlur(blur))
    return img


def plain_image(size=(1600, 1200), seed: int = 3):
    """Retrato sin QR (ruido de color + degradado)."""
    import numpy as np
    rng = np.random.default_rng(seed)
    base = rng.normal(110, 40, (size[1], size[0], 3)).clip(0, 255).astype("uint8")
    return Image.fromarray(base)


def save_jpeg(img, path: Path) -> Path:
    path.parent.mkdir(parents=True, exist_ok=True)
    img.save(path, "JPEG", quality=92)
    return path
