"""Códigos de familia (formato HEC PREFIJO-XXX-XXX), búsqueda tolerante a guiones y redirección /r/."""
import re
import uuid

import pytest

from core.db import get_db
from services import school_service
from utils import code_generator as G
from conftest import DEMO_CODE, SCHOOL

NEW_FORMAT = re.compile(r"^[A-Z]{2,6}-[23456789A-HJKMNPQRSTUVWXYZ]{3}-[23456789A-HJKMNPQRSTUVWXYZ]{3}$")


def test_generated_codes_have_the_hec_format_and_no_ambiguous_characters():
    for _ in range(300):
        code = G.build_code("mar")
        assert NEW_FORMAT.match(code), code
        assert not set("0O1IL") & set(code.split("-", 1)[1].replace("-", ""))


def test_prefix_from_name_strips_accents_and_pads():
    assert G.prefix_from_name("École Sainte-Marie") == "ECO"
    assert G.prefix_from_name("Çà") == "CAX"
    assert G.prefix_from_name("1234") == "FOT"
    assert G.prefix_from_name("") == "FOT"


def test_normalize_and_display():
    assert G.normalize_code("mar-4jc-ibd") == "MAR4JCIBD"
    assert G.format_code_display("MAR4JCKBD") == "MAR-4JC-KBD"
    assert G.format_code_display("LMR-4827") == "LMR-4827"


def test_unique_code_retries_on_collision(monkeypatch):
    with get_db() as conn:
        taken = G.build_code("COL")
        conn.execute("INSERT INTO families (code, family_name, school_id) VALUES (?,?,?)", (taken, "x", SCHOOL))
        conn.commit()
        seq = iter([taken, taken.replace("-", ""), "COL-FRE-SH2"])
        monkeypatch.setattr(G, "build_code", lambda prefix: next(seq))
        assert G.generate_unique_code(conn, "COL") == "COL-FRE-SH2"
        conn.execute("DELETE FROM families WHERE code=?", (taken,))
        conn.commit()


def test_unique_code_gives_up_after_too_many_collisions(monkeypatch):
    with get_db() as conn:
        conn.execute("INSERT OR REPLACE INTO families (code, family_name, school_id) VALUES ('AAA-BBB-CCC','x',?)", (SCHOOL,))
        conn.commit()
        monkeypatch.setattr(G, "build_code", lambda prefix: "AAA-BBB-CCC")
        with pytest.raises(RuntimeError):
            G.generate_unique_code(conn, "AAA")
        conn.execute("DELETE FROM families WHERE code='AAA-BBB-CCC'")
        conn.commit()


# ─── Importación : los códigos nuevos llevan el prefijo del colegio ───────────

CSV = "prenom;nom;classe;email\nAna;Zapata;CP A;ana@example.com\nLuis;Zapata;CE1 A;ana@example.com\nPau;Quirós;CP B;\n"


async def _school(client, admin_token, name, **settings):
    h = {"Authorization": f"Bearer {admin_token}"}
    sid = (await client.post("/api/admin/schools", json={"name": name}, headers=h)).json()["school"]["id"]
    headers = {**h, "X-School": sid}
    if settings:
        assert (await client.put("/api/admin/settings", json={"settings": settings}, headers=headers)).status_code == 200
    return headers


async def test_imported_families_get_codes_with_the_schools_configured_prefix(client, admin_token):
    headers = await _school(client, admin_token, "Prefijo Configurado", qr_code_prefix="mrs")
    await client.post("/api/admin/students/import", json={"csv_text": CSV}, headers=headers)
    codes = [f["code"] for f in (await client.get("/api/admin/families", headers=headers)).json()["families"]]
    assert len(codes) == 2 and all(NEW_FORMAT.match(c) and c.startswith("MRS-") for c in codes)


