"""Traducere stari tehnice in text uman + clasa CSS.

Functii pure: fara DB, fara request. Usor de testat unitar si de importat in template-uri.
"""

import json
from datetime import datetime
from typing import Tuple

# ---------------------------------------------------------------------------
# Tipul returnat: (text_principal, subtext_tooltip, css_class)
# ---------------------------------------------------------------------------

Eticheta = Tuple[str, str, str]

# ---------------------------------------------------------------------------
# Etichete stari submissions
# Clasele CSS corespund celor definite in base.html:
#   s-queued (accent/albastru), s-sending (warn/galben), s-sent (ok/verde),
#   s-error, s-needs_data, s-needs_mapping (err/rosu).
# ---------------------------------------------------------------------------

STARI_SUBMISSION: dict[str, Eticheta] = {
    "queued": (
        "In asteptare sa fie trimise",
        "",
        "s-queued",
    ),
    "sending": (
        "Se trimite acum",
        "",
        "s-sending",
    ),
    "sent": (
        "Declarate la RAR (finalizate)",
        "Confirmate cu numar de prezentare; nu se mai pot modifica.",
        "s-sent",
    ),
    "needs_mapping": (
        "Lipseste codul prestatiei",
        "Alege codul RAR in tab-ul Mapari.",
        "s-needs_mapping",
    ),
    "needs_data": (
        "Date incomplete (respinse de RAR)",
        "Corecteaza randul si reimporta.",
        "s-needs_data",
    ),
    "error": (
        "Eroare la trimitere",
        "Trimiterea a esuat si nu se mai reincearca automat. Vezi detaliul randului; "
        "daca tine de credentialele RAR, corecteaza-le in Cont.",
        "s-error",
    ),
}


# ---------------------------------------------------------------------------
# Etichete scurte (pill) pentru coloana Stare din tabelul de trimiteri
# Dict propriu — NU element in tuple Eticheta (ar rupe template-urile care
# despacheteaza 3 elemente). eticheta_stare ramane neatinsa.
# ---------------------------------------------------------------------------

ETICHETE_SCURTE: dict[str, str] = {
    "queued":        "In coada",
    "sending":       "Se trimite",
    "sent":          "Finalizat",
    "needs_mapping": "De mapat",
    "needs_data":    "Date lipsa",
    "error":         "Eroare",
}


# ---------------------------------------------------------------------------
# Randuri tinute manual (held) — US-006, PRD 5.19
#
# `held` NU e o stare noua in masina de stari: un rand cu `status='queued' AND
# held=1` este in coada, dar tinut pana la eliberarea umana. Il afisam distinct
# ("In asteptare (manual)") cu culoare de AVERTIZARE (--warn / amber) — asteptare
# benigna, NU eroare. Reutilizam clasa `s-needs_review` (definita in base.html ca
# `color:var(--warn)`); NU folosim clasele needs_* rosii (s-needs_data /
# s-needs_mapping = --err). CHECK-ul din schema.sql ramane neatins.
# ---------------------------------------------------------------------------

ETICHETA_HELD: Eticheta = (
    "In asteptare (manual)",
    "Tinuta pentru confirmarea ta; nu pleaca spre RAR pana nu o eliberezi.",
    "s-needs_review",
)

ETICHETA_HELD_SCURTA = "Manual"


def eticheta_scurta(status: str, held: bool = False) -> str:
    """
    Returneaza eticheta compacta (pill) pentru o stare de submission.

    Cand `status='queued'` si `held` este adevarat, randul e tinut manual si
    intoarce "Manual" (distinct de "In coada") — vezi PRD 5.19 D5. Pentru orice
    alta stare `held` este ignorat, iar apelul fara `held` ramane identic cu cel
    dinainte de US-006.

    Arunca KeyError daca starea nu este mapata — intentionat, ca sa prinda
    stari noi adaugate in schema fara mapare corespunzatoare.
    """
    if held and status == "queued":
        return ETICHETA_HELD_SCURTA
    try:
        return ETICHETE_SCURTE[status]
    except KeyError:
        raise KeyError(
            f"Starea de submission {status!r} nu are eticheta scurta in labels.py. "
            "Adauga-o in ETICHETE_SCURTE."
        )


