"""Teste US-001 (PRD 3.6): backend — persista editarea unui rand de preview.

Approach B: editarea scrie un patch CANONIC in import_rows.override_json (criptat
Fernet), aplicat ULTIMUL in _resolve_row_for_preview + commit. raw_json/idempotency
raman neatinse. Ruta editare = mutatie PURA (nu re-deriva status, nu atinge submissions).

TDD: testele se scriu inainte de implementare (RED -> GREEN).
"""

from __future__ import annotations

import io
import os
import pathlib
import tempfile

import pytest
from fastapi.testclient import TestClient

_FIXTURES = pathlib.Path(__file__).parent / "fixtures"


@pytest.fixture()
def client(monkeypatch):
    tmp = tempfile.mkdtemp()
    monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "edit.db"))
    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:
    """Nomenclator + mapare OP-1 -> R-FRANE (auto_send=1) pentru contul dat."""
    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 _upload(client: TestClient, fixture: str, headers: dict | None = None) -> int:
    data = (_FIXTURES / fixture).read_bytes()
    r = client.post(
        "/v1/import",
        files={"file": (fixture, io.BytesIO(data), "text/csv")},
        headers=headers or {},
    )
    assert r.status_code == 200, r.text
    return int(r.json()["import_id"])


def _save_mapping(client: TestClient, import_id: int, json_mapare: dict,
                  headers: dict | None = None) -> None:
    r = client.post(
        f"/v1/import/{import_id}/column-mapping",
        json={"json_mapare": json_mapare, "format_data": "YYYY-MM-DD"},
        headers=headers or {},
    )
    assert r.status_code == 200, r.text


def _preview(client: TestClient, import_id: int, headers: dict | None = None) -> list[dict]:
    r = client.get(f"/v1/import/{import_id}/preview", headers=headers or {})
    assert r.status_code == 200, r.text
    return r.json()["rows"]


def _status_for(rows: list[dict], row_index: int) -> str:
    return next(r["resolved_status"] for r in rows if r["row_index"] == row_index)


def _resolved_for(rows: list[dict], row_index: int) -> dict:
    return next(r["resolved"] for r in rows if r["row_index"] == row_index)


_MAP_NECANONIC = {
    "Serie sasiu": "vin",
    "Nr": "nr_inmatriculare",
    "Data": "data_prestatie",
    "KM": "odometru_final",
    "Operatie": "operatie",
}
_MAP_LIPSA = {
    "Serie sasiu": "vin",
    "Nr": "nr_inmatriculare",
    "KM": "odometru_final",
    "Operatie": "operatie",
}


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

def test_editeaza_rand_antet_necanonic_devine_ok(client):
    """Fisier cu antet ne-canonic (Serie sasiu/Data): rand needs_data -> editez data -> ok.

    Prinde bug-ul de stocare: o editare pe cheia canonica trebuie sa se aplice chiar
    daca raw_json e cheiat pe anteturi ne-canonice.
    """
    _seed_op1()
    iid = _upload(client, "import_antet_necanonic.csv")
    _save_mapping(client, iid, _MAP_NECANONIC)
    assert _status_for(_preview(client, iid), 0) == "needs_data"

    r = client.post(f"/v1/import/{iid}/rand/0/editeaza", json={"data_prestatie": "2026-06-10"})
    assert r.status_code == 200, r.text

    assert _status_for(_preview(client, iid), 0) == "ok"


def test_editeaza_completeaza_coloana_absenta(client):
    """Fisier FARA coloana de data: editarea adauga data_prestatie -> ok.

    Demonstreaza ca override poate exprima un camp a carui coloana LIPSESTE din fisier.
    """
    _seed_op1()
    iid = _upload(client, "import_lipsa_coloana.csv")
    _save_mapping(client, iid, _MAP_LIPSA)
    assert _status_for(_preview(client, iid), 0) == "needs_data"

    r = client.post(f"/v1/import/{iid}/rand/0/editeaza", json={"data_prestatie": "2026-06-10"})
    assert r.status_code == 200, r.text

    assert _status_for(_preview(client, iid), 0) == "ok"


def test_editeaza_status_identic_cu_GET_preview(client):
    """Ruta editare NU re-deriva status (intoarce doar override); statusul vine din GET preview."""
    _seed_op1()
    iid = _upload(client, "import_antet_necanonic.csv")
    _save_mapping(client, iid, _MAP_NECANONIC)

    r = client.post(f"/v1/import/{iid}/rand/0/editeaza", json={"data_prestatie": "2026-06-10"})
    assert r.status_code == 200
    body = r.json()
    # Mutatie pura: raspunsul nu contine un status rederivat in ruta.
    assert "resolved_status" not in body
    assert body.get("override", {}).get("data_prestatie") == "2026-06-10"
    # Statusul se rederiva DOAR prin preview.
    assert _status_for(_preview(client, iid), 0) == "ok"


