"""Cheie de idempotenta = hash de continut canonic.

RAR nu are camp nr. comanda si accepta duplicate -> dedup-ul e in sarcina noastra.
Hash stabil peste o reprezentare canonica a prezentarii.

canonicalize_row + build_key sunt helpere publice partajate intre canalul API si
canalul import:
  - canonicalize_row: normeaza VIN/nr/odometru (strip ".0" Excel coercion) INAINTE
    de validare si INAINTE de cheie.
  - build_key: aplica account_or_default INAINTE de hash (None si 1 => o cheie).
    Altfel acelasi rand logic din canale diferite (account_id None pe canalul API,
    1 pe import) ar primi chei diferite -> al doilea FINALIZATA duplicat.

Randuri vechi cu cheie-None nu sunt gasite de build_key nou: dual-lookup la
already_sent (cheia noua, apoi build_key_legacy) sau recompute-keys o singura data.
"""

from __future__ import annotations

import hashlib
import json
from typing import Any


def _op_identity(p: Any) -> str:
    """Cod RAR (normalizat) daca exista, altfel codul intern ROAAUTO."""
    get = p.get if isinstance(p, dict) else (lambda k, d=None: getattr(p, k, d))
    cod = (get("cod_prestatie", "") or "").strip().upper()
    if cod:
        return cod
    return (get("cod_op_service", "") or "").strip()


def canonicalize_row(raw: dict[str, Any]) -> dict[str, Any]:
    """Normalizare canonica a unui rand brut. Apelata INAINTE de validare si de build_key.

    - VIN, nr_inmatriculare: strip + upper.
    - odometru_final: strip ".0" (Excel coercion numeric 123456.0 -> "123456").
      Necesar ca validation._parse_int (isdigit()) sa nu respinga float-string.
    - data_prestatie: strip (normalizarea la YYYY-MM-DD se face in parser).
    - prestatii: pastrate ca-atare (rezolvarea e in resolve_prestatii).
    """
    vin = (raw.get("vin") or "").strip().upper()
    nr = (raw.get("nr_inmatriculare") or "").strip().upper()

    # Odometru: strip ".0" Excel float coercion
    odo_raw = raw.get("odometru_final")
    if odo_raw is not None:
        odo_s = str(odo_raw).strip()
        # "123456.0" -> "123456"; "123456.50" nu (nu e coercion Excel pur)
        if "." in odo_s:
            before, after = odo_s.split(".", 1)
            if after == "0" and before.lstrip("-").isdigit():
                odo_s = before
    else:
        odo_s = ""

    # Data (pastrata ca string; parsarea la YYYY-MM-DD e in parser)
    data = str(raw.get("data_prestatie") or "").strip()

    # Prestatii (copie superficiala; rezolvarea e upstream)
    prestatii = list(raw.get("prestatii") or [])

    return {
        "vin": vin,
        "nr_inmatriculare": nr,
        "data_prestatie": data,
        "odometru_final": odo_s,
        "prestatii": prestatii,
    }


def build_key(account_id: int | None, canon: dict[str, Any], rar_env: str = "test") -> str:
    """SHA-256 partajat canal-API + canal-import, env-aware (PRD 5.20 US-003).

    Aplica account_or_default inainte de hash: None si 1 colapseaza la aceeasi
    cheie => acelasi rand logic din canale diferite nu se trimite de doua ori.

    `rar_env` ('test'|'prod') intra in cheie: aceeasi prezentare la test si apoi la
    prod sunt DOUA trimiteri reale distincte (sisteme RAR separate), nu un duplicat.
    Default 'test' = back-compat cu apelantii care nu paseaza inca env-ul; toate
    rutele de ingestie paseaza env-ul rezolvat explicit.
    """
    # Import local ca sa evitam import circular (mapping importa din idempotency via validator)
    from .mapping import account_or_default
    acct = account_or_default(account_id)
    canonic = {
        "account_id": acct,
        "rar_env": rar_env,
        "vin": canon.get("vin", ""),
        "nr_inmatriculare": canon.get("nr_inmatriculare", ""),
        "data_prestatie": canon.get("data_prestatie"),
        "odometru_final": canon.get("odometru_final", ""),
        "prestatii": sorted(_op_identity(p) for p in (canon.get("prestatii") or [])),
    }
    blob = json.dumps(canonic, sort_keys=True, ensure_ascii=False, separators=(",", ":"))
    return hashlib.sha256(blob.encode("utf-8")).hexdigest()


def idempotency_key(account_id: int | None, prezentare: dict[str, Any], rar_env: str = "test") -> str:
    """SHA-256 peste (account_id + rar_env + campurile semnificative ale prezentarii).

    Wrapper backward-compat peste canonicalize_row + build_key.
    Exclude obs si b64Image (cosmetice, nu definesc unicitatea declaratiei).

    NOTA: account_id=None si account_id=1 produc ACEEASI cheie (via
    account_or_default in build_key). Randuri vechi cu cheie-None nu sunt
    acoperite automat — dual-lookup sau recompute-keys la migrare productie.
    """
    canon = canonicalize_row(prezentare)
    return build_key(account_id, canon, rar_env)


def build_key_legacy(account_id: int | None, prezentare: dict[str, Any]) -> str:
    """Cheia in formatul vechi (account_id AS-PASSED, fara canonicalize).

    Folosita EXCLUSIV pentru dual-lookup la already_sent pe DB cu randuri vechi.
    Nu folosi pentru randuri noi.
    """
    canonic = {
        "account_id": account_id,
        "vin": (prezentare.get("vin") or "").strip().upper(),
        "nr_inmatriculare": (prezentare.get("nr_inmatriculare") or "").strip().upper(),
        "data_prestatie": prezentare.get("data_prestatie"),
        "odometru_final": str(prezentare.get("odometru_final") or "").strip(),
        "prestatii": sorted(_op_identity(p) for p in (prezentare.get("prestatii") or [])),
    }
    blob = json.dumps(canonic, sort_keys=True, ensure_ascii=False, separators=(",", ":"))
    return hashlib.sha256(blob.encode("utf-8")).hexdigest()
