"""Teste US-003 (PRD 5.11) — Preview pas 3 in format identic cu tabelul Trimiteri.

Proces TDD: aceste teste sunt scrise INAINTE de implementare (RED) si verifica:
  1. Coloana Note nu afiseaza repr Python brut (lista/dict Python) pentru needs_mapping.
  2. Starea afisata in pill-ul randului este eticheta umana (nu codul brut).
  3. Tabelul de preview foloseste clasa CSS .tabel-trimiteri.

Fixture real cu rand needs_mapping OBLIGATORIU pentru test_preview_nu_contine_repr_python
(altfel testul trece in gol pe un preview fara erori in Note).
"""

from __future__ import annotations

import csv
import io
import os
import re
import tempfile

import pytest
from fastapi.testclient import TestClient


# --------------------------------------------------------------------------- #
# Fixture client cu DB izolat                                                   #
# --------------------------------------------------------------------------- #

@pytest.fixture()
def client(monkeypatch):
    tmp = tempfile.mkdtemp()
    monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "preview_us003.db"))
    monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false")
    from app.config import get_settings
    get_settings.cache_clear()
    from app.crypto import reset_cache
    reset_cache()
    from app.main import app
    with TestClient(app) as c:
        yield c
    get_settings.cache_clear()
    reset_cache()


# --------------------------------------------------------------------------- #
# Utilitare                                                                     #
# --------------------------------------------------------------------------- #

def _csv_bytes(rows: list[dict], sep: str = ";") -> bytes:
    buf = io.StringIO()
    writer = csv.DictWriter(buf, fieldnames=list(rows[0].keys()), delimiter=sep)
    writer.writeheader()
    writer.writerows(rows)
    return buf.getvalue().encode("utf-8")


def _seed_nomenclator_si_mapare(
    cod_prestatie: str = "R-FRANE", cod_op: str = "OP-FRANE"
) -> None:
    """Semeaza nomenclatorul si o mapare pentru a produce randuri ok in preview."""
    from app.db import get_connection
    conn = get_connection()
    try:
        conn.execute(
            "INSERT OR REPLACE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?,?)",
            (cod_prestatie, "Reparatie frane"),
        )
        conn.execute(
            "INSERT OR IGNORE INTO operations_mapping "
            "(account_id, cod_op_service, cod_prestatie, auto_send) VALUES (1,?,?,1)",
            (cod_op, cod_prestatie),
        )
        conn.commit()
    finally:
        conn.close()


def _upload_and_preview(client: TestClient, rows: list[dict]) -> tuple[int, str]:
    """Upload CSV + salveaza mapare coloane (daca lipseste) + GET preview.

    Returneaza (import_id, html_preview).
    """
    data = _csv_bytes(rows)
    r = client.post(
        "/_import/upload",
        files={"file": ("test.csv", io.BytesIO(data), "text/csv")},
    )
    assert r.status_code == 200, r.text

    # Extrage import_id din URL-urile prezente in raspuns
    m = re.search(r"/_import/(\d+)/", r.text)
    assert m, f"import_id negasit in raspunsul de upload: {r.text[:400]}"
    iid = int(m.group(1))

    # Daca s-a returnat formularul de mapare coloane, il salvam
    if f"/_import/{iid}/mapare-coloane" in r.text:
        r2 = client.post(
            f"/_import/{iid}/mapare-coloane",
            data={
                "colname": ["VIN", "Nr", "Data", "KM", "Operatie"],
                "canon":   ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie"],
                "format_data": "YYYY-MM-DD",
            },
        )
        assert r2.status_code == 200, r2.text

    r3 = client.get(f"/_import/{iid}/preview")
    assert r3.status_code == 200, r3.text
    return iid, r3.text


# --------------------------------------------------------------------------- #
# Date fixture                                                                   #
# --------------------------------------------------------------------------- #

# Un rand cu operatie nemapata — produce starea needs_mapping cu errors=[{"unmapped":[...]}]
_ROWS_UNMAPPED = [
    {
        "VIN": "WVWZZZ1KZAW000001",
        "Nr": "B001TST",
        "Data": "2026-06-15",
        "KM": "123456",
        "Operatie": "OP-FARA-COD",
    },
]

# Un rand ok (mapare existenta) + un rand unmapped
_ROWS_OK_SI_UNMAPPED = [
    {
        "VIN": "WVWZZZ1KZAW000001",
        "Nr": "B001TST",
        "Data": "2026-06-15",
        "KM": "123456",
        "Operatie": "OP-FRANE",
    },
    {
        "VIN": "WVWZZZ1KZAW000002",
        "Nr": "B002TST",
        "Data": "2026-06-15",
        "KM": "100000",
        "Operatie": "OP-FARA-COD",
    },
]

# Doua randuri identice — produc starea duplicate_in_file
_ROWS_DUPLICATE = [
    {
        "VIN": "WVWZZZ1KZAW000001",
        "Nr": "B001TST",
        "Data": "2026-06-15",
        "KM": "123456",
        "Operatie": "OP-FRANE",
    },
    {
        "VIN": "WVWZZZ1KZAW000001",
        "Nr": "B001TST",
        "Data": "2026-06-15",
        "KM": "123456",
        "Operatie": "OP-FRANE",
    },
]


# --------------------------------------------------------------------------- #
# Teste RED → GREEN                                                              #
# --------------------------------------------------------------------------- #

