"""Teste US-007 (PRD 5.12): gate review in modal — import_rows.reviewed + confirmare.

TDD RED: testele sunt scrise inainte de implementare.

Scenarii:
  1. Migrare: import_rows.reviewed INTEGER DEFAULT 0, idempotent.
  2. Rand needs_review exclus din "gata de trimis" pana la confirmare explicita.
  3. POST confirma-review seteaza reviewed=1 → randul devine ok la recalcul.
  4. `reviewed` NU intra in payload/idempotency (marcaj separat).
  5. Editarea unei valori pe un rand confirmat reseteaza reviewed=0 (D#9).
"""

from __future__ import annotations

import io
import os
import re
import tempfile

import pytest
from fastapi.testclient import TestClient


# --------------------------------------------------------------------------- #
# Fixtures                                                                      #
# --------------------------------------------------------------------------- #

@pytest.fixture()
def client(monkeypatch):
    """Client fara autentificare web obligatorie (cont 1 implicit)."""
    tmp = tempfile.mkdtemp()
    monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "ir.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()


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

def _seed_op1(account_id: int = 1) -> None:
    """Semeaza nomenclator + mapare OP-1 → R-FRANE."""
    from app.db import get_connection
    conn = get_connection()
    try:
        conn.execute(
            "INSERT OR REPLACE INTO nomenclator_rar (cod_prestatie, nume_prestatie) "
            "VALUES ('R-FRANE','Reparatie frane')"
        )
        conn.execute(
            "INSERT OR IGNORE INTO operations_mapping "
            "(account_id, cod_op_service, cod_prestatie, auto_send) "
            "VALUES (?, 'OP-1', 'R-FRANE', 1)",
            (account_id,),
        )
        conn.commit()
    finally:
        conn.close()


def _csv_bytes(rows: list[dict], sep: str = ";") -> bytes:
    import csv as _csv
    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")


# Rand care declanseaza needs_review: data in format ambiguu (DD.MM.YYYY cu zi<=12)
# si format_data=None -> col_fmt="ambiguous" -> is_ambiguous_date=True
_ROWS_NEEDS_REVIEW = [
    {
        "VIN": "WVWZZZ1KZAW000123",
        "Nr": "B001TST",
        "Data": "05.06.2026",   # Format ambiguu: zi=5 <= 12, luna=6 <= 12
        "KM": "123456",
        "Operatie": "OP-1",
    },
]

_MAP_COLS = {
    "VIN": "vin",
    "Nr": "nr_inmatriculare",
    "Data": "data_prestatie",
    "KM": "odometru_final",
    "Operatie": "operatie",
}


def _get_csrf(client: TestClient) -> str:
    r = client.get("/")
    m = re.search(r'name="csrf_token"\s+value="([^"]+)"', r.text) or \
        re.search(r'value="([^"]+)"\s+name="csrf_token"', r.text)
    return m.group(1) if m else ""


def _upload_and_preview_needs_review(client: TestClient) -> int:
    """Upload CSV cu data ambigua + salveaza mapare fara format_data → preview.

    format_data=None → col_fmt='ambiguous' → data '05.06.2026' → is_ambiguous=True
    → flag needs_review in _resolve_row_for_preview.

    Intoarce import_id.
    """
    rows = _ROWS_NEEDS_REVIEW
    csv_data = _csv_bytes(rows)
    csrf = _get_csrf(client)
    r = client.post(
        "/_import/upload",
        files={"file": ("test.csv", io.BytesIO(csv_data), "text/csv")},
        data={"csrf_token": csrf},
    )
    assert r.status_code == 200, r.text
    m = re.search(r"/_import/(\d+)/mapare-coloane", r.text)
    assert m, f"import_id negasit in raspuns: {r.text[:300]}"
    iid = int(m.group(1))
    colnames = list(rows[0].keys())
    canons = [_MAP_COLS[c] for c in colnames]
    csrf2 = _get_csrf(client)
    # IMPORTANT: format_data="" (gol/None) → date_col_format={} → col_fmt="ambiguous"
    r2 = client.post(f"/_import/{iid}/mapare-coloane", data={
        "colname": colnames,
        "canon": canons,
        "format_data": "",   # fara format -> ambiguous
        "csrf_token": csrf2,
    })
    assert r2.status_code == 200, r2.text
    return iid


