from pathlib import Path

SAMPLE_CSV = Path(__file__).resolve().parent.parent / "samples" / "familles_exemple.csv"


async def _new_school(client, admin_token, name):
    r = await client.post("/api/admin/schools", json={"name": name},
                          headers={"Authorization": f"Bearer {admin_token}"})
    assert r.status_code == 200, r.text
    sid = r.json()["school"]["id"]
    return {"Authorization": f"Bearer {admin_token}", "X-School": sid}


async def test_import_sample_csv_groups_siblings(client, admin_token):
    headers = await _new_school(client, admin_token, "Import Ecole")
    csv_text = SAMPLE_CSV.read_text(encoding="utf-8")
    r = await client.post("/api/admin/students/import", json={"csv_text": csv_text, "replace": False}, headers=headers)
    assert r.status_code == 200, r.text
    body = r.json()
    # 10 élèves, 7 familles, 3 fratries, 1 famille sans e-mail
    assert body["students_imported"] == 10
    assert body["families_created"] == 7
    assert body["siblings_detected"] == 3
    assert body["emails_imported"] == 6

    fam = await client.get("/api/admin/families?q=Delacroix", headers=headers)
    families = fam.json()["families"]
    assert len(families) == 1
    assert families[0]["is_sibling"] is True
    assert {s["first_name"] for s in families[0]["students"]} == {"Lucas", "Emma"}


async def test_import_is_scoped_to_the_selected_school(client, admin_token, admin_headers):
    headers = await _new_school(client, admin_token, "Scoped Ecole")
    await client.post("/api/admin/students/import", json={"csv_text": SAMPLE_CSV.read_text(encoding="utf-8")},
                      headers=headers)
    other = await client.get("/api/admin/families?q=Delacroix", headers=admin_headers)
    assert other.json()["families"] == []


async def test_replace_only_wipes_the_selected_school(client, admin_token, admin_headers):
    headers = await _new_school(client, admin_token, "Replace Ecole")
    csv_text = SAMPLE_CSV.read_text(encoding="utf-8")
    before = (await client.get("/api/admin/families", headers=admin_headers)).json()["total"]
    await client.post("/api/admin/students/import", json={"csv_text": csv_text}, headers=headers)
    r = await client.post("/api/admin/students/import", json={"csv_text": csv_text, "replace": True}, headers=headers)
    assert r.json()["families_total"] == 7
    after = (await client.get("/api/admin/families", headers=admin_headers)).json()["total"]
    assert after == before


async def test_import_requires_operator_role(client):
    r = await client.post("/api/admin/students/import", json={"csv_text": "a;b;c", "replace": False})
    assert r.status_code == 401


async def test_import_rejects_missing_columns(client, admin_headers):
    r = await client.post("/api/admin/students/import",
                          json={"csv_text": "colonne1;colonne2\nx;y", "replace": False}, headers=admin_headers)
    assert r.status_code == 400


async def test_create_family_by_hand_gets_its_own_code(client, admin_token):
    headers = await _new_school(client, admin_token, "Manual Ecole")
    body = {"family_name": "Durand", "email": "a@b.fr",
            "students": [{"first_name": "Léa", "class_name": "CE1"}, {"first_name": "Tom", "class_name": "CM2"}]}
    r1 = await client.post("/api/admin/families", json=body, headers=headers)
    r2 = await client.post("/api/admin/families", json=body, headers=headers)
    assert r1.status_code == 200, r1.text
    f1, f2 = r1.json()["family"], r2.json()["family"]
    assert f1["code"] != f2["code"] and f1["is_sibling"] is True
    listed = (await client.get("/api/admin/families?q=Durand", headers=headers)).json()
    assert listed["total"] == 2


async def test_create_family_validates_input_and_role(client, admin_token):
    headers = await _new_school(client, admin_token, "Manual Validation")
    bad_mail = {"family_name": "X", "email": "nope", "students": [{"first_name": "A", "class_name": "CP"}]}
    assert (await client.post("/api/admin/families", json=bad_mail, headers=headers)).status_code == 400
    no_students = {"family_name": "X", "students": []}
    assert (await client.post("/api/admin/families", json=no_students, headers=headers)).status_code == 422
    assert (await client.post("/api/admin/families", json=bad_mail)).status_code == 401


async def test_family_card_png_is_signed_scoped_and_print_sized(client, admin_token):
    import io
    from PIL import Image
    headers = await _new_school(client, admin_token, "Cards Ecole")
    body = {"family_name": "Delacroix", "students": [{"first_name": "Lucas", "class_name": "CE2"}]}
    fam = (await client.post("/api/admin/families", json=body, headers=headers)).json()["family"]
    listed = (await client.get("/api/admin/families", headers=headers)).json()["families"][0]
    assert listed["card_url"].startswith(f"/api/media/card/{headers['X-School']}/{fam['code']}.png?")

    r = await client.get(listed["card_url"] + "&lang=es")
    assert r.status_code == 200 and r.headers["content-type"] == "image/png"
    im = Image.open(io.BytesIO(r.content))
    assert im.size == (1260, 795)
    assert round(im.info["dpi"][0]) == 374                      # 85.6 mm wide at 1:1

    tampered = listed["card_url"].replace("sig=", "sig=0")
    assert (await client.get(tampered)).status_code == 403
    other = listed["card_url"].replace(headers["X-School"], "ecole-test")
    assert (await client.get(other)).status_code == 403          # signature is tied to the school


async def test_cards_zip_groups_by_class_and_requires_operator(client, admin_token):
    import io
    import zipfile
    headers = await _new_school(client, admin_token, "Zip Ecole")
    await client.post("/api/admin/students/import", json={"csv_text": SAMPLE_CSV.read_text(encoding="utf-8")},
                      headers=headers)
    r = await client.get("/api/admin/cards.zip", headers=headers)
    assert r.status_code == 200 and r.headers["content-type"] == "application/zip"
    names = zipfile.ZipFile(io.BytesIO(r.content)).namelist()
    assert len(names) == 7 and all(n.endswith(".png") and "/" in n for n in names)
    assert (await client.get("/api/admin/cards.zip")).status_code == 401
    empty = await _new_school(client, admin_token, "Empty Zip Ecole")
    assert (await client.get("/api/admin/cards.zip", headers=empty)).status_code == 404


def test_card_renders_long_names_and_unknown_language_without_failing():
    from services import card_service
    png = card_service.render_card(
        code="MAR-P8Y-D23", family_name="Fernandez-Montgomery de la Vega y Ortiz",
        students=[{"first_name": f"Prénom{i}", "class_name": "CM2 A"} for i in range(6)],
        school_name="École " + "Très Longue " * 6, portal_url="https://photos.example.com/", rgpd_days="60", lang="zz")
    assert png.startswith(b"\x89PNG")


async def test_setup_checklist_reports_progress(client, admin_headers):
    r = await client.get("/api/admin/setup", headers=admin_headers)
    assert r.status_code == 200
    body = r.json()
    ids = [s["id"] for s in body["steps"]]
    assert ids[0] == "school_name" and "stripe" in ids
    assert body["total"] == len(ids) and 0 <= body["done"] <= body["total"]
