"""Teste T5: import DBF -> SQLite (dry-run/raport + commit idempotent).

`mapare_prestatii.DBF` real e gol, deci pentru maparile reale scriem fixturi DBF
dBASE III minimale. Nomenclatorul real (`prestatii_rar.DBF`, 20 randuri) e citit
direct pentru un test de read pe date reale.
"""

from __future__ import annotations

import os
import struct
import tempfile
from pathlib import Path

import pytest

from app.config import ROOT


# --------------------------------------------------------------------------- #
# Helper: scriitor dBASE III minimal pentru fixturi                           #
# --------------------------------------------------------------------------- #

def write_dbf(path: Path, fields: list[tuple[str, str, int]], records: list[dict]) -> None:
    """Scrie un DBF dBASE III. fields = [(nume, tip C/L, lungime)]."""
    header = bytearray(32)
    header[0] = 0x03  # dBASE III, fara memo
    header[1:4] = bytes((25, 1, 1))  # data ultimei actualizari (fictiva)
    n_fields = len(fields)
    header_len = 32 + 32 * n_fields + 1
    record_len = 1 + sum(length for _, _, length in fields)
    struct.pack_into("<I", header, 4, len(records))
    struct.pack_into("<H", header, 8, header_len)
    struct.pack_into("<H", header, 10, record_len)

    field_descs = bytearray()
    for name, ftype, length in fields:
        fd = bytearray(32)
        fd[0:11] = name.encode("ascii").ljust(11, b"\x00")[:11]
        fd[11] = ord(ftype)
        fd[16] = length
        field_descs += fd

    body = bytearray()
    for rec in records:
        body += b"\x20"  # flag stergere = spatiu (activ)
        for name, ftype, length in fields:
            val = rec.get(name)
            if ftype == "L":
                body += (b"T" if val else b"F")
            else:
                s = "" if val is None else str(val)
                body += s.encode("cp1252", "replace").ljust(length, b" ")[:length]

    path.write_bytes(bytes(header) + bytes(field_descs) + b"\x0d" + bytes(body) + b"\x1a")


MAPARE_FIELDS = [("COD_OP", "C", 10), ("DESCR_OP", "C", 40), ("COD_RAR", "C", 10), ("AUTO_SEND", "L", 1)]
PREST_FIELDS = [("COD_PREST", "C", 10), ("NUME_PREST", "C", 60)]


@pytest.fixture()
def env(monkeypatch):
    tmp = tempfile.mkdtemp()
    monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "t.db"))
    from app.config import get_settings

    get_settings.cache_clear()
    yield Path(tmp)
    get_settings.cache_clear()


# --------------------------------------------------------------------------- #
# Read pe DBF                                                                  #
# --------------------------------------------------------------------------- #

def test_read_nomenclator_real_dbf():
    from tools.import_dbf import read_nomenclator

    rep = read_nomenclator(ROOT / "legacy-vfp" / "prestatii_rar.DBF")
    assert len(rep["rows"]) == 20
    codes = {r["cod_prestatie"] for r in rep["rows"]}
    assert "OE-1" in codes and "R-ODO" in codes
    # codurile sunt normalizate upper
    assert all(r["cod_prestatie"] == r["cod_prestatie"].upper() for r in rep["rows"])


def test_read_mapari_valid_blank_duplicate(tmp_path):
    from tools.import_dbf import read_mapari

    p = tmp_path / "m.DBF"
    write_dbf(
        p,
        MAPARE_FIELDS,
        [
            {"COD_OP": "OP1", "DESCR_OP": "Reparatie motor", "COD_RAR": "oe-1", "AUTO_SEND": True},
            {"COD_OP": "OP2", "DESCR_OP": "Schimb ulei", "COD_RAR": "OE-2", "AUTO_SEND": False},
            {"COD_OP": "", "DESCR_OP": "fara cod op", "COD_RAR": "OE-3", "AUTO_SEND": True},  # blank
            {"COD_OP": "OP4", "DESCR_OP": "fara cod rar", "COD_RAR": "", "AUTO_SEND": True},  # blank
            {"COD_OP": "OP1", "DESCR_OP": "duplicat", "COD_RAR": "OE-9", "AUTO_SEND": True},  # duplicate
        ],
    )
    rep = read_mapari(p)
    assert len(rep["rows"]) == 2
    assert rep["blanks"] == 2
    assert rep["duplicates"] == ["OP1"]
    # cod_rar normalizat upper, auto_send pastrat
    assert rep["rows"][0]["cod_prestatie"] == "OE-1"
    assert rep["rows"][0]["auto_send"] is True
    assert rep["rows"][1]["auto_send"] is False


