"""Teste regula "exclude de la declarare" (operations_mapping.exclus).

O operatie exclusa nu se declara la RAR: randurile de import cu toate operatiile
excluse devin 'excluded' (Nedeclarat) si nu se comit; operatiile excluse dispar
din panoul de mapare; submission-urile API blocate trec pe needs_data cu motiv.
Un cod ales explicit pe rand bate regula de excludere.
"""

from __future__ import annotations

import io
import json
import os
import re
import tempfile

import pytest
from fastapi.testclient import TestClient

from tests.test_web_preview_edit import (  # helpers reutilizate
    _csv_bytes,
    _get_csrf,
    _upload_and_preview,
)


@pytest.fixture()
def client(monkeypatch):
    tmp = tempfile.mkdtemp()
    monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "excl.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()


_ROWS_ITP = [
    {
        "VIN": "WVWZZZ3CZ9E123456",
        "Nr": "TM789BC",
        "Data": "2026-05-15",
        "KM": "82500",
        "Operatie": "OP-ITP",
    },
    {
        "VIN": "WVWZZZ1KZAW000123",
        "Nr": "B001TST",
        "Data": "2026-06-10",
        "KM": "123456",
        "Operatie": "OP-1",
    },
]


def _seed(account_id: int = 1) -> None:
    """Nomenclator + mapare OP-1 -> R-FRANE (OP-ITP ramane nemapat)."""
    from app.db import get_connection
    conn = get_connection()
    try:
        for cod, nume in (("R-FRANE", "Reparatie frane"), ("OE-2", "Verificare")):
            conn.execute(
                "INSERT OR REPLACE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
                (cod, nume),
            )
        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 _row_status(iid: int, row_index: int) -> str:
    from app.db import get_connection
    from app.web.routes import _preview_one_row
    conn = get_connection()
    try:
        result, row = _preview_one_row(conn, iid, 1, row_index)
        assert row is not None and not isinstance(result, str)
        return row["resolved_status"]
    finally:
        conn.close()


def test_panoul_de_mapare_ofera_optiunea_nedeclarat(client):
    _seed()
    iid = _upload_and_preview(client, rows=_ROWS_ITP)
    r = client.get(f"/_import/{iid}/preview")
    assert r.status_code == 200
    assert '__NEDECLARAT__' in r.text, "Selectul de mapare trebuie sa ofere 'Nu se declara la RAR'"


def test_exclude_din_preview_marcheaza_randul_nedeclarat(client):
    _seed()
    iid = _upload_and_preview(client, rows=_ROWS_ITP)
    assert _row_status(iid, 0) == "needs_mapping"

    csrf = _get_csrf(client)
    r = client.post(f"/_import/{iid}/mapare-operatii", data={
        "cod_op_service": "OP-ITP",
        "cod_prestatie": "__NEDECLARAT__",
        "csrf_token": csrf,
    })
    assert r.status_code == 200, r.text
    assert "Excluse de la declarare: OP-ITP" in r.text
    assert "Nedeclarat" in r.text

    assert _row_status(iid, 0) == "excluded"
    assert _row_status(iid, 1) == "ok"
    # Operatia exclusa nu mai apare in panoul de mapat
    assert "Operatii de mapat" not in r.text or "OP-ITP" not in r.text.split("Operatii de mapat")[1][:2000]

    # Regula persistata
    from app.db import get_connection
    conn = get_connection()
    try:
        rule = conn.execute(
            "SELECT cod_prestatie, exclus FROM operations_mapping WHERE account_id=1 AND cod_op_service='OP-ITP'"
        ).fetchone()
    finally:
        conn.close()
    assert rule is not None and rule["exclus"] == 1 and rule["cod_prestatie"] == ""


def test_commit_sare_randurile_excluse(client):
    _seed()
    iid = _upload_and_preview(client, rows=_ROWS_ITP)
    csrf = _get_csrf(client)
    r = client.post(f"/_import/{iid}/mapare-operatii", data={
        "cod_op_service": "OP-ITP",
        "cod_prestatie": "__NEDECLARAT__",
        "csrf_token": csrf,
    })
    assert r.status_code == 200

    # Doar randul OP-1 e "gata de trimis" -> n_confirmat=1
    csrf = _get_csrf(client)
    rc = client.post(f"/_import/{iid}/confirma", data={"n_confirmat": "1", "csrf_token": csrf})
    assert rc.status_code == 200, rc.text

    from app.db import get_connection
    conn = get_connection()
    try:
        subs = conn.execute("SELECT payload_json FROM submissions WHERE batch_id=?", (iid,)).fetchall()
    finally:
        conn.close()
    assert len(subs) == 1, "Doar randul declarabil se enqueue-uieste"
    payload = json.loads(subs[0]["payload_json"])
    assert payload["prestatii"][0]["cod_prestatie"] == "R-FRANE"