def _get_reviewed(import_id: int, row_index: int) -> int:
    """Citeste valoarea reviewed din DB pentru un rand."""
    from app.db import get_connection
    conn = get_connection()
    try:
        row = conn.execute(
            "SELECT reviewed FROM import_rows WHERE batch_id=? AND row_index=?",
            (import_id, row_index),
        ).fetchone()
        return int(row["reviewed"]) if row else -1
    finally:
        conn.close()


def _get_idempotency_key(import_id: int, row_index: int) -> str | None:
    """Citeste cheia de idempotenta a unui rand prin endpoint-ul de preview."""
    from app.db import get_connection
    from app.crypto import decrypt_creds
    from app.api.v1.import_router import (
        _resolve_row_for_preview, _signature, _build_idempotency_key,
    )
    import json
    from app.mapping import account_or_default, load_mapping_meta, load_nomenclator_codes, load_text_rules
    from app.import_parse import parse_date_value

    conn = get_connection()
    try:
        acct = account_or_default(1)
        row_db = conn.execute(
            "SELECT raw_json, override_json, reviewed FROM import_rows "
            "WHERE batch_id=? AND row_index=?",
            (import_id, row_index),
        ).fetchone()
        if not row_db:
            return None
        raw = decrypt_creds(row_db["raw_json"]) or {}
        ov = decrypt_creds(row_db["override_json"]) if row_db["override_json"] else None

        col_names = list(raw.keys())
        sig = _signature(col_names)
        mapping_row = conn.execute(
            "SELECT json_mapare, format_data FROM column_mappings "
            "WHERE account_id=? AND signature_coloane=?",
            (acct, sig),
        ).fetchone()
        if not mapping_row:
            return None
        json_mapare = json.loads(mapping_row["json_mapare"])
        format_data = mapping_row["format_data"]
        date_col_format: dict[str, str] = {}
        if format_data:
            for col_f, camp_c in json_mapare.items():
                if camp_c == "data_prestatie":
                    date_col_format[col_f] = format_data

        mapping_meta = load_mapping_meta(conn, acct)
        mapping = {op: meta["cod_prestatie"] for op, meta in mapping_meta.items()}
        valid_codes = load_nomenclator_codes(conn) or None
        text_rules = load_text_rules(conn, acct)

        info = _resolve_row_for_preview(
            raw_row=raw,
            json_mapare=json_mapare,
            date_col_format=date_col_format,
            coercion_flags=[],
            mapping=mapping,
            mapping_meta=mapping_meta,
            formula_columns=[],
            override=ov or None,
            valid_codes=valid_codes,
            text_rules=text_rules,
            reviewed=bool(row_db["reviewed"]),
        )
        try:
            return _build_idempotency_key(1, info["resolved"])
        except Exception:
            return None
    finally:
        conn.close()


# --------------------------------------------------------------------------- #
# Teste                                                                         #
# --------------------------------------------------------------------------- #

def test_migrare_adauga_coloana_reviewed_idempotent():
    """_migrate adauga coloana import_rows.reviewed INTEGER DEFAULT 0.

    Idempotent: a doua invocare nu ridica eroare.
    """
    tmp = tempfile.mkdtemp()
    import os
    os.environ["AUTOPASS_DB_PATH"] = os.path.join(tmp, "m.db")
    from app.config import get_settings
    get_settings.cache_clear()
    from app.crypto import reset_cache
    reset_cache()
    try:
        from app.db import get_connection, _migrate

        # Initializare DB (inclusiv _migrate)
        from app.db import init_db
        init_db()

        conn = get_connection()
        try:
            cols = {r["name"] for r in conn.execute("PRAGMA table_info(import_rows)").fetchall()}
            assert "reviewed" in cols, \
                "import_rows trebuie sa aiba coloana 'reviewed' dupa init_db()"

            # Verifica DEFAULT 0
            row_info = {r["name"]: r for r in conn.execute("PRAGMA table_info(import_rows)").fetchall()}
            reviewed_col = row_info.get("reviewed")
            assert reviewed_col is not None
            # dflt_value poate fi "0" sau 0 in functie de SQLite versiune
            assert str(reviewed_col["dflt_value"]) == "0", \
                f"import_rows.reviewed trebuie sa aiba DEFAULT 0, got: {reviewed_col['dflt_value']}"

            # Idempotenta: a doua invocare a _migrate nu ridica eroare
            _migrate(conn)  # no exception
        finally:
            conn.close()
    finally:
        del os.environ["AUTOPASS_DB_PATH"]
        get_settings.cache_clear()
        reset_cache()