# --------------------------------------------------------------------------- #
# Orfane                                                                       #
# --------------------------------------------------------------------------- #

def test_find_orphans():
    from tools.import_dbf import find_orphans

    mapari = [
        {"cod_op_service": "OP1", "cod_prestatie": "OE-1", "denumire": "x"},
        {"cod_op_service": "OP2", "cod_prestatie": "ZZZ", "denumire": "y"},
    ]
    orphans = find_orphans(mapari, {"OE-1", "OE-2"})
    assert len(orphans) == 1
    assert orphans[0]["cod_op_service"] == "OP2"


# --------------------------------------------------------------------------- #
# End-to-end dry-run + commit + idempotenta                                   #
# --------------------------------------------------------------------------- #

def _fixtures(dirpath: Path) -> tuple[Path, Path]:
    mp = dirpath / "mapare.DBF"
    np_ = dirpath / "prest.DBF"
    write_dbf(
        np_,
        PREST_FIELDS,
        [{"COD_PREST": "OE-1", "NUME_PREST": "REPARATIE"}, {"COD_PREST": "R-ODO", "NUME_PREST": "REPARATIE ODOMETRU"}],
    )
    write_dbf(
        mp,
        MAPARE_FIELDS,
        [
            {"COD_OP": "OP1", "DESCR_OP": "Reparatie", "COD_RAR": "OE-1", "AUTO_SEND": True},
            {"COD_OP": "OP2", "DESCR_OP": "Operatie necunoscuta", "COD_RAR": "XYZ", "AUTO_SEND": False},  # orfan
        ],
    )
    return mp, np_


def test_dry_run_does_not_write(env):
    from tools.import_dbf import run
    from app.db import get_connection

    mp, np_ = _fixtures(env)
    res = run(commit=False, mapare_path=mp, prest_path=np_)
    assert res["written"] == {"nomenclator": 0, "mapari": 0}
    assert len(res["orphans"]) == 1
    assert res["orphans"][0]["cod_op_service"] == "OP2"

    conn = get_connection()
    try:
        n = conn.execute("SELECT COUNT(*) AS n FROM operations_mapping").fetchone()["n"]
        assert n == 0
    finally:
        conn.close()


def test_commit_writes_and_is_idempotent(env):
    from tools.import_dbf import run
    from app.db import get_connection

    mp, np_ = _fixtures(env)
    res1 = run(commit=True, mapare_path=mp, prest_path=np_)
    assert res1["written"] == {"nomenclator": 2, "mapari": 2}

    conn = get_connection()
    try:
        maps = conn.execute(
            "SELECT cod_op_service, cod_prestatie, auto_send FROM operations_mapping ORDER BY cod_op_service"
        ).fetchall()
        assert [(m["cod_op_service"], m["cod_prestatie"], m["auto_send"]) for m in maps] == [
            ("OP1", "OE-1", 1),
            ("OP2", "XYZ", 0),
        ]
        nom = conn.execute("SELECT COUNT(*) AS n FROM nomenclator_rar").fetchone()["n"]
    finally:
        conn.close()

    # A doua rulare nu duplica (upsert pe cheile UNIQUE).
    run(commit=True, mapare_path=mp, prest_path=np_)
    conn = get_connection()
    try:
        assert conn.execute("SELECT COUNT(*) AS n FROM operations_mapping").fetchone()["n"] == 2
        assert conn.execute("SELECT COUNT(*) AS n FROM nomenclator_rar").fetchone()["n"] == nom
    finally:
        conn.close()


def test_missing_dbf_raises(env):
    from tools.import_dbf import run

    with pytest.raises(FileNotFoundError):
        run(commit=False, mapare_path=env / "nope.DBF", prest_path=env / "nope2.DBF")
