"""
ENGCUT — Validador Automático de Entradas de Estoque (SQLite)
=============================================================
Detecta registros incorretos imediatamente após cada operação de escrita
no banco SQLite (STOCK + HISTORY). Chamado por:
  - save_entry()            em front_end/engcut_ui.py
  - _import_inventory_xlsx() em front_end/engcut_ui.py
  - check_sqlite_entries()  em stock_integrity.py (batch)

Retorna lista de StockViolation — lista vazia = banco íntegro.

Códigos de severidade:
  CRITICAL — dado corrompido ou ausente; operação deve ser revertida
  ERROR    — inconsistência matemática ou semântica confirmada
  WARNING  — suspeita que requer atenção; não bloqueia
"""
from __future__ import annotations
import sqlite3
from dataclasses import dataclass
from pathlib import Path


@dataclass
class StockViolation:
    severity:  str   # CRITICAL | ERROR | WARNING
    code:      str   # SV-NNN
    item_id:   str
    field:     str
    message:   str


# ── Constantes de range plausível ────────────────────────────────────────────

# IMPERIAL: length_internal em inches
_IMP_MIN_IN = 0.5      # ≈ 1.27 cm — menor retalho razoável
_IMP_MAX_IN = 5000.0   # ≈ 127 m  — maior barra comercial razoável

# METRIC: length_internal em mm
_MET_MIN_MM = 10.0       # 1 cm
_MET_MAX_MM = 100_000.0  # 100 m


def _get_db(db_path):
    from stock_db_schema import get_db_path
    return db_path or get_db_path()


def validate_stock_entries(
    stock_ids: list[str],
    unit_system: str = "IMPERIAL",
    db_path: Path | None = None,
) -> list[StockViolation]:
    """
    Valida uma lista de IDs de STOCK logo após a escrita.
    Retorna lista de StockViolation. Lista vazia = OK.
    """
    if not stock_ids:
        return []

    db = _get_db(db_path)
    if not Path(db).exists():
        return [StockViolation(
            "CRITICAL", "SV-000", "", "db",
            f"[SV-000] Banco SQLite não encontrado: {db}"
        )]

    is_imp = "IMPERIAL" in str(unit_system).upper()
    violations: list[StockViolation] = []

    con = sqlite3.connect(str(db))
    con.row_factory = sqlite3.Row
    try:
        for sid in stock_ids:
            violations.extend(_validate_one(con, sid, is_imp))
    finally:
        con.close()

    return violations