def test_needs_review_exclus_din_gata_pana_la_confirmare(client):
    """Un rand needs_review nu intra in 'gata de trimis' (n_confirmat = 0) pana la confirmare.

    US-007 Q1: randul cu data ambigua apare cu pill 'Verifica valori' si este
    EXCLUS din n_confirmat. Bannerul de discoverability (T1) trebuie sa fie prezent.
    """
    _seed_op1()
    iid = _upload_and_preview_needs_review(client)

    # Verifica preview via GET /_import/{id}/preview
    r = client.get(f"/_import/{iid}/preview")
    assert r.status_code == 200, r.text
    html = r.text

    # Randul are starea needs_review
    assert "needs_review" in html, \
        "Randul cu data ambigua trebuie sa apara cu starea needs_review in preview"
    # Pill-ul cu eticheta "Verifica valori"
    assert "Verifica valori" in html or "verifica valori" in html.lower(), \
        "Pill-ul 'Verifica valori' trebuie sa apara pentru randul needs_review"

    # n_confirmat = 0 (randul NU e in ok)
    # Cautam valoarea campului n_confirmat (value="0")
    n_match = re.search(r'id="n-confirmat"[^>]*value="(\d+)"', html) or \
              re.search(r'name="n_confirmat"[^>]*value="(\d+)"', html)
    if n_match:
        n_val = int(n_match.group(1))
        assert n_val == 0, \
            f"n_confirmat trebuie sa fie 0 cand randul e needs_review (negasit), got {n_val}"

    # Bannerul de discoverability (T1) — prezent cand summary.needs_review > 0
    # Trebuie sa contina un mesaj despre faptul ca randurile nu pleaca pana la confirmare
    # (ex. 'nu pleaca la RAR' sau 'confirmi in modal' sau 'Verifica valori')
    banner_present = (
        "nu pleaca" in html.lower() or
        "confirmi in modal" in html.lower() or
        "preview-needs-review-banner" in html
    )
    assert banner_present, \
        "Bannerul de discoverability (T1) trebuie sa fie prezent cand exista randuri needs_review"


def test_confirmare_in_modal_seteaza_reviewed_si_devine_ok(client):
    """POST /_import/{id}/rand/0/confirma-review seteaza reviewed=1.

    US-007 T2: operatorul apasa 'Confirma valorile' in modal →
    reviewed=1 in DB → randul devine ok la recalcul preview.

    Verifica:
    - Raspuns 200
    - reviewed=1 in DB
    - HX-Trigger: randSalvat cu noua stare 'Gata de trimis' (pentru toast)
    - HX-Trigger: reincarcaPreview + HX-Trigger-After-Settle: inchideModal
    """
    import json as _json
    _seed_op1()
    iid = _upload_and_preview_needs_review(client)

    # Verifica ca randul e needs_review inainte de confirmare
    assert _get_reviewed(iid, 0) == 0, "reviewed trebuie sa fie 0 inainte de confirmare"

    # POST confirma-review
    csrf = _get_csrf(client)
    r = client.post(f"/_import/{iid}/rand/0/confirma-review", data={"csrf_token": csrf})
    assert r.status_code == 200, r.text

    # reviewed=1 in DB
    assert _get_reviewed(iid, 0) == 1, \
        "reviewed trebuie sa fie 1 in DB dupa confirmare"

    # Contractul nou: reload preview + randSalvat cu noua stare (nu OOB pe <tr>).
    trig = _json.loads(r.headers.get("HX-Trigger", "{}"))
    assert trig.get("reincarcaPreview") is True, "confirma-review trebuie sa ceara reincarcaPreview"
    assert trig.get("randSalvat", {}).get("stare") == "Gata de trimis", \
        "Dupa confirmare, randSalvat.stare trebuie sa fie 'Gata de trimis' (pentru toast)"

    # Modal se inchide
    trigger = r.headers.get("HX-Trigger-After-Settle", "")
    assert "inchideModal" in trigger, \
        f"confirma-review trebuie sa emita inchideModal, got: '{trigger}'"