def test_editeaza_rand_scoped_alt_cont_404(client):
    """Editarea unui rand al altui cont -> 404 (scoping JOIN, fara leak)."""
    from app.db import get_connection
    from app.accounts import create_account
    from app.auth import create_api_key

    _seed_op1()
    iid = _upload(client, "import_antet_necanonic.csv")  # cont 1 (default)
    _save_mapping(client, iid, _MAP_NECANONIC)

    conn = get_connection()
    try:
        acct2 = create_account(conn, "Alt cont", active=True)
        key2 = create_api_key(conn, acct2)
        conn.commit()
    finally:
        conn.close()

    r = client.post(
        f"/v1/import/{iid}/rand/0/editeaza",
        json={"data_prestatie": "2026-06-10"},
        headers={"X-API-Key": key2},
    )
    assert r.status_code == 404


def test_editeaza_batch_inexistent_404(client):
    r = client.post("/v1/import/99999/rand/0/editeaza", json={"vin": "WVWZZZ1KZAW000123"})
    assert r.status_code == 404


def test_editeaza_row_index_invalid_pe_batch_valid_404(client):
    _seed_op1()
    iid = _upload(client, "import_antet_necanonic.csv")
    _save_mapping(client, iid, _MAP_NECANONIC)
    r = client.post(f"/v1/import/{iid}/rand/9999/editeaza", json={"vin": "WVWZZZ1KZAW000123"})
    assert r.status_code == 404


def test_editeaza_pastreaza_campuri_neatinse(client):
    """Editarea unui camp nu pierde prestatiile/operatia rezolvate ale randului."""
    _seed_op1()
    iid = _upload(client, "import_antet_necanonic.csv")
    _save_mapping(client, iid, _MAP_NECANONIC)

    client.post(f"/v1/import/{iid}/rand/0/editeaza", json={"data_prestatie": "2026-06-10"})
    res = _resolved_for(_preview(client, iid), 0)
    prestatii = res.get("prestatii") or []
    assert prestatii, "prestatiile au disparut dupa editare"
    assert (prestatii[0].get("cod_prestatie") or prestatii[0].get("cod_op_service")) in ("R-FRANE", "OP-1")


def test_editeaza_batch_committed_409(client):
    """Editarea pe un batch deja comis -> 409 (nu mai are efect downstream)."""
    from app.db import get_connection
    _seed_op1()
    iid = _upload(client, "import_antet_necanonic.csv")
    _save_mapping(client, iid, _MAP_NECANONIC)
    conn = get_connection()
    try:
        conn.execute("UPDATE import_batches SET status='committed' WHERE id=?", (iid,))
        conn.commit()
    finally:
        conn.close()
    r = client.post(f"/v1/import/{iid}/rand/0/editeaza", json={"data_prestatie": "2026-06-10"})
    assert r.status_code == 409


def test_editeaza_raw_corupt_no_op(client):
    """Override curent ilizibil (decrypt fail) -> 422/no-op, fara scriere goala, fara crash."""
    from app.db import get_connection
    _seed_op1()
    iid = _upload(client, "import_antet_necanonic.csv")
    _save_mapping(client, iid, _MAP_NECANONIC)

    conn = get_connection()
    try:
        conn.execute(
            "UPDATE import_rows SET override_json=? WHERE batch_id=? AND row_index=0",
            ("token-corupt-nedecriptabil", iid),
        )
        conn.commit()
    finally:
        conn.close()

    r = client.post(f"/v1/import/{iid}/rand/0/editeaza", json={"data_prestatie": "2026-06-10"})
    assert r.status_code == 422

    # override_json NU a fost suprascris cu o valoare goala
    conn = get_connection()
    try:
        oj = conn.execute(
            "SELECT override_json FROM import_rows WHERE batch_id=? AND row_index=0", (iid,)
        ).fetchone()["override_json"]
    finally:
        conn.close()
    assert oj == "token-corupt-nedecriptabil"


def test_editeaza_empty_input_sterge_campul(client):
    """Semantica empty: input gol = STERGE cheia din override (revine la valoarea din fisier)."""
    _seed_op1()
    iid = _upload(client, "import_antet_necanonic.csv")
    _save_mapping(client, iid, _MAP_NECANONIC)

    # Randul 1 are odometru_final 55000 in fisier. Suprascriem cu 77000 prin override.
    client.post(f"/v1/import/{iid}/rand/1/editeaza", json={"odometru_final": "77000"})
    assert _resolved_for(_preview(client, iid), 1).get("odometru_final") == "77000"

    # Input gol -> sterge override-ul -> revine la valoarea din fisier (55000).
    client.post(f"/v1/import/{iid}/rand/1/editeaza", json={"odometru_final": ""})
    assert _resolved_for(_preview(client, iid), 1).get("odometru_final") == "55000"