def eticheta_stare(status: str, held: bool = False) -> Eticheta:
    """
    Returneaza (text, subtext, css_class) pentru o stare de submission.

    Cand `status='queued'` si `held` este adevarat, randul e tinut manual si
    intoarce eticheta "In asteptare (manual)" cu clasa de avertizare (amber /
    --warn) — vezi PRD 5.19 US-006/D5. Pentru orice alta stare `held` este
    ignorat, iar apelul fara `held` ramane identic cu cel dinainte de US-006.

    Arunca KeyError daca starea nu este mapata — intentionat, ca sa prinda
    stari noi adaugate in schema fara mapare corespunzatoare.
    """
    if held and status == "queued":
        return ETICHETA_HELD
    try:
        return STARI_SUBMISSION[status]
    except KeyError:
        raise KeyError(
            f"Starea de submission {status!r} nu are eticheta umana in labels.py. "
            "Adauga-o in STARI_SUBMISSION."
        )


# ---------------------------------------------------------------------------
# Etichete worker (viu / mort)
# ---------------------------------------------------------------------------

def eticheta_worker(viu: bool) -> Eticheta:
    """
    Returneaza (text, subtext, css_class) pentru starea worker-ului.

    viu=True  => "Trimitere automata: activa"  (clasa s-sent / verde)
    viu=False => "Trimitere automata: oprita"  (clasa s-error / rosu)
    """
    if viu:
        return (
            "Trimitere automata: activa",
            "Sistemul verifica coada si trimite la RAR la fiecare cateva secunde.",
            "s-sent",
        )
    return (
        "Trimitere automata: oprita",
        "Nimic nu pleaca spre RAR pana reporneste. Anunta administratorul.",
        "s-error",
    )


# ---------------------------------------------------------------------------
# Etichete conexiune RAR (ok / indisponibil)
# ---------------------------------------------------------------------------

def eticheta_rar(stare: str) -> Eticheta:
    """
    Returneaza (text, subtext, css_class) pentru starea conexiunii cu RAR.

    stare="ok"           => "Legatura cu RAR: functionala"  (s-sent / verde)
    stare="indisponibil" => "Legatura cu RAR: indisponibila" (s-error / rosu)
    """
    if stare == "ok":
        return (
            "Legatura cu RAR: functionala",
            "Portalul AUTOPASS raspunde.",
            "s-sent",
        )
    return (
        "Legatura cu RAR: indisponibila",
        "Portalul RAR nu raspunde acum; coada se reia automat cand revine.",
        "s-error",
    )


# ---------------------------------------------------------------------------
# Format data RAR
# ---------------------------------------------------------------------------

def format_data_rar(raw: object) -> str:
    """Formateaza un timestamp ISO ca `dd.mm.yyyy hh24:mi:ss` (ora romaneasca).

    - Valoare lipsa (None / "") -> "—".
    - ISO valid (cu sau fara timezone / 'Z' / microsecunde) -> data formatata,
      fara fractiuni de secunda.
    - Format invalid -> fallback grijuliu: intoarce stringul brut (nu arunca),
      ca operatorul sa vada totusi ceva, nu o pagina rupta.
    """
    if raw is None:
        return "—"
    s = str(raw).strip()
    if not s:
        return "—"
    iso = s.replace("Z", "+00:00") if s.endswith("Z") else s
    try:
        dt = datetime.fromisoformat(iso)
    except (ValueError, TypeError):
        return s
    return dt.strftime("%d.%m.%Y %H:%M:%S")


# ---------------------------------------------------------------------------
# Motiv uman din rar_error
# ---------------------------------------------------------------------------

def motiv_uman(status: str, rar_error: object) -> str:
    """Transforma `rar_error` (JSON tehnic) intr-un motiv lizibil pentru coloana Motiv.

    Formele intalnite (vezi router.py / mapping.py):
      - validare continut: list[{field, message}] -> mesajele concatenate.
      - operatie nemapata: {"unmapped": [{cod_op_service, denumire}]}.
      - auto-send oprit:   {"auto_send": "..."}.
      - eroare RAR:        text simplu sau dict generic.
    Fara rar_error -> "". Nu arunca niciodata (degradeaza la text brut trunchiat).
    """
    if not rar_error:
        return ""
    raw = rar_error if isinstance(rar_error, str) else str(rar_error)
    try:
        data = json.loads(raw)
    except (ValueError, TypeError):
        return raw[:160]

    if isinstance(data, dict):
        if "unmapped" in data:
            ops = data.get("unmapped") or []
            nume = ", ".join(
                (o.get("cod_op_service") or "") for o in ops if isinstance(o, dict)
            ).strip(", ")
            return f"Cod RAR lipsa pentru: {nume}" if nume else "Cod RAR lipsa"
        if "auto_send" in data:
            return "Necesita confirmare manuala (auto-send oprit pentru cod)"
        if "problema" in data:
            return str(data.get("problema") or "")[:200]
        parti = [f"{k}: {v}" for k, v in data.items()]
        return "; ".join(parti)[:200]

    if isinstance(data, list):
        msgs: list[str] = []
        for e in data:
            if isinstance(e, dict):
                msgs.append(
                    str(e.get("message") or e.get("msg") or "; ".join(str(v) for v in e.values()))
                )
            else:
                msgs.append(str(e))
        return "; ".join(m for m in msgs if m)[:200]

    return str(data)[:160]