def test_reviewed_nu_intra_in_payload_sau_idempotency(client):
    """reviewed NU intra in payload, override_json sau cheia de idempotenta.

    US-007 marcaj separat (D#8): reviewed e DOAR un flag de confirmare umana,
    nu un camp de continut. Cheia de idempotenta trebuie sa fie identica inainte
    si dupa confirmare.
    """
    _seed_op1()
    iid = _upload_and_preview_needs_review(client)

    # Cheia inainte de confirmare
    key_before = _get_idempotency_key(iid, 0)
    assert key_before is not None, "Cheia de idempotenta trebuie sa existe inainte de confirmare"

    # Confirma
    csrf = _get_csrf(client)
    r = client.post(f"/_import/{iid}/rand/0/confirma-review", data={"csrf_token": csrf})
    assert r.status_code == 200, r.text

    # Cheia dupa confirmare
    key_after = _get_idempotency_key(iid, 0)
    assert key_after is not None, "Cheia de idempotenta trebuie sa existe dupa confirmare"

    assert key_before == key_after, \
        f"Cheia de idempotenta NU trebuie sa se schimbe la confirmare! " \
        f"Inainte: {key_before}, dupa: {key_after}. " \
        "'reviewed' NU trebuie sa intre in payload sau cheia de idempotenta."

    # Verifica ca 'reviewed' nu e in override_json
    from app.db import get_connection
    from app.crypto import decrypt_creds
    conn = get_connection()
    try:
        row = conn.execute(
            "SELECT override_json FROM import_rows WHERE batch_id=? AND row_index=?",
            (iid, 0),
        ).fetchone()
        if row and row["override_json"]:
            ov = decrypt_creds(row["override_json"]) or {}
            assert "reviewed" not in ov, \
                "'reviewed' NU trebuie sa fie in override_json (camp separat, nu camp de continut)"
    finally:
        conn.close()


def test_editare_valoare_pe_needs_review_reseteaza_reviewed(client):
    """Editarea unei valori pe un rand confirmat reseteaza reviewed=0 (D#9).

    Cand operatorul SCHIMBA o valoare (via POST editeaza) pe un rand deja confirmat
    (reviewed=1), apply_row_override trebuie sa reseteze reviewed=0.
    → Randul se intoarce in starea needs_review si cere re-confirmare.
    """
    _seed_op1()
    iid = _upload_and_preview_needs_review(client)

    # Confirma randul
    csrf = _get_csrf(client)
    r = client.post(f"/_import/{iid}/rand/0/confirma-review", data={"csrf_token": csrf})
    assert r.status_code == 200, r.text
    assert _get_reviewed(iid, 0) == 1, "reviewed trebuie sa fie 1 dupa confirmare"

    # Editeaza o valoare (odometru_final)
    csrf2 = _get_csrf(client)
    r2 = client.post(f"/_import/{iid}/rand/0/editeaza", data={
        "odometru_final": "200000",
        "csrf_token": csrf2,
    })
    assert r2.status_code == 200, r2.text

    # reviewed trebuie sa fie 0 (resetat de apply_row_override)
    assert _get_reviewed(iid, 0) == 0, \
        "reviewed trebuie sa fie resetat la 0 dupa editarea unei valori (D#9 — re-cere confirmare)"


def test_confirma_review_guard_committed_409(client):
    """POST confirma-review pe batch deja comis → 409 (guard committed)."""
    _seed_op1()
    iid = _upload_and_preview_needs_review(client)

    # Marcheaza batch ca committed
    from app.db import get_connection
    conn = get_connection()
    try:
        conn.execute("UPDATE import_batches SET status='committed' WHERE id=?", (iid,))
        conn.commit()
    finally:
        conn.close()

    csrf = _get_csrf(client)
    r = client.post(f"/_import/{iid}/rand/0/confirma-review", data={"csrf_token": csrf})
    assert r.status_code == 409, \
        f"confirma-review pe batch committed trebuie sa returneze 409, got {r.status_code}"