async def test_without_a_configured_prefix_it_is_derived_from_the_school_name(client, admin_token):
    headers = await _school(client, admin_token, "Lyon Centre")
    await client.post("/api/admin/students/import", json={"csv_text": CSV}, headers=headers)
    codes = [f["code"] for f in (await client.get("/api/admin/families", headers=headers)).json()["families"]]
    assert all(c.startswith("LYO-") for c in codes)


async def test_codes_are_unique_across_schools_even_with_the_same_prefix(client, admin_token):
    a = await _school(client, admin_token, "Gemela Uno", qr_code_prefix="GEM")
    b = await _school(client, admin_token, "Gemela Dos", qr_code_prefix="GEM")
    for h in (a, b):
        await client.post("/api/admin/students/import", json={"csv_text": CSV}, headers=h)
    all_codes = [f["code"] for h in (a, b) for f in (await client.get("/api/admin/families", headers=h)).json()["families"]]
    assert len(all_codes) == len(set(all_codes)) == 4


@pytest.mark.parametrize("bad", ["M", "ABCDEFG", "M4R", "MA-R", "ñandú"])
async def test_prefix_setting_is_validated(client, admin_headers, bad):
    r = await client.put("/api/admin/settings", json={"settings": {"qr_code_prefix": bad}}, headers=admin_headers)
    assert r.status_code == 400


async def test_prefix_setting_is_uppercased_and_can_be_cleared(client, admin_headers):
    r = await client.put("/api/admin/settings", json={"settings": {"qr_code_prefix": "tst"}}, headers=admin_headers)
    assert r.json()["settings"]["qr_code_prefix"] == "TST"
    r = await client.put("/api/admin/settings", json={"settings": {"qr_code_prefix": ""}}, headers=admin_headers)
    assert r.json()["settings"]["qr_code_prefix"] == ""


# ─── Búsqueda tolerante ───────────────────────────────────────────────────────

@pytest.mark.parametrize("typed", ["TST-1001", "tst-1001", "TST1001", " tst 1001 "])
async def test_family_lookup_ignores_hyphens_case_and_spaces(client, typed):
    r = await client.get("/api/portal/photos", params={"code": typed})
    assert r.status_code == 200, typed
    assert r.json()["family_code"] == DEMO_CODE            # siempre el código canónico


async def test_lookup_without_hyphens_also_works_for_consent_orders_and_previews(client):
    r = await client.post("/api/portal/consent", json={"family_code": "tst1001"})
    assert r.status_code == 200
    photos = (await client.get("/api/portal/photos", params={"code": "TST1001"})).json()["photos"]
    assert (await client.get(photos[0]["url"])).status_code == 200
    order = {"family_code": "tst1001", "customer_name": "N", "customer_email": "a@b.co", "total": 9.0, "items": [{
        "photo_filename": "portrait.jpg", "format_id": "print-10x15", "format_label": "x", "quantity": 1,
        "unit_price": 9.0, "is_magic": False}]}
    assert (await client.post("/api/portal/orders", json=order)).status_code == 200


async def test_a_wrong_code_still_counts_against_the_bruteforce_limit(client):
    statuses = [(await client.get("/api/portal/photos", params={"code": "ZZZ9999"})).status_code for _ in range(20)]
    assert statuses[0] == 404 and 429 in statuses


# ─── /r/{código} ──────────────────────────────────────────────────────────────

async def test_qr_redirect_sends_the_family_to_the_portal_over_https(client):
    r = await client.get("/r/MAR-4JC-IBD", headers={"host": "photos.meeconic.com"}, follow_redirects=False)
    assert r.status_code == 302 and r.headers["location"] == "https://photos.meeconic.com/?code=MAR-4JC-IBD"


async def test_qr_redirect_rejects_garbage_codes(client):
    for bad in ("..", "%2e%2e", "a" * 60, "x"):
        r = await client.get(f"/r/{bad}", follow_redirects=False)
        assert r.status_code in (404, 307, 308), bad
        assert r.status_code != 302