def test_rand_mixt_exclude_doar_operatia_exclusa(client):
    """Rand cu 2 operatii (una exclusa, una mapata) -> ok; payload fara cea exclusa."""
    _seed()
    rows = [{
        "VIN": "WVWZZZ1KZAW000789",
        "Nr": "B003TST",
        "Data": "2026-06-12",
        "KM": "90000",
        "Operatie": "OP-ITP",
    }]
    iid = _upload_and_preview(client, rows=rows)
    csrf = _get_csrf(client)
    client.post(f"/_import/{iid}/mapare-operatii", data={
        "cod_op_service": "OP-ITP", "cod_prestatie": "__NEDECLARAT__", "csrf_token": csrf,
    })
    assert _row_status(iid, 0) == "excluded"

    # Editorul de rand: adauga explicit un cod suplimentar -> randul redevine declarabil
    r = client.post(f"/_import/{iid}/rand/0/editeaza", data={
        "cod_prestatie": ["", "OE-2"],
        "chip_op_service": ["OP-ITP", ""],
        "chip_denumire": ["", ""],
    })
    assert r.status_code == 200, r.text
    assert _row_status(iid, 0) == "ok"

    csrf = _get_csrf(client)
    rc = client.post(f"/_import/{iid}/confirma", data={"n_confirmat": "1", "csrf_token": csrf})
    assert rc.status_code == 200, rc.text
    from app.db import get_connection
    conn = get_connection()
    try:
        sub = conn.execute("SELECT payload_json FROM submissions WHERE batch_id=?", (iid,)).fetchone()
    finally:
        conn.close()
    payload = json.loads(sub["payload_json"])
    # Round-trip: itemul exclus RAMANE in payload_json (persistat), adnotat exclus,
    # dar fara cod_prestatie — nu pleaca niciodata la RAR (vezi build_rar_payload).
    by_op = {p.get("cod_op_service"): p for p in payload["prestatii"] if p.get("cod_op_service")}
    assert by_op["OP-ITP"]["exclus"] is True
    assert not by_op["OP-ITP"].get("cod_prestatie")
    coduri_declarabile = [p.get("cod_prestatie") for p in payload["prestatii"] if not p.get("exclus")]
    assert coduri_declarabile == ["OE-2"], f"Operatia exclusa nu trebuie sa plece la RAR: {coduri_declarabile}"


def test_reupload_rand_cu_exclus_deja_comis_e_already_sent(client):
    """Cheia de idempotenta la commit (doar declarabile) e IDENTICA cu preview.

    Re-incarcarea aceluiasi rand (dupa ce a fost deja comis cu un item exclus
    adnotat in payload) trebuie recunoscuta drept duplicat — nu un rand nou.
    """
    _seed()
    rows = [{
        "VIN": "WVWZZZ1KZAW000789",
        "Nr": "B003TST",
        "Data": "2026-06-12",
        "KM": "90000",
        "Operatie": "OP-ITP",
    }]
    iid = _upload_and_preview(client, rows=rows)
    csrf = _get_csrf(client)
    client.post(f"/_import/{iid}/mapare-operatii", data={
        "cod_op_service": "OP-ITP", "cod_prestatie": "__NEDECLARAT__", "csrf_token": csrf,
    })
    client.post(f"/_import/{iid}/rand/0/editeaza", data={
        "cod_prestatie": ["", "OE-2"],
        "chip_op_service": ["OP-ITP", ""],
        "chip_denumire": ["", ""],
    })
    csrf = _get_csrf(client)
    rc = client.post(f"/_import/{iid}/confirma", data={"n_confirmat": "1", "csrf_token": csrf})
    assert rc.status_code == 200, rc.text

    # Re-incarca acelasi rand + acelasi cod ales pe editorul de rand. Maparea de
    # coloane e deja memorata din primul upload -> raspunsul sare direct la preview
    # (fara link mapare-coloane); extragem import_id din hx-get pe #import-section.
    csv_data = _csv_bytes(rows)
    csrf = _get_csrf(client)
    r2 = client.post(
        "/_import/upload",
        files={"file": ("test.csv", io.BytesIO(csv_data), "text/csv")},
        data={"csrf_token": csrf},
    )
    assert r2.status_code == 200, r2.text
    m2 = re.search(r"/_import/(\d+)/preview", r2.text)
    assert m2, f"import_id negasit in raspunsul de reupload: {r2.text[:300]}"
    iid2 = int(m2.group(1))
    csrf = _get_csrf(client)
    client.post(f"/_import/{iid2}/mapare-operatii", data={
        "cod_op_service": "OP-ITP", "cod_prestatie": "__NEDECLARAT__", "csrf_token": csrf,
    })
    client.post(f"/_import/{iid2}/rand/0/editeaza", data={
        "cod_prestatie": ["", "OE-2"],
        "chip_op_service": ["OP-ITP", ""],
        "chip_denumire": ["", ""],
    })
    # already_sent se calculeaza doar la refresh-ul complet de preview (lookup pe
    # tot batch-ul), nu la editarea unui singur rand — reincarcam preview-ul.
    rp = client.get(f"/_import/{iid2}/preview")
    assert rp.status_code == 200
    assert "Deja trimis" in rp.text, \
        "Randul re-incarcat trebuie recunoscut deja trimis (cheia de idempotenta neschimbata)"