def test_preview_nu_contine_repr_python(client):
    """Coloana Note nu afiseaza repr Python brut pentru randuri needs_mapping.

    Fixture real cu rand needs_mapping OBLIGATORIU — altfel testul ar trece in gol
    pe un preview fara erori (coloana Note goala nu produce repr).

    Repr Python apare in codul curent cand Jinja2 randeaza
    ``{{ e.values() | list | first }}`` unde valoarea e lista ``unmapped``:
        [{'cod_op_service': 'OP-FARA-COD', 'denumire': 'OP-FARA-COD'}]
    """
    _, html = _upload_and_preview(client, _ROWS_UNMAPPED)

    # Confirma ca preview-ul contine cel putin un rand needs_mapping (fixture corect)
    has_nm = (
        "s-needs_mapping" in html
        or "needs_mapping" in html
        or "Cod RAR lipsa" in html
    )
    assert has_nm, "Testul necesita cel putin un rand needs_mapping — fixture gresit"

    # Repr Python brut: Jinja2 auto-escapa ghilimelele simple -> &#39;
    # Deci [{'cod_op_service': ...}] devine [{&#39;cod_op_service&#39;: ...}] in HTML.
    # Verificam secventa specifica a repr-ului HTML-escapata:
    assert "&#39;cod_op_service&#39;" not in html, (
        "Repr Python HTML-escapata (&#39;cod_op_service&#39;) gasita in HTML — "
        "adaptorul trebuie sa formateze erorile uman INAINTE de randare in Note"
    )


def test_preview_stare_eticheta_umana(client):
    """Starea din pill-ul fiecarui rand este eticheta umana, nu codul brut.

    Testeaza starile ok si needs_mapping (cele mai comune in preview).
    Celelalte stari (needs_review, already_sent, duplicate_in_file) sunt in testele ext.
    """
    _seed_nomenclator_si_mapare()
    _, html = _upload_and_preview(client, _ROWS_OK_SI_UNMAPPED)

    # Etichetele umane trebuie sa apara in HTML (din pill-urile randurilor)
    # "Gata de trimis" cu majuscula — nu confundam cu "gata de trimis" din
    # summary pills (deja prezente in codul curent cu lowercase)
    assert "Gata de trimis" in html, (
        "Eticheta umana 'Gata de trimis' lipsa — "
        "pill-ul randului ok afiseaza inca codul brut"
    )
    assert "Cod RAR lipsa" in html, (
        "Eticheta umana 'Cod RAR lipsa' lipsa — "
        "pill-ul randului needs_mapping afiseaza inca codul brut"
    )

    # Codurile brute NU trebuie sa apara ca text vizibil al pill-ului de rand.
    # Cautam <span class="pill ...">ok</span> (codul brut ca text al pill-ului).
    assert re.search(r'class="pill[^"]*">ok<', html) is None, (
        "Pill cu text brut 'ok' gasit in randuri — trebuie 'Gata de trimis'"
    )
    assert re.search(r'class="pill[^"]*">needs_mapping<', html) is None, (
        "Pill cu text brut 'needs_mapping' gasit in randuri — trebuie 'Cod RAR lipsa'"
    )


def test_preview_stare_eticheta_umana_duplicate(client):
    """Starea duplicate_in_file afiseaza eticheta umana 'Duplicat in fisier'."""
    _seed_nomenclator_si_mapare()
    _, html = _upload_and_preview(client, _ROWS_DUPLICATE)

    # Confirma ca exista randuri duplicate_in_file
    has_dup = "duplicate_in_file" in html or "Duplicat in fisier" in html
    assert has_dup, "Testul necesita randuri duplicate_in_file — fixture gresit"

    assert "Duplicat in fisier" in html, (
        "Eticheta umana 'Duplicat in fisier' lipsa — "
        "pill-ul randului duplicate_in_file afiseaza inca codul brut"
    )
    assert re.search(r'class="pill[^"]*">duplicate_in_file<', html) is None, (
        "Pill cu text brut 'duplicate_in_file' gasit — trebuie 'Duplicat in fisier'"
    )


def test_nota_umana_preview_needs_mapping_cu_flag_prioritizeaza_unmapped():
    """needs_mapping + flag -> Note afiseaza 'Cod RAR lipsa', nu textul flag-ului.

    BUG 3: nota_umana_preview verifica `if flags:` inaintea ramurei unmapped.
    Un rand needs_mapping care are si un flag (ex. VIN numeric) afisa textul
    flag-ului in loc de 'Cod RAR lipsa pentru: COD' — exact confuzia US-003
    voia s-o evite (userul corecteaza data si ramane blocat pe cod).
    Fix: cand status == 'needs_mapping', prioritizeaza ramura unmapped.
    """
    from app.web.labels import nota_umana_preview

    errors = [{"unmapped": [{"cod_op_service": "OP-TEST", "denumire": "Op Test"}]}]
    flags = ["VIN numeric (12345) — verificati seria sasiului"]

    result = nota_umana_preview("needs_mapping", errors, flags)

    assert "Cod RAR lipsa" in result, (
        f"needs_mapping cu flag trebuie sa afiseze 'Cod RAR lipsa', "
        f"nu textul flag-ului — primit: {result!r}"
    )
    assert "VIN numeric" not in result, (
        f"needs_mapping cu flag NU trebuie sa afiseze textul flag-ului — primit: {result!r}"
    )


def test_preview_foloseste_clasa_tabel_trimiteri(client):
    """Tabelul de preview foloseste clasa CSS .tabel-trimiteri (nu doar .tablewrap).

    Necesara pentru:
    - table-layout:fixed (fara overflow orizontal la 1280px)
    - carduri <768px: td::before citeste data-eticheta
    - col-* latimi pentru cele 4 coloane extra (col-km, col-note, col-verificat, col-actiuni)
    """
    _, html = _upload_and_preview(client, _ROWS_UNMAPPED)

    assert "tabel-trimiteri" in html, (
        "Clasa CSS 'tabel-trimiteri' lipsa din tabelul de preview — "
        "necesara pentru table-layout:fixed si carduri mobile"
    )
