"""Cache local de configuracion del xamiSDK.

Guarda credenciales y disenos que Xami empuja, para NO consultar en cada firma.
Versionado por elemento individual: cada credencial y cada diseno lleva su propia
version; al llegar un update, se reemplaza solo ese elemento.

Estructura en disco (bajo XAMISDK_HOME/cache/):
  credentials/<credential_key>.json
  designs/<design_key>.json          (params completos, incluida la imagen del sello)

La imagen del sello puede ser grande; se guarda embebida en el JSON del diseno tal
como llega. Ver docs/servicios_xamiSDK.md (§3 cache).
"""
from __future__ import annotations
import json
import os
import stat


class Cache:
    def __init__(self, base_dir: str):
        self.base_dir = base_dir
        self.cred_dir = os.path.join(base_dir, "cache", "credentials")
        self.design_dir = os.path.join(base_dir, "cache", "designs")

    def ensure_dirs(self):
        os.makedirs(self.cred_dir, exist_ok=True)
        os.makedirs(self.design_dir, exist_ok=True)

    # ---- credenciales ----
    def put_credential(self, cred: dict):
        """Guarda/actualiza una credencial. Requiere 'credential_key'."""
        key = cred.get("credential_key") or cred.get("key")
        if not key:
            raise ValueError("credencial sin credential_key")
        self.ensure_dirs()
        path = os.path.join(self.cred_dir, f"{_safe(key)}.json")
        _write_json(path, cred)

    def remove_credential(self, credential_key: str) -> bool:
        path = os.path.join(self.cred_dir, f"{_safe(credential_key)}.json")
        if os.path.exists(path):
            os.remove(path)
            return True
        return False

    def get_credential(self, credential_key: str) -> dict | None:
        path = os.path.join(self.cred_dir, f"{_safe(credential_key)}.json")
        return _read_json(path)

    def list_credentials(self) -> list[dict]:
        return _list_json(self.cred_dir)

    # ---- disenos ----
    def put_design(self, design: dict):
        """Guarda/actualiza un diseno. Requiere 'design_key'."""
        key = design.get("design_key") or design.get("key")
        if not key:
            raise ValueError("diseno sin design_key")
        self.ensure_dirs()
        path = os.path.join(self.design_dir, f"{_safe(key)}.json")
        _write_json(path, design)

    def remove_design(self, design_key: str) -> bool:
        path = os.path.join(self.design_dir, f"{_safe(design_key)}.json")
        if os.path.exists(path):
            os.remove(path)
            return True
        return False

    def get_design(self, design_key: str) -> dict | None:
        path = os.path.join(self.design_dir, f"{_safe(design_key)}.json")
        return _read_json(path)

    def list_designs(self) -> list[dict]:
        return _list_json(self.design_dir)

    # ---- resumen ----
    def summary(self) -> dict:
        return {
            "credentials": len(self.list_credentials()),
            "designs": len(self.list_designs()),
        }


def _safe(key: str) -> str:
    """Sanitiza una key para usarla como nombre de archivo."""
    return "".join(c for c in str(key) if c.isalnum() or c in "-_") or "_"


def _write_json(path: str, data: dict):
    with open(path, "w") as f:
        json.dump(data, f, indent=2)
    os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)   # 600


def _read_json(path: str) -> dict | None:
    if not os.path.exists(path):
        return None
    try:
        with open(path) as f:
            return json.load(f)
    except Exception:
        return None


def _list_json(directory: str) -> list[dict]:
    if not os.path.isdir(directory):
        return []
    out = []
    for name in sorted(os.listdir(directory)):
        if name.endswith(".json"):
            d = _read_json(os.path.join(directory, name))
            if d is not None:
                out.append(d)
    return out