def test_confirma_review_scoped_404_alt_cont():
    """POST confirma-review pe un rand al altui cont → 404 (scoping JOIN)."""
    tmp = tempfile.mkdtemp()
    env_patch = {
        "AUTOPASS_DB_PATH": os.path.join(tmp, "scope.db"),
        "AUTOPASS_WEB_AUTH_REQUIRED": "true",
    }
    for k, v in env_patch.items():
        os.environ[k] = v
    from app.config import get_settings
    get_settings.cache_clear()
    from app.crypto import reset_cache
    reset_cache()
    from app.web import ratelimit
    ratelimit._hits.clear()
    from app.main import app

    try:
        with TestClient(app, follow_redirects=False) as c:
            from app.db import get_connection
            from app.accounts import create_account
            from app.users import create_user
            conn = get_connection()
            try:
                acct1 = create_account(conn, "Firma A", active=True)
                create_user(conn, acct1, "userA@test.com", "parola123secure")
                acct2 = create_account(conn, "Firma B", active=True)
                create_user(conn, acct2, "userB@test.com", "parola123secure")
            finally:
                conn.close()

            # Semeaza operatii pentru acct1
            from app.db import get_connection as gcn
            conn2 = gcn()
            try:
                conn2.execute(
                    "INSERT OR REPLACE INTO nomenclator_rar (cod_prestatie, nume_prestatie) "
                    "VALUES ('R-FRANE','Reparatie frane')"
                )
                conn2.execute(
                    "INSERT OR IGNORE INTO operations_mapping "
                    "(account_id, cod_op_service, cod_prestatie, auto_send) "
                    "VALUES (?, 'OP-1', 'R-FRANE', 1)", (acct1,)
                )
                conn2.commit()
            finally:
                conn2.close()

            # Login ca userA, creeaza batch
            def _login(client, email, pwd="parola123secure"):
                resp = client.get("/login")
                m = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp.text) or \
                    re.search(r'value="([^"]+)"\s+name="csrf_token"', resp.text)
                assert m
                client.post("/login", data={
                    "email": email, "parola": pwd, "csrf_token": m.group(1),
                })

            _login(c, "userA@test.com")

            rows = _ROWS_NEEDS_REVIEW
            csv_data = _csv_bytes(rows)

            def _csrf():
                r = c.get("/")
                m = re.search(r'name="csrf_token"\s+value="([^"]+)"', r.text) or \
                    re.search(r'value="([^"]+)"\s+name="csrf_token"', r.text)
                return m.group(1) if m else ""

            csrf = _csrf()
            r = c.post(
                "/_import/upload",
                files={"file": ("test.csv", io.BytesIO(csv_data), "text/csv")},
                data={"csrf_token": csrf},
            )
            assert r.status_code == 200
            m = re.search(r"/_import/(\d+)/mapare-coloane", r.text)
            assert m
            iid = int(m.group(1))
            colnames = list(rows[0].keys())
            canons = [_MAP_COLS[c] for c in colnames]
            csrf2 = _csrf()
            c.post(f"/_import/{iid}/mapare-coloane", data={
                "colname": colnames, "canon": canons,
                "format_data": "", "csrf_token": csrf2,
            })

            # Login ca userB si incearca confirma-review pe batch-ul lui A
            _login(c, "userB@test.com")
            csrf3 = _csrf()
            r2 = c.post(f"/_import/{iid}/rand/0/confirma-review", data={"csrf_token": csrf3})
            assert r2.status_code == 404, \
                f"confirma-review cross-account trebuie sa returneze 404, got {r2.status_code}"
    finally:
        for k in env_patch:
            if k in os.environ:
                del os.environ[k]
        ratelimit._hits.clear()
        get_settings.cache_clear()
        reset_cache()


# --------------------------------------------------------------------------- #
# Teste markup Bug 1: confirma-review form swap (B1)                           #
# --------------------------------------------------------------------------- #

