"""Teste US-004 — un singur „Salveaza maparile" pe panoul de operatii nemapate.

Ruta noua: POST /_import/{id}/mapare-operatii (plural)
  - primeste perechi (cod_op_service, cod_prestatie) ca liste paralele
  - apeleaza save_mapping pentru fiecare pereche cu cod ales (reuse exact)
  - ignora perechile cu cod_prestatie gol (nu eroare, nu salvare)
  - D#12: validare per-item — cod invalid -> skip + sumar, restul salvate
  - O singura _web_compute_preview + re-randare #import-section la final
  - CSRF + scoped sesiune + guard batch committed (409) pastrate
"""

from __future__ import annotations

import csv as csv_mod
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"))
    # Mod dev: fallback cont 1, fara login/CSRF (ca in test_import_mapare_operatie).
    monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false")
    from app.config import get_settings

    get_settings.cache_clear()
    from app.main import app
    from fastapi.testclient import TestClient

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


# ------------------------------------------------------------------ #
# Helpers de setup                                                     #
# ------------------------------------------------------------------ #

_HEADER = ["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final",
           "Cod operatie", "Denumire"]
_CANON = ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final",
          "operatie", "denumire_op"]

# Doua operatii distincte: OP-REV si OP-FR
_ROWS_2OPS = [
    ["WVWZZZ1KZAW001111", "B100TST", "2026-06-15", "123456", "OP-REV", "Revizie periodica"],
    ["WVWZZZ1KZAW002222", "CJ200AB", "2026-05-20", "98765",  "OP-REV", "Revizie periodica"],
    ["WVWZZZ1KZAW003333", "IS300CD", "2026-04-10", "50000",  "OP-FR",  "Franare"],
]

# O singura operatie
_ROWS_1OP = [
    ["WVWZZZ1KZAW001111", "B100TST", "2026-06-15", "123456", "OP-REV", "Revizie periodica"],
    ["WVWZZZ1KZAW002222", "CJ200AB", "2026-05-20", "98765",  "OP-REV", "Revizie periodica"],
]


def _csv_bytes(header, rows, sep=";") -> bytes:
    buf = io.StringIO()
    w = csv_mod.writer(buf, delimiter=sep)
    w.writerow(header)
    for r in rows:
        w.writerow(r)
    return buf.getvalue().encode("utf-8")


def _upload(client, rows=None) -> int:
    """Incarca fisier CSV si intoarce import_id."""
    rows = _ROWS_2OPS if rows is None else rows
    r = client.post(
        "/_import/upload",
        files={"file": ("t.csv", _csv_bytes(_HEADER, rows), "text/csv")},
    )
    assert r.status_code == 200, r.text
    m = re.search(r"/_import/(\d+)/mapare-coloane", r.text)
    assert m, f"form mapare-coloane lipsa: {r.text[:300]}"
    return int(m.group(1))


def _map_columns(client, import_id, canon=None):
    return client.post(
        f"/_import/{import_id}/mapare-coloane",
        data={
            "colname": _HEADER,
            "canon": canon or _CANON,
            "format_data": "YYYY-MM-DD",
        },
    )


def _get_batch_counts(import_id):
    from app.db import get_connection
    conn = get_connection()
    try:
        return conn.execute(
            "SELECT ok, needs_mapping FROM import_batches WHERE id=?", (import_id,)
        ).fetchone()
    finally:
        conn.close()


def _get_row_statuses(import_id):
    from app.db import get_connection
    conn = get_connection()
    try:
        rows = conn.execute(
            "SELECT resolved_status FROM import_rows WHERE batch_id=? ORDER BY row_index",
            (import_id,),
        ).fetchall()
        return [r["resolved_status"] for r in rows]
    finally:
        conn.close()


def _get_mapping(cod_op_service, account_id=1):
    from app.db import get_connection
    conn = get_connection()
    try:
        return conn.execute(
            "SELECT cod_prestatie FROM operations_mapping WHERE account_id=? AND cod_op_service=?",
            (account_id, cod_op_service),
        ).fetchone()
    finally:
        conn.close()


# ------------------------------------------------------------------ #
# 1. Salveaza multiple operatii intr-un singur POST                   #
# ------------------------------------------------------------------ #