# ---------------------------------------------------------------------------
# parse_erori — transforma rar_error in lista 3-niveluri
# ---------------------------------------------------------------------------

def parse_erori(rar_error: object) -> list[dict]:
    """Transforma `rar_error` (JSON stocat) intr-o lista de erori 3-niveluri.

    Fiecare element al listei are cheile: problema, cauza, fix, field (sau None).
    Functie PURA — nu arunca niciodata exceptii; degradeaza gratios pe orice forma.

    Forme recunoscute:
      - None / "" / falsy         -> lista goala []
      - array imbogatit (au cod sau problema) -> un element per eroare
      - dict cu cod specific      -> 1 element cu cele 3 niveluri din dict
      - dict fara cod (forma veche: unmapped / auto_send) -> 1 element cu problema din context
      - lista cu {field, message} fara cod -> degradare: problema=message, cauza/fix=""
      - string plain              -> 1 element cu problema=text, cauza/fix=""
      - JSON corupt               -> 1 element cu problema=text brut, cauza/fix=""
    """
    if not rar_error:
        return []

    raw = rar_error if isinstance(rar_error, str) else str(rar_error)

    # Incercare parsare JSON
    try:
        data = json.loads(raw)
    except (ValueError, TypeError):
        # String plain sau JSON corupt: degradare gratuoasa
        return [{"problema": raw[:200], "cauza": "", "fix": "", "field": None}]

    # --- Forma: array de erori ---
    if isinstance(data, list):
        rezultat = []
        for e in data:
            if not isinstance(e, dict):
                rezultat.append({"problema": str(e)[:200], "cauza": "", "fix": "", "field": None})
                continue
            # Eroare imbogatita (are cod sau problema)
            if e.get("cod") or e.get("problema"):
                rezultat.append({
                    "problema": e.get("problema") or e.get("cod") or "",
                    "cauza": e.get("cauza") or e.get("message") or "",
                    "fix": e.get("fix") or "",
                    "field": e.get("field"),
                    # Cod BRUT de catalog (ex. RAR_EROARE_SERVER) — DOAR pentru modal.
                    "cod": e.get("cod"),
                })
            else:
                # Forma veche: {field, message} fara cod
                msg = str(e.get("message") or e.get("msg") or "; ".join(str(v) for v in e.values()))
                elem = {
                    "problema": msg[:200],
                    "cauza": "",
                    "fix": "",
                    "field": e.get("field"),
                }
                # Filtreaza elementele complet goale (problema/cauza/fix toate vide)
                if not (
                    elem["problema"].strip() == ""
                    and elem["cauza"].strip() == ""
                    and elem["fix"].strip() == ""
                ):
                    rezultat.append(elem)
        return rezultat

    # --- Forma: dict ---
    if isinstance(data, dict):
        # Dict imbogatit cu cod explicit
        if data.get("cod") or data.get("problema"):
            return [{
                "problema": data.get("problema") or data.get("cod") or "",
                "cauza": data.get("cauza") or "",
                "fix": data.get("fix") or "",
                "field": data.get("field"),
                # Cod BRUT de catalog (ex. COD_NEMAPAT) — DOAR pentru modal.
                "cod": data.get("cod"),
            }]
        # Dict vechi: unmapped
        if "unmapped" in data:
            ops = data.get("unmapped") or []
            coduri = ", ".join(
                (o.get("cod_op_service") or "") for o in ops if isinstance(o, dict)
            ).strip(", ")
            problema = f"Cod RAR lipsa pentru: {coduri}" if coduri else "Cod RAR lipsa"
            return [{"problema": problema, "cauza": "", "fix": "", "field": None}]
        # Dict vechi: auto_send
        if "auto_send" in data:
            return [{"problema": "Necesita confirmare manuala (auto-send oprit pentru cod)",
                     "cauza": "", "fix": "", "field": None}]
        # Dict generic necunoscut
        parti = "; ".join(f"{k}: {v}" for k, v in data.items())
        if not parti.strip():
            return []
        return [{"problema": parti[:200], "cauza": "", "fix": "", "field": None}]

    # Scalar (nr, bool, etc.)
    return [{"problema": str(data)[:200], "cauza": "", "fix": "", "field": None}]