def test_confirma_review_form_nu_foloseste_hx_swap_none():
    """Bug B1 (markup): formularul confirma-review NU trebuie sa foloseasca
    hx-swap='none' — cu none, htmx aplica doar OOB-urile dar NU executa scriptul
    din continutul principal → updateN() nu ruleaza → n_confirmat stale → 422.

    Forma corecta: hx-target='#detaliu-modal-body' + hx-swap='innerHTML'.
    """
    import pathlib
    template = (
        pathlib.Path(__file__).parent.parent
        / "app/web/templates/_editare_preview_modal.html"
    )
    html = template.read_text(encoding="utf-8")

    # Gasim sectiunea formularului confirma-review (dupa marcajul T2)
    idx = html.find("confirma-review")
    assert idx >= 0, "Formularul confirma-review nu a fost gasit in template"
    form_section = html[idx:]

    # NU trebuie sa existe hx-swap="none" pe formularul confirma-review
    assert 'hx-swap="none"' not in form_section, (
        "Formularul confirma-review NU trebuie sa foloseasca hx-swap='none'. "
        "Cu none, scriptul updateN() nu ruleaza → n_confirmat stale → gate 422."
    )

    # TREBUIE sa tinteasca #detaliu-modal-body cu innerHTML
    assert 'hx-target="#detaliu-modal-body"' in form_section, (
        "Formularul confirma-review trebuie sa aiba hx-target='#detaliu-modal-body' "
        "ca scriptul updateN sa fie executat (identic cu formularul editeaza)."
    )
    assert 'hx-swap="innerHTML"' in form_section, (
        "Formularul confirma-review trebuie sa aiba hx-swap='innerHTML' "
        "ca scriptul updateN sa fie executat."
    )


def _upload_and_preview_rows(client: TestClient, rows: list[dict]) -> int:
    """Upload CSV cu `rows` + salveaza mapare fara format_data -> preview.

    Generalizarea lui `_upload_and_preview_needs_review` pentru mai multe randuri
    (fiecare cu data ambigua -> needs_review). Intoarce import_id.
    """
    csv_data = _csv_bytes(rows)
    csrf = _get_csrf(client)
    r = client.post(
        "/_import/upload",
        files={"file": ("test.csv", io.BytesIO(csv_data), "text/csv")},
        data={"csrf_token": csrf},
    )
    assert r.status_code == 200, r.text
    m = re.search(r"/_import/(\d+)/mapare-coloane", r.text)
    assert m, f"import_id negasit in raspuns: {r.text[:300]}"
    iid = int(m.group(1))
    colnames = list(rows[0].keys())
    canons = [_MAP_COLS[c] for c in colnames]
    csrf2 = _get_csrf(client)
    r2 = client.post(f"/_import/{iid}/mapare-coloane", data={
        "colname": colnames,
        "canon": canons,
        "format_data": "",   # fara format -> ambiguous -> needs_review
        "csrf_token": csrf2,
    })
    assert r2.status_code == 200, r2.text
    return iid


# Trei randuri, VIN-uri distincte (nu se dedupe ca duplicat), toate cu data ambigua.
_ROWS_MULTI_NEEDS_REVIEW = [
    {"VIN": "WVWZZZ1KZAW000123", "Nr": "B001TST", "Data": "05.06.2026", "KM": "123456", "Operatie": "OP-1"},
    {"VIN": "WVWZZZ1KZAW000456", "Nr": "B002TST", "Data": "07.08.2026", "KM": "223456", "Operatie": "OP-1"},
    {"VIN": "WVWZZZ1KZAW000789", "Nr": "B003TST", "Data": "09.10.2026", "KM": "323456", "Operatie": "OP-1"},
]


def test_confirma_toate_review_marcheaza_toate(client):
    """B1 bulk: POST /_import/{id}/confirma-toate-review seteaza reviewed=1 pe TOATE
    randurile needs_review din batch cu un singur click.

    Verifica:
    - Raspuns 200 cu preview re-randat (fragment #import-section)
    - reviewed=1 in DB pentru toate cele 3 randuri
    - dupa recalcul, randurile nu mai sunt needs_review (devin ok -> gata de trimis)
    """
    _seed_op1()
    iid = _upload_and_preview_rows(client, _ROWS_MULTI_NEEDS_REVIEW)

    # Toate randurile pornesc needs_review (reviewed=0)
    for i in range(3):
        assert _get_reviewed(iid, i) == 0, f"randul {i} trebuie sa fie reviewed=0 initial"

    csrf = _get_csrf(client)
    r = client.post(f"/_import/{iid}/confirma-toate-review", data={"csrf_token": csrf})
    assert r.status_code == 200, r.text
    # Raspunsul re-randeaza sectiunea de preview
    assert "import-section" in r.text, "bulk trebuie sa re-randeze #import-section"

    # Toate randurile confirmate in DB
    for i in range(3):
        assert _get_reviewed(iid, i) == 1, \
            f"randul {i} trebuie sa fie reviewed=1 dupa confirmarea in bloc"

    # Recalcul: niciun rand nu mai e needs_review (toate ok)
    r2 = client.get(f"/_import/{iid}/preview")
    assert r2.status_code == 200, r2.text
    assert 'data-status="needs_review"' not in r2.text, \
        "dupa confirmarea in bloc, niciun rand nu mai trebuie sa fie needs_review"