def test_cod_explicit_pe_operatie_bate_regula_de_excludere(client):
    """Chips cu cod explicit PE operatia exclusa -> codul ales de user castiga."""
    _seed()
    rows = [_ROWS_ITP[0]]
    iid = _upload_and_preview(client, rows=rows)
    csrf = _get_csrf(client)
    client.post(f"/_import/{iid}/mapare-operatii", data={
        "cod_op_service": "OP-ITP", "cod_prestatie": "__NEDECLARAT__", "csrf_token": csrf,
    })
    assert _row_status(iid, 0) == "excluded"

    r = client.post(f"/_import/{iid}/rand/0/editeaza", data={
        "cod_prestatie": "OE-2",
        "chip_op_service": "OP-ITP",
        "chip_denumire": "Inspectie",
    })
    assert r.status_code == 200, r.text
    assert _row_status(iid, 0) == "ok"


def test_mapari_tab_exclude_si_rerezolva_submissions_api(client):
    """Excluderea din tab-ul Mapari trece submission-urile blocate (canal API) pe needs_data."""
    _seed()
    # Submission blocat pe OP-XYZ (canal API, batch_id NULL)
    r = client.post("/v1/prezentari", json={
        "rar_credentials": {"email": "x@y.ro", "password": "s"},
        "prezentari": [{
            "vin": "WVWZZZ1KZAW000555",
            "nr_inmatriculare": "B055TST",
            "data_prestatie": "2026-06-20",
            "odometru_final": "50000",
            "prestatii": [{"cod_op_service": "OP-XYZ", "denumire": "Operatie interna"}],
        }],
    })
    assert r.status_code == 200, r.text
    sid = r.json()["results"][0]["submission_id"]
    assert r.json()["results"][0]["status"] == "needs_mapping"

    # Operatia apare la mapat
    frag = client.get("/_fragments/mapari")
    assert "OP-XYZ" in frag.text

    csrf = _get_csrf(client)
    resp = client.post("/mapari", data={
        "cod_op_service": "OP-XYZ",
        "cod_prestatie": "__NEDECLARAT__",
        "csrf_token": csrf,
    })
    assert resp.status_code == 200, resp.text
    assert "exclus de la declarare" in resp.text

    from app.db import get_connection
    conn = get_connection()
    try:
        row = conn.execute("SELECT status, rar_error FROM submissions WHERE id=?", (sid,)).fetchone()
        # Operatia exclusa nu mai apare in pending
        from app.mapping import pending_unmapped
        pend = pending_unmapped(conn, 1)
    finally:
        conn.close()
    assert row["status"] == "needs_data"
    assert "excluse de la declarare" in (row["rar_error"] or "")
    assert all(e["cod_op_service"] != "OP-XYZ" for e in pend)


def test_maparea_unei_operatii_excluse_reactiveaza_declararea(client):
    """save_mapping peste o regula de excludere reseteaza exclus=0."""
    _seed()
    from app.db import get_connection
    from app.mapping import save_exclusion, load_excluded_ops, load_mapping
    conn = get_connection()
    try:
        save_exclusion(conn, 1, "OP-ITP")
        assert "OP-ITP" in load_excluded_ops(conn, 1)
        from app.mapping import save_mapping
        save_mapping(conn, 1, "OP-ITP", "OE-2", auto_send=False)
        assert "OP-ITP" not in load_excluded_ops(conn, 1)
        assert load_mapping(conn, 1)["OP-ITP"] == "OE-2"
    finally:
        conn.close()


def test_ingestie_api_cu_operatie_exclusa_nu_blocheaza(client):
    """POST /v1/prezentari cu operatie deja exclusa -> needs_data cu motiv, nu needs_mapping."""
    _seed()
    from app.db import get_connection
    from app.mapping import save_exclusion
    conn = get_connection()
    try:
        save_exclusion(conn, 1, "OP-ITP")
        conn.commit()
    finally:
        conn.close()

    r = client.post("/v1/prezentari", json={
        "rar_credentials": {"email": "x@y.ro", "password": "s"},
        "prezentari": [{
            "vin": "WVWZZZ1KZAW000777",
            "nr_inmatriculare": "B077TST",
            "data_prestatie": "2026-06-21",
            "odometru_final": "60000",
            "prestatii": [{"cod_op_service": "OP-ITP", "denumire": "ITP"}],
        }],
    })
    assert r.status_code == 200, r.text
    rez = r.json()["results"][0]
    assert rez["status"] == "needs_data", rez