# ---------------------------------------------------------------------------
# Etichete stari preview import (vocabular DIFERIT de starile de submission)
#
# Starile de preview (ok/needs_review/already_sent/duplicate_in_file) NU
# exista in STARI_SUBMISSION — reutilizarea directa a eticheta_stare/eticheta_scurta
# ridica KeyError. Acest map este sursa de adevar pentru stratul de adaptare din
# _web_compute_preview (routes.py) si pentru template (_preview_rand.html).
# ---------------------------------------------------------------------------

STARI_PREVIEW: dict[str, tuple[str, str]] = {
    "ok":                ("Gata de trimis",     "s-ok"),
    "needs_review":      ("Verifica valori",    "s-needs_review"),
    "needs_mapping":     ("Cod RAR lipsa",      "s-needs_mapping"),
    "needs_data":        ("Date incomplete",    "s-needs_data"),
    "already_sent":      ("Deja trimis",        "s-already_sent"),
    "duplicate_in_file": ("Duplicat in fisier", "s-duplicate_in_file"),
}


def nota_umana_preview(status: str, errors: list, flags: list) -> str:
    """Formateaza mesajul uman pentru coloana Note din tabelul de preview import.

    Primeste ``errors`` ca lista Python (nu JSON string) — NU pasa la motiv_uman
    sau parse_erori care asteapta un JSON string si ar produce repr Python brut
    prin fallback ``raw[:160]`` (bug documentat in PRD 5.11 US-003).

    Logica de prioritate:
      - already_sent / duplicate_in_file -> "" (template le afiseaza separat)
      - needs_mapping -> unmapped INAINTE de flags (codul lipsa e motivul real)
      - flags non-goale -> primul flag (needs_review: data ambigua etc.)
      - errors cu cheie "unmapped" -> "Cod RAR lipsa pentru: COD1, COD2"
      - errors cu field+message (needs_data) -> primul mesaj de validare
      - altceva -> ""

    Fara exceptii. Trunchiat la 200 caractere.
    """
    if status in ("already_sent", "duplicate_in_file"):
        return ""
    # needs_mapping: codul RAR lipseste — prioritizeaza 'unmapped' inaintea flags,
    # altfel un rand cu si un flag (ex. VIN numeric) ar afisa textul flag-ului
    # si ascunde motivul real (cod lipsa).
    if status == "needs_mapping":
        for e in errors:
            if not isinstance(e, dict):
                continue
            if "unmapped" in e:
                ops = e.get("unmapped") or []
                coduri = ", ".join(
                    o.get("cod_op_service", "") for o in ops if isinstance(o, dict)
                )
                return ("Cod RAR lipsa pentru: " + coduri if coduri else "Cod RAR lipsa")
    if flags:
        return str(flags[0])[:200]
    for e in errors:
        if not isinstance(e, dict):
            continue
        if "unmapped" in e:
            ops = e.get("unmapped") or []
            coduri = ", ".join(
                o.get("cod_op_service", "") for o in ops if isinstance(o, dict)
            )
            return (f"Cod RAR lipsa pentru: {coduri}" if coduri else "Cod RAR lipsa")
        msg = (
            e.get("message")
            or e.get("msg")
            or e.get("problema")
            or e.get("cauza")
            or ""
        )
        if msg:
            return str(msg)[:200]
    return ""


# ---------------------------------------------------------------------------
# Etichete mediu RAR (Test / Productie) — US-010 PRD 5.20
#
# Conventia de culori (semantica risc L.142):
#   prod  = env-badge-prod  (fill atentie, text alb) — declaratie REALA, ireversibila.
#   test  = env-badge-test  (outline discret, --muted) — mediu de proba.
# ---------------------------------------------------------------------------

ETICHETE_ENV: dict[str, tuple[str, str]] = {
    "prod": ("PRODUCȚIE", "env-badge-prod"),
    "test": ("Testare",   "env-badge-test"),
}


def eticheta_env(env: object) -> tuple[str, str]:
    """Returneaza (text, css_class) pentru mediul RAR.

    Fallback sigur: env necunoscut sau None -> ('Testare', 'env-badge-test').
    Nu arunca niciodata.
    """
    if not env:
        return ("Testare", "env-badge-test")
    return ETICHETE_ENV.get(str(env), ("Testare", "env-badge-test"))


# ---------------------------------------------------------------------------
# Constante auxiliare (microcopy fix, fara logica)
# ---------------------------------------------------------------------------

ETICHETA_ULTIMA_AUTENTIFICARE_RAR = "Ultima autentificare la RAR"