def test_confirma_toate_review_guard_committed_409(client):
    """POST confirma-toate-review pe batch deja comis -> 409."""
    _seed_op1()
    iid = _upload_and_preview_rows(client, _ROWS_MULTI_NEEDS_REVIEW)

    from app.db import get_connection
    conn = get_connection()
    try:
        conn.execute("UPDATE import_batches SET status='committed' WHERE id=?", (iid,))
        conn.commit()
    finally:
        conn.close()

    csrf = _get_csrf(client)
    r = client.post(f"/_import/{iid}/confirma-toate-review", data={"csrf_token": csrf})
    assert r.status_code == 409, \
        f"confirma-toate-review pe batch committed trebuie sa returneze 409, got {r.status_code}"


def test_confirma_toate_review_scoped_404_alt_cont():
    """POST confirma-toate-review pe batch-ul altui cont -> 404, iar randurile
    contului A raman needs_review (reviewed=0): bulk-ul altui cont NU le atinge."""
    tmp = tempfile.mkdtemp()
    env_patch = {
        "AUTOPASS_DB_PATH": os.path.join(tmp, "scope_bulk.db"),
        "AUTOPASS_WEB_AUTH_REQUIRED": "true",
    }
    for k, v in env_patch.items():
        os.environ[k] = v
    from app.config import get_settings
    get_settings.cache_clear()
    from app.crypto import reset_cache
    reset_cache()
    from app.web import ratelimit
    ratelimit._hits.clear()
    from app.main import app

    try:
        with TestClient(app, follow_redirects=False) as c:
            from app.db import get_connection
            from app.accounts import create_account
            from app.users import create_user
            conn = get_connection()
            try:
                acct1 = create_account(conn, "Firma A", active=True)
                create_user(conn, acct1, "userA@test.com", "parola123secure")
                acct2 = create_account(conn, "Firma B", active=True)
                create_user(conn, acct2, "userB@test.com", "parola123secure")
                conn.execute(
                    "INSERT OR REPLACE INTO nomenclator_rar (cod_prestatie, nume_prestatie) "
                    "VALUES ('R-FRANE','Reparatie frane')"
                )
                conn.execute(
                    "INSERT OR IGNORE INTO operations_mapping "
                    "(account_id, cod_op_service, cod_prestatie, auto_send) "
                    "VALUES (?, 'OP-1', 'R-FRANE', 1)", (acct1,)
                )
                conn.commit()
            finally:
                conn.close()

            def _login(client, email, pwd="parola123secure"):
                resp = client.get("/login")
                m = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp.text) or \
                    re.search(r'value="([^"]+)"\s+name="csrf_token"', resp.text)
                assert m
                client.post("/login", data={"email": email, "parola": pwd, "csrf_token": m.group(1)})

            def _csrf():
                r = c.get("/")
                m = re.search(r'name="csrf_token"\s+value="([^"]+)"', r.text) or \
                    re.search(r'value="([^"]+)"\s+name="csrf_token"', r.text)
                return m.group(1) if m else ""

            # userA creeaza batch cu randuri needs_review
            _login(c, "userA@test.com")
            rows = _ROWS_MULTI_NEEDS_REVIEW
            csrf = _csrf()
            r = c.post(
                "/_import/upload",
                files={"file": ("test.csv", io.BytesIO(_csv_bytes(rows)), "text/csv")},
                data={"csrf_token": csrf},
            )
            assert r.status_code == 200
            m = re.search(r"/_import/(\d+)/mapare-coloane", r.text)
            assert m
            iid = int(m.group(1))
            colnames = list(rows[0].keys())
            canons = [_MAP_COLS[c] for c in colnames]
            csrf2 = _csrf()
            c.post(f"/_import/{iid}/mapare-coloane", data={
                "colname": colnames, "canon": canons, "format_data": "", "csrf_token": csrf2,
            })

            # userB incearca bulk pe batch-ul lui A -> 404
            _login(c, "userB@test.com")
            csrf3 = _csrf()
            r2 = c.post(f"/_import/{iid}/confirma-toate-review", data={"csrf_token": csrf3})
            assert r2.status_code == 404, \
                f"confirma-toate-review cross-account trebuie sa returneze 404, got {r2.status_code}"

            # Randurile lui A raman needs_review (reviewed=0): nu au fost atinse.
            conn = get_connection()
            try:
                n = conn.execute(
                    "SELECT COUNT(*) AS c FROM import_rows WHERE batch_id=? AND reviewed=1",
                    (iid,),
                ).fetchone()["c"]
                assert n == 0, "bulk-ul altui cont NU trebuie sa marcheze reviewed pe randurile lui A"
            finally:
                conn.close()
    finally:
        for k in env_patch:
            if k in os.environ:
                del os.environ[k]
        ratelimit._hits.clear()
        get_settings.cache_clear()
        reset_cache()