def test_mapare_operatii_salveaza_multiple_intr_un_post(client):
    """POST mapare-operatii cu 2 operatii alese -> ambele salvate, randurile trec la ok."""
    import_id = _upload(client)
    r = _map_columns(client, import_id)
    assert r.status_code == 200

    # Inainte: 0 ok, 3 needs_mapping
    b = _get_batch_counts(import_id)
    assert b["needs_mapping"] == 3
    assert b["ok"] == 0

    # Un singur POST cu ambele operatii
    rm = client.post(
        f"/_import/{import_id}/mapare-operatii",
        data={
            "cod_op_service": ["OP-REV", "OP-FR"],
            "cod_prestatie": ["OE-3", "OE-1"],
        },
    )
    assert rm.status_code == 200, rm.text

    # Raspuns e preview re-randat cu #import-section
    assert "import-section" in rm.text

    # Ambele mapari persistate
    assert _get_mapping("OP-REV") is not None
    assert _get_mapping("OP-REV")["cod_prestatie"] == "OE-3"
    assert _get_mapping("OP-FR") is not None
    assert _get_mapping("OP-FR")["cod_prestatie"] == "OE-1"

    # Toate randurile trecute la ok
    b2 = _get_batch_counts(import_id)
    assert b2["ok"] == 3, b2
    assert b2["needs_mapping"] == 0, b2

    statuses = _get_row_statuses(import_id)
    assert all(s == "ok" for s in statuses), statuses


def test_panoul_mapare_are_un_singur_form(client):
    """Preview-ul randeaza panoul de mapare cu un singur <form> si un buton Salveaza maparile."""
    import_id = _upload(client)
    r = _map_columns(client, import_id)
    assert r.status_code == 200

    # Ruta noua mapare-operatii (plural) prezenta in form
    assert f"/_import/{import_id}/mapare-operatii" in r.text
    # Un singur buton Salveaza
    assert "Salveaza maparile" in r.text
    # NU apare ruta singular mapare-operatie ca target de form (panoul unificat)
    # (poate apare ca ruta pastrata, dar nu ca hx-post al formularului de mapare in bulk)


# ------------------------------------------------------------------ #
# 2. Ignora operatiile fara cod ales                                  #
# ------------------------------------------------------------------ #

def test_mapare_operatii_ignora_randuri_neselectate(client):
    """Operatia cu cod_prestatie gol e ignorata (nu eroare, nu salvare)."""
    import_id = _upload(client, rows=_ROWS_2OPS)
    _map_columns(client, import_id)

    # OP-REV cu cod ales, OP-FR fara cod ales (string gol = "— alege cod RAR —")
    rm = client.post(
        f"/_import/{import_id}/mapare-operatii",
        data={
            "cod_op_service": ["OP-REV", "OP-FR"],
            "cod_prestatie": ["OE-3", ""],
        },
    )
    assert rm.status_code == 200, rm.text

    # OP-REV salvat
    assert _get_mapping("OP-REV") is not None

    # OP-FR nesal vat (nu eroare, nu mapare)
    assert _get_mapping("OP-FR") is None

    # Randurile OP-REV (2) sunt ok, OP-FR (1) raman needs_mapping
    b = _get_batch_counts(import_id)
    assert b["ok"] == 2, b
    assert b["needs_mapping"] == 1, b

    # Panoul mai arata OP-FR (inca nemapat)
    assert "OP-FR" in rm.text


def test_mapare_operatii_fara_nicio_selectie_nu_eroare(client):
    """POST cu toate cod_prestatie goale -> nici o eroare, nici o salvare, preview re-randat."""
    import_id = _upload(client, rows=_ROWS_1OP)
    _map_columns(client, import_id)

    rm = client.post(
        f"/_import/{import_id}/mapare-operatii",
        data={
            "cod_op_service": ["OP-REV"],
            "cod_prestatie": [""],
        },
    )
    assert rm.status_code == 200, rm.text

    # Nicio mapare salvata
    assert _get_mapping("OP-REV") is None

    # Randurile raman needs_mapping
    b = _get_batch_counts(import_id)
    assert b["needs_mapping"] == 2, b
    assert b["ok"] == 0, b


# ------------------------------------------------------------------ #
# 3. Re-rezolva randurile blocate cu needs_mapping                    #
# ------------------------------------------------------------------ #

def test_mapare_operatii_re_rezolva_blocatele(client):
    """Operatiile cu cod ales trec din needs_mapping la ok (re-rezolvare imediata)."""
    import_id = _upload(client, rows=_ROWS_1OP)
    _map_columns(client, import_id)

    # Inainte: 2 needs_mapping
    b = _get_batch_counts(import_id)
    assert b["needs_mapping"] == 2, b
    assert b["ok"] == 0, b

    rm = client.post(
        f"/_import/{import_id}/mapare-operatii",
        data={
            "cod_op_service": ["OP-REV"],
            "cod_prestatie": ["OE-3"],
        },
    )
    assert rm.status_code == 200

    # Dupa: 2 ok, 0 needs_mapping
    b2 = _get_batch_counts(import_id)
    assert b2["ok"] == 2, b2
    assert b2["needs_mapping"] == 0, b2

    statuses = _get_row_statuses(import_id)
    assert all(s == "ok" for s in statuses), statuses

    # Preview randat nu mai arata panoul de mapare
    assert "Operatii de mapat la cod RAR" not in rm.text


# ------------------------------------------------------------------ #
# 4. D#12 — validare per-item: cod invalid skip + sumar, restul ok   #
# ------------------------------------------------------------------ #

