"""Teste US-004: wizard import cu stepper vizual (4 pasi numerotati).

TDD — testele sunt scrise INAINTE de implementare (RED), apoi se face GREEN.

Verifica:
  - Pasul 1 activ (aria-current="step") in fragmentul de upload
  - Pasul 2 activ in fragmentul mapare-coloane
  - Pasul 3 activ in preview
  - Pasii 1 si 2 marcati ca "facuti" in preview (clasa/marcaj)
  - hx-target="#import-section" pastrat in fragmentele de import
  - csrf_token prezent in formularele de import
"""

from __future__ import annotations

import io
import os
import re
import tempfile

import pytest


@pytest.fixture()
def client(monkeypatch):
    tmp = tempfile.mkdtemp()
    monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "t.db"))
    monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false")
    from app.config import get_settings

    get_settings.cache_clear()
    from app.web import ratelimit
    ratelimit._hits.clear()  # izolare: limiterul login e global in-proces
    from app.main import app
    from fastapi.testclient import TestClient

    with TestClient(app) as c:
        yield c
    ratelimit._hits.clear()
    get_settings.cache_clear()


# ---------------------------------------------------------------------------
# Helpere
# ---------------------------------------------------------------------------

def _make_csv_bytes(rows: list[dict], sep: str = ";") -> bytes:
    import csv

    buf = io.StringIO()
    if not rows:
        return b""
    writer = csv.DictWriter(buf, fieldnames=list(rows[0].keys()), delimiter=sep)
    writer.writeheader()
    writer.writerows(rows)
    return buf.getvalue().encode("utf-8")


def _make_xlsx_bytes(rows: list[dict]) -> bytes:
    openpyxl = pytest.importorskip("openpyxl")
    wb = openpyxl.Workbook()
    ws = wb.active
    if not rows:
        return b""
    headers = list(rows[0].keys())
    ws.append(headers)
    for row in rows:
        ws.append([row.get(h) for h in headers])
    buf = io.BytesIO()
    wb.save(buf)
    return buf.getvalue()


_SAMPLE_ROWS = [
    {
        "VIN": "WVWZZZ1KZAW000123",
        "Nr inmatriculare": "B001TST",
        "Data prestatie": "15.06.2026",
        "Odometru final": "123456",
        "Operatie": "Revizie",
    },
    {
        "VIN": "WVWZZZ1KZAW000456",
        "Nr inmatriculare": "B002TST",
        "Data prestatie": "16.06.2026",
        "Odometru final": "200000",
        "Operatie": "Revizie",
    },
]


def _seed_op_mapping(client, cod_op: str = "Revizie", cod_prest: str = "OE-1") -> None:
    client.post("/v1/mapari", json={
        "cod_op_service": cod_op,
        "cod_prestatie": cod_prest,
        "auto_send": True,
    })