def test_confirm_rapid_buton_prezent_in_tabel(client):
    """B1 quick: randul needs_review are butonul de confirm rapid direct in tabel,
    care POST-eaza pe ruta /confirma-review (fara a deschide modalul)."""
    _seed_op1()
    iid = _upload_and_preview_needs_review(client)

    r = client.get(f"/_import/{iid}/preview")
    assert r.status_code == 200, r.text
    html = r.text
    assert "btn-confirma-rapid" in html, \
        "randul needs_review trebuie sa aiba butonul de confirm rapid in tabel"
    assert f"/_import/{iid}/rand/0/confirma-review" in html, \
        "butonul de confirm rapid trebuie sa POST-eze pe ruta /confirma-review"


def test_confirm_rapid_per_rand_seteaza_reviewed(client):
    """B1 quick: POST /confirma-review din tabel (fara modal) seteaza reviewed=1 pe un
    singur rand si NU atinge celelalte randuri needs_review din batch."""
    _seed_op1()
    iid = _upload_and_preview_rows(client, _ROWS_MULTI_NEEDS_REVIEW)

    csrf = _get_csrf(client)
    r = client.post(f"/_import/{iid}/rand/1/confirma-review", data={"csrf_token": csrf})
    assert r.status_code == 200, r.text

    assert _get_reviewed(iid, 1) == 1, "randul confirmat rapid trebuie sa fie reviewed=1"
    assert _get_reviewed(iid, 0) == 0, "confirmul per-rand NU trebuie sa atinga alte randuri"
    assert _get_reviewed(iid, 2) == 0, "confirmul per-rand NU trebuie sa atinga alte randuri"


def test_confirma_review_cere_reincarcarea_preview(client):
    """Contractul nou (dogfood 5.13): confirma-review NU mai depinde de scriptul updateN
    din payload (care, cu OOB pe <tr> rupt, lasa randul stale). Acum cere reincarcaPreview,
    iar preview-ul reincarcat re-randeaza contorul si butonul de confirmare cu n_confirmat
    corect server-side — deci problema B1 (n_confirmat stale -> 422) dispare structural.

    Verifica:
    - Raspuns 200
    - HX-Trigger contine reincarcaPreview (reincarca contorul/confirmarea, fresh)
    """
    import json as _json
    _seed_op1()
    iid = _upload_and_preview_needs_review(client)

    csrf = _get_csrf(client)
    r = client.post(f"/_import/{iid}/rand/0/confirma-review", data={"csrf_token": csrf})
    assert r.status_code == 200, r.text

    trig = _json.loads(r.headers.get("HX-Trigger", "{}"))
    assert trig.get("reincarcaPreview") is True, (
        "confirma-review trebuie sa ceara reincarcaPreview — preview-ul reincarcat aduce "
        "n_confirmat corect server-side (fara dependenta de scriptul updateN din payload)."
    )