def test_mapare_operatii_cod_invalid_skip_salveaza_restul(client):
    """D#12: daca un cod ales e invalid (1 din 2), skip-ul + sumar, celalalt salvat, 1 re-render."""
    import_id = _upload(client, rows=_ROWS_2OPS)
    _map_columns(client, import_id)

    # OP-REV cod valid, OP-FR cod inexistent
    rm = client.post(
        f"/_import/{import_id}/mapare-operatii",
        data={
            "cod_op_service": ["OP-REV", "OP-FR"],
            "cod_prestatie": ["OE-3", "COD-INEXISTENT"],
        },
    )
    assert rm.status_code == 200, rm.text

    # OP-REV salvat (codul valid)
    assert _get_mapping("OP-REV") is not None
    assert _get_mapping("OP-REV")["cod_prestatie"] == "OE-3"

    # OP-FR nesalvat (cod invalid)
    assert _get_mapping("OP-FR") is None

    # Sumar in mesaj: cod invalid mentionat
    assert "COD-INEXISTENT" in rm.text or "necunoscut" in rm.text.lower()

    # Randurile OP-REV (2) ok, OP-FR (1) inca needs_mapping
    b = _get_batch_counts(import_id)
    assert b["ok"] == 2, b
    assert b["needs_mapping"] == 1, b

    # O singura re-randare (200, nu redirect, nu multiple #import-section)
    assert rm.text.count("import-section") >= 1


# ------------------------------------------------------------------ #
# 5. Guard batch committed (409)                                      #
# ------------------------------------------------------------------ #

def test_mapare_operatii_batch_committed_409(client):
    """Batch deja comis -> 409 Conflict."""
    import_id = _upload(client, rows=_ROWS_1OP)
    _map_columns(client, import_id)

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

    rm = client.post(
        f"/_import/{import_id}/mapare-operatii",
        data={
            "cod_op_service": ["OP-REV"],
            "cod_prestatie": ["OE-3"],
        },
    )
    assert rm.status_code == 409, rm.text


# ------------------------------------------------------------------ #
# 6. Guard scoped sesiune (404 cross-account)                         #
# ------------------------------------------------------------------ #

def test_mapare_operatii_scoped_404_alt_cont(monkeypatch):
    """Import apartinand altui cont -> 404 (scoping corect)."""
    tmp = tempfile.mkdtemp()
    monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "scope.db"))
    monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "true")
    from app.config import get_settings
    get_settings.cache_clear()

    from app.accounts import create_account
    from app.users import create_user
    from app.db import get_connection
    from app.main import app
    from fastapi.testclient import TestClient

    with TestClient(app, follow_redirects=False) as c:
        conn = get_connection()
        try:
            acct_a = create_account(conn, "ServiceA", active=True)
            create_user(conn, acct_a, "a@test.com", "parolasecreta10")
            acct_b = create_account(conn, "ServiceB", active=True)
            create_user(conn, acct_b, "b@test.com", "parolasecreta10")
        finally:
            conn.close()

        def _login_and_get_csrf(c, email, password="parolasecreta10"):
            resp = c.get("/login")
            m = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp.text)
            assert m
            csrf = m.group(1)
            c.post("/login", data={"email": email, "parola": password, "csrf_token": csrf})
            # Get a fresh CSRF token for next request
            resp2 = c.get("/?tab=import")
            m2 = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp2.text)
            return m2.group(1) if m2 else csrf

        # Login cu cont A, upload fisier (batch apartine cont A)
        csrf_a = _login_and_get_csrf(c, "a@test.com")

        r = c.post(
            "/_import/upload",
            files={"file": ("t.csv", _csv_bytes(_HEADER, _ROWS_1OP), "text/csv")},
            data={"csrf_token": csrf_a},
        )
        assert r.status_code == 200, r.text
        m2 = re.search(r"/_import/(\d+)/mapare-coloane", r.text)
        assert m2
        import_id = int(m2.group(1))

        # Extrage CSRF din raspuns pentru mapare-coloane
        m_csrf = re.search(r'name="csrf_token"\s+value="([^"]+)"', r.text)
        csrf_for_map = m_csrf.group(1) if m_csrf else csrf_a
        c.post(f"/_import/{import_id}/mapare-coloane",
               data={"colname": _HEADER, "canon": _CANON, "format_data": "YYYY-MM-DD",
                     "csrf_token": csrf_for_map})

        # Login cu cont B, incearca mapare pe batch-ul lui A
        csrf_b = _login_and_get_csrf(c, "b@test.com")

        rm = c.post(
            f"/_import/{import_id}/mapare-operatii",
            data={
                "cod_op_service": ["OP-REV"],
                "cod_prestatie": ["OE-3"],
                "csrf_token": csrf_b,
            },
        )
        assert rm.status_code == 404, f"expected 404 got {rm.status_code}: {rm.text[:200]}"

    get_settings.cache_clear()