def _upload_and_get_import_id(client, rows=None) -> int:
    xlsx = _make_xlsx_bytes(rows or _SAMPLE_ROWS)
    r = client.post(
        "/_import/upload",
        files={"file": ("test.xlsx", xlsx,
                        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")},
    )
    assert r.status_code == 200
    m = re.search(r"/_import/(\d+)/mapare-coloane", r.text)
    assert m, f"Nu s-a gasit import_id in raspuns: {r.text[:500]}"
    return int(m.group(1))


def _get_preview_via_mapare(client, import_id: int) -> str:
    """Salveaza maparea de coloane si returneaza textul raspunsului preview."""
    r = client.post(
        f"/_import/{import_id}/mapare-coloane",
        data={
            "colname": ["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Operatie"],
            "canon": ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie"],
            "format_data": "DD.MM.YYYY",
        },
    )
    assert r.status_code == 200
    return r.text


# ---------------------------------------------------------------------------
# US-004 Teste stepper
# ---------------------------------------------------------------------------

def test_stepper_pas1_la_upload(client):
    """Fragmentul de upload contine stepper-ul cu pasul 1 activ.

    Verifica prezenta marcajului aria-current='step' pe pasul 'Incarca fisier'
    sau clasa activa asociata pasului 1.
    """
    r = client.get("/_import/reset")
    assert r.status_code == 200
    text = r.text
    # Stepper-ul trebuie sa fie prezent
    assert "stepper" in text or "pasi-import" in text or "step" in text.lower(), \
        "Stepper-ul nu a fost gasit in fragmentul de upload"
    # Pasul 1 trebuie sa aiba aria-current="step"
    assert 'aria-current="step"' in text, \
        "aria-current='step' nu a fost gasit in fragmentul de upload (pasul 1)"
    # Textul pasului 1 trebuie sa fie prezent
    assert "Incarca" in text, "Textul pasului 1 'Incarca' nu a fost gasit"


def test_stepper_pas1_via_tab_import(client):
    """Accesand /?tab=import, panoul contine stepper cu pasul 1 activ."""
    r = client.get("/?tab=import")
    assert r.status_code == 200
    text = r.text
    assert 'aria-current="step"' in text, \
        "aria-current='step' nu a fost gasit in panoul Import (/?tab=import)"
    assert "Incarca" in text, "Textul pasului 1 'Incarca' nu a fost gasit in panoul Import"


def test_stepper_pas2_la_mapare(client):
    """Fragmentul mapare-coloane contine stepper cu pasul 2 activ.

    Declanseaza un upload cu coloane NEMAPATE ca sa primesti _mapcoloane.html.
    """
    # Upload fara mapare salvata → trebuie sa vina _mapcoloane.html
    csv_bytes = _make_csv_bytes(_SAMPLE_ROWS)
    r = client.post(
        "/_import/upload",
        files={"file": ("test.csv", csv_bytes, "text/csv")},
    )
    assert r.status_code == 200
    text = r.text
    # Trebuie sa fie formularul de mapare coloane
    assert "mapare-coloane" in text, "Nu s-a primit fragmentul de mapare coloane"
    # Stepper prezent
    assert "stepper" in text or "step" in text.lower(), \
        "Stepper-ul nu a fost gasit in fragmentul mapare-coloane"
    # Pasul 2 trebuie sa aiba aria-current="step" cu textul "Potriveste"
    # (pasul 1 e facut, pasul 2 e activ)
    assert 'aria-current="step"' in text, \
        "aria-current='step' nu a fost gasit in fragmentul mapare-coloane (pasul 2)"
    assert "Potriveste" in text, "Textul pasului 2 'Potriveste' nu a fost gasit"


def test_stepper_pas3_la_preview(client):
    """Preview contine stepper cu pasul 3 activ.

    Declanseaza upload + salvare mapare → se ajunge la preview.
    """
    _seed_op_mapping(client)
    import_id = _upload_and_get_import_id(client)
    text = _get_preview_via_mapare(client, import_id)

    # Preview trebuie sa fie prezent
    assert "Preview" in text or "confirm-form" in text, \
        "Nu s-a primit fragmentul de preview"
    # Stepper prezent
    assert "stepper" in text or "step" in text.lower(), \
        "Stepper-ul nu a fost gasit in preview"
    # Pasul 3 activ
    assert 'aria-current="step"' in text, \
        "aria-current='step' nu a fost gasit in preview (pasul 3)"
    assert "Verifica" in text, "Textul pasului 3 'Verifica' nu a fost gasit in preview"


def test_stepper_pas3_la_preview_direct_mapare_retinuta(client):
    """Upload cu mapare retinuta sare direct la preview cu pasul 3 activ.

    Primul upload + mapare memoreaza configuratia.
    Al doilea upload cu acelasi antet sare direct la preview (pas 3).
    Pasii 1 si 2 sunt implicit facuti (comportament stepper la pas=3).
    """
    _seed_op_mapping(client)
    import_id1 = _upload_and_get_import_id(client)
    _get_preview_via_mapare(client, import_id1)

    # Al doilea upload — mapare retinuta → preview direct
    xlsx = _make_xlsx_bytes(_SAMPLE_ROWS)
    r = client.post(
        "/_import/upload",
        files={"file": ("test2.xlsx", xlsx,
                        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")},
    )
    assert r.status_code == 200
    text = r.text
    # Preview direct cu mesaj "Mapare retinuta"
    assert "Mapare retinuta" in text, "Preview direct (mapare retinuta) nu a fost randat"
    # Stepper prezent cu pasul 3 activ
    assert 'aria-current="step"' in text, \
        "aria-current='step' nu a fost gasit in preview direct (mapare retinuta)"
    assert "Verifica" in text, "Textul pasului 3 'Verifica' nu a fost gasit in preview direct"


def test_stepper_marcheaza_pasii_facuti(client):
    """In preview (pas 3), pasii 1 si 2 sunt marcati ca facuti (clasa is-done).

    Verifica prin prezenta clasei CSS is-done (doua aparitii: pasii 1 si 2).
    """
    _seed_op_mapping(client)
    import_id = _upload_and_get_import_id(client)
    text = _get_preview_via_mapare(client, import_id)

    # Clasa "is-done" trebuie sa apara pentru pasii 1 si 2 (index < pas curent)
    assert "is-done" in text, \
        "Clasa 'is-done' nu a fost gasita in preview (pasii 1 si 2 ar trebui marcati ca is-done)"
    # Numarul de aparitii: cel putin 2 pasi marcati ca is-done
    count_done = text.count("is-done")
    assert count_done >= 2, \
        f"Asteptat cel putin 2 pasi marcati ca 'is-done' in preview, gasit {count_done}"


def test_import_hx_target_in_tab(client):
    """Fragmentele de import pastreaza hx-target='#import-section'.

    Fragmentul de upload (/_import/reset) trebuie sa contina
    hx-target='#import-section' pentru ca HTMX sa actualizeze corect
    containerul din panoul de tab, nu din alta parte.
    """
    r = client.get("/_import/reset")
    assert r.status_code == 200
    text = r.text
    assert 'hx-target="#import-section"' in text, \
        "hx-target='#import-section' nu a fost gasit in fragmentul de upload"
    # Wrapper-ul extern trebuie sa aiba id="import-section"
    assert 'id="import-section"' in text, \
        "id='import-section' nu a fost gasit in fragmentul de upload"


def test_import_forms_pastreaza_csrf(client):
    """Formularele de import contin csrf_token (input hidden cu valoare).

    Testeaza atat fragmentul de upload cat si cel de mapare coloane.
    """
    # Fragment upload
    r_upload = client.get("/_import/reset")
    assert r_upload.status_code == 200
    text_upload = r_upload.text
    # Trebuie sa contina campul csrf_token (poate fi gol in modul dev fara sesiune,
    # dar campul trebuie sa existe)
    assert 'name="csrf_token"' in text_upload, \
        "name='csrf_token' nu a fost gasit in formularul de upload"

    # Fragment mapare coloane
    csv_bytes = _make_csv_bytes(_SAMPLE_ROWS)
    r_map = client.post(
        "/_import/upload",
        files={"file": ("test.csv", csv_bytes, "text/csv")},
    )
    assert r_map.status_code == 200
    text_map = r_map.text
    if "mapare-coloane" in text_map:  # s-a primit fragmentul de mapare
        assert 'name="csrf_token"' in text_map, \
            "name='csrf_token' nu a fost gasit in formularul mapare-coloane"


# ---------------------------------------------------------------------------
# US-013 Teste: import colapsat + tokeni scala + pill-uri cu dot (PRD 5.16)
# ---------------------------------------------------------------------------

def test_import_colapsat_implicit(client):
    """Pe Acasa (first-run, fara trimiteri), sectiunea de import e deschisa implicit.

    La first-run (are_trimiteri=False), <details> trebuie sa aiba atributul `open`.
    Summary-ul trebuie sa contina textul slim 'Importa fisier' (bara colapsabila).
    Verifica si ca <details id="import-details"> este prezent pe pagina principala.
    """
    r = client.get("/")
    assert r.status_code == 200
    text = r.text

    # Elementul <details> trebuie sa fie prezent
    assert 'id="import-details"' in text, \
        "Elementul <details id='import-details'> lipseste de pe pagina principala"

    # La first-run (nu exista trimiteri), details trebuie sa fie deschis (atribut open)
    assert 'id="import-details" open' in text, \
        "La first-run, <details id='import-details'> trebuie sa aiba atributul 'open'"

    # Textul summary trebuie sa contina 'Importa fisier' (bara slim colapsabila)
    assert "Importa fisier" in text, \
        "Textul 'Importa fisier' nu a fost gasit in summary-ul sectiunii de import"


def test_wizard_foloseste_scala_tokeni(client):
    """Fragmentele wizard-ului de import folosesc tokeni var(--fs-*) in loc de px hardcodat.

    Verifica ca fragmentul de mapare coloane (_mapcoloane.html) si cel de upload
    (_upload.html) contin referinte la tokenii de scala --fs-* in inline styles,
    nu font-size hardcodat in px sub 12px.
    """
    # Fragment upload (/_import/reset) → _upload.html
    r_upload = client.get("/_import/reset")
    assert r_upload.status_code == 200
    upload_text = r_upload.text
    # Tokenii trebuie sa apara in inline styles
    assert "var(--fs-" in upload_text, \
        "Tokenii var(--fs-*) nu au fost gasiti in fragmentul de upload (_upload.html)"

    # Fragment mapare coloane → _mapcoloane.html
    csv_bytes = _make_csv_bytes(_SAMPLE_ROWS)
    r_map = client.post(
        "/_import/upload",
        files={"file": ("test.csv", csv_bytes, "text/csv")},
    )
    assert r_map.status_code == 200
    map_text = r_map.text
    # Mapcoloane trebuie sa contina tokeni
    assert "var(--fs-" in map_text, \
        "Tokenii var(--fs-*) nu au fost gasiti in fragmentul mapare coloane (_mapcoloane.html)"

    # Verifica ca nu exista font-size sub 12px hardcodat in fragmentele wizard
    import re
    for fragment_text, fragment_name in [(upload_text, "upload"), (map_text, "mapcoloane")]:
        for size_str in re.findall(r'font-size:\s*(\d+)px', fragment_text):
            size = int(size_str)
            assert size >= 12, \
                f"font-size:{size}px sub 12px gasit in fragmentul {fragment_name} — trebuie var(--fs-*)"


def test_preview_stari_pill_dot(client):
    """Pill-urile de stare din preview contin un dot consistent cu designul 5.16.

    Verifica ca pill-urile din tabelul de preview si din rezumatul de stari contin
    un element dot (span cu border-radius:99px ca inline style), consistent cu stripul
    slim si cu designul 5.16 (dot + text, nu text gol).
    Eticheta umana: din STARI_PREVIEW ('Gata de trimis', 'Cod RAR lipsa' etc.) — nicio
    eticheta noua.
    """
    _seed_op_mapping(client)
    import_id = _upload_and_get_import_id(client)
    text = _get_preview_via_mapare(client, import_id)

    # Preview trebuie sa fie prezent
    assert "confirm-form" in text or "Preview" in text, \
        "Fragmentul de preview nu a fost randat"

    # Pill-urile de stare trebuie sa contina un dot (span cu border-radius:99px)
    assert "border-radius:99px" in text, \
        "Dot-ul (border-radius:99px) nu a fost gasit in pill-urile de stare din preview"

    # Etichetele umane din STARI_PREVIEW trebuie sa fie prezente (nicio eticheta noua)
    # 'Gata de trimis' apare in rezumatul de stari (pill) sau in tabelul de randuri
    assert "Gata de trimis" in text or "Cod RAR lipsa" in text or "Verifica valori" in text, \
        "Etichetele umane din STARI_PREVIEW nu au fost gasite in preview"