def _validate_one(con, sid: str, is_imperial: bool) -> list[StockViolation]:
    v: list[StockViolation] = []

    # ── 1. STOCK entry deve existir ──────────────────────────────────────────
    sr = con.execute("SELECT * FROM stock WHERE id=?", (sid,)).fetchone()
    if not sr:
        v.append(StockViolation(
            "CRITICAL", "SV-001", sid, "stock.id",
            f"[SV-001] {sid}: STOCK não encontrado após escrita (INSERT falhou?)"
        ))
        return v

    len_int = float(sr["length_internal"] or 0)
    val_s   = sr["value"]
    wtot_s  = sr["weight_total"]
    wlin_s  = sr["weight_linear"] or 0

    # ── 2. length_internal > 0 ───────────────────────────────────────────────
    if len_int <= 0:
        v.append(StockViolation(
            "CRITICAL", "SV-002", sid, "length_internal",
            f"[SV-002] {sid}: length_internal={len_int} ≤ 0 — comprimento inválido"
        ))

    # ── 3. length_internal dentro do range plausível para o sistema ──────────
    if len_int > 0:
        if is_imperial:
            if len_int < _IMP_MIN_IN or len_int > _IMP_MAX_IN:
                hint = ""
                if len_int > 1000:
                    # Provavelmente armazenado em mm por engano
                    hint = f" | possível erro: {len_int:.0f} mm → {len_int/25.4:.1f} in = {len_int/25.4/12:.1f} ft?"
                elif len_int < 0.5:
                    hint = f" | possível conversão incorreta"
                v.append(StockViolation(
                    "ERROR", "SV-003", sid, "length_internal",
                    f"[SV-003] {sid}: length_internal={len_int:.3f} in fora do range "
                    f"IMPERIAL [{_IMP_MIN_IN}–{_IMP_MAX_IN} in]{hint}"
                ))
        else:
            if len_int < _MET_MIN_MM or len_int > _MET_MAX_MM:
                hint = ""
                if len_int < 10 and len_int > 0:
                    hint = f" | possível erro: {len_int} m → {len_int*1000:.0f} mm?"
                elif len_int > _MET_MAX_MM:
                    hint = f" | possível erro de unidade"
                v.append(StockViolation(
                    "ERROR", "SV-003", sid, "length_internal",
                    f"[SV-003] {sid}: length_internal={len_int:.3f} mm fora do range "
                    f"METRIC [{_MET_MIN_MM}–{_MET_MAX_MM} mm]{hint}"
                ))

    # ── 4. weight_total consistente com weight_linear × length ───────────────
    if wlin_s > 0 and len_int > 0:
        div = 12.0 if is_imperial else 1000.0
        expected_wtot = round(wlin_s * len_int / div, 4)
        if wtot_s is None:
            v.append(StockViolation(
                "WARNING", "SV-004", sid, "weight_total",
                f"[SV-004] {sid}: weight_total=NULL com weight_linear={wlin_s} "
                f"(esperado ≈ {expected_wtot})"
            ))
        else:
            diff_pct = abs(float(wtot_s) - expected_wtot) / (expected_wtot or 1) * 100
            if diff_pct > 1.0:
                v.append(StockViolation(
                    "ERROR", "SV-004", sid, "weight_total",
                    f"[SV-004] {sid}: weight_total={wtot_s} ≠ esperado {expected_wtot} "
                    f"({diff_pct:.2f}% divergência)"
                ))

    # ── 5. value não-nulo se há regra financeira ──────────────────────────────
    if val_s is None:
        v.append(StockViolation(
            "WARNING", "SV-005", sid, "value",
            f"[SV-005] {sid}: value=NULL — verificar regras de preço em CRITERIOS"
        ))

    # ── 6. AVAILABLE: dest_project deve ser NULL ──────────────────────────────
    if sr["status"] == "AVAILABLE" and sr["dest_project"] is not None:
        v.append(StockViolation(
            "WARNING", "SV-006", sid, "dest_project",
            f"[SV-006] {sid}: status=AVAILABLE mas dest_project='{sr['dest_project']}'"
        ))

    # ── 7. HISTORY: entrada deve existir ─────────────────────────────────────
    hist = con.execute(
        "SELECT * FROM history WHERE item_id=? ORDER BY rowid", (sid,)
    ).fetchall()

    if not hist:
        v.append(StockViolation(
            "ERROR", "SV-007", sid, "history",
            f"[SV-007] {sid}: HISTORY sem entrada — rastreabilidade de auditoria comprometida"
        ))
        return v

    # ── 8. ENTRADA_MANUAL: length_piece deve ser 0 ────────────────────────────
    for hr in hist:
        if hr["event"] != "ENTRADA_MANUAL":
            continue
        lp = hr["length_piece"]
        lr = hr["length_remnant"]

        # piece_length deve ser 0: a peça inteira VAI para estoque, não foi cortada
        if lp is not None and abs(float(lp)) > 0.001:
            v.append(StockViolation(
                "ERROR", "SV-008", sid, "history.length_piece",
                f"[SV-008] {sid}: HISTORY ENTRADA_MANUAL length_piece={lp} "
                f"(deve ser 0 — a barra inteira é adicionada ao estoque, nenhuma peça foi cortada)"
            ))

        # length_remnant deve ≈ length_internal (a barra adicionada é o retalho)
        if len_int > 0 and lr is not None:
            diff = abs(float(lr) - len_int)
            if diff > len_int * 0.01:  # tolerância 1%
                v.append(StockViolation(
                    "WARNING", "SV-009", sid, "history.length_remnant",
                    f"[SV-009] {sid}: HISTORY length_remnant={lr} ≠ STOCK length_internal={len_int} "
                    f"(divergência={diff:.4f})"
                ))

    # ── 9. Consistência valor STOCK ↔ HISTORY ────────────────────────────────
    if val_s is not None:
        for hr in hist:
            hv = hr["value"]
            if hv is not None and abs(float(hv) - float(val_s)) > 0.10:
                v.append(StockViolation(
                    "WARNING", "SV-010", sid, "value",
                    f"[SV-010] {sid}: STOCK.value={val_s} ≠ HISTORY.value={hv} "
                    f"(divergência=${abs(float(hv)-float(val_s)):.4f})"
                ))
                break

    return v


def batch_validate_all_available(
    unit_system: str = "IMPERIAL",
    db_path: Path | None = None,
) -> list[StockViolation]:
    """
    Valida todos os itens AVAILABLE no banco.
    Chamado por stock_integrity.py para checagem em lote (Step -1).
    """
    db = _get_db(db_path)
    if not Path(db).exists():
        return []

    con = sqlite3.connect(str(db))
    con.row_factory = sqlite3.Row
    try:
        ids = [r["id"] for r in con.execute("SELECT id FROM stock WHERE status='AVAILABLE'")]
    finally:
        con.close()

    return validate_stock_entries(ids, unit_system=unit_system, db_path=db_path)


def format_violations(violations: list[StockViolation]) -> str:
    """Formata violações para log ou diálogo."""
    if not violations:
        return "OK — nenhuma violação detectada."
    lines = []
    for vl in violations:
        lines.append(f"[{vl.severity}] {vl.message}")
    return "\n".join(lines)
