"""Router de mensajes entrantes del xamiSDK (el push de Xami).

Punto unico por el que Xami empuja datos. Para cada sobre:
  1. abre el sobre (verifica firma de Xami + descifra con la privada del SDK)
  2. valida el timestamp (anti-replay, ventana configurable)
  3. rutea por `type` a la accion correspondiente (actualiza cache, etc.)

Tipos entrantes (Fase 2): credential.update/revoke, design.update/remove, ping.
Otros tipos (sign.result, license.revoke) se manejaran en fases posteriores.
Ver docs/servicios_xamiSDK.md (§2 endpoint router).
"""
from __future__ import annotations
import time

from xamisdk import crypto
from xamisdk.cache import Cache
from xamisdk.config import Config
from xamisdk.results import Results
from xamisdk.pades_engine import PadesEngine

# ventana anti-replay (segundos): se rechazan mensajes con timestamp fuera de +-window
REPLAY_WINDOW = 300


class RouterError(Exception):
    pass


class MessageRouter:
    def __init__(self, config: Config, cache: Cache | None = None):
        self.config = config
        self.cache = cache or Cache(config.base_dir)
        self.results = Results(config.base_dir)
        self.engine = PadesEngine(config.base_dir)
        self._state = config.load_state()

    def _xami_pub(self) -> str:
        return self.config.xami_public_pem()

    def _sdk_priv(self) -> str:
        return self.config.sdk_private_pem()

    def handle_envelope(self, envelope: str) -> dict:
        """Procesa un sobre sellado recibido de Xami. Devuelve un dict con el
        resultado {type, ok, ...}. Lanza RouterError si el sobre es invalido."""
        # 1. abrir (verifica firma de Xami + descifra)
        try:
            payload = crypto.open_from_xami(envelope, self._xami_pub(), self._sdk_priv())
        except Exception as e:
            raise RouterError(f"sobre invalido (firma/descifrado): {e}") from None

        # 2. anti-replay por timestamp
        ts = payload.get("ts")
        if ts is not None:
            skew = abs(int(time.time()) - int(ts))
            if skew > REPLAY_WINDOW:
                raise RouterError(f"mensaje fuera de la ventana temporal ({skew}s)")

        # 3. rutear por type
        mtype = payload.get("type")
        if not mtype:
            raise RouterError("mensaje sin type")
        return self.route(mtype, payload)

    def route(self, mtype: str, payload: dict) -> dict:
        handler = _HANDLERS.get(mtype)
        if not handler:
            return {"type": mtype, "ok": False, "error": "type no soportado"}
        return handler(self, payload)

    # ---- handlers ----
    def _h_credential_update(self, payload: dict) -> dict:
        cred = payload.get("credential") or {}
        self.cache.put_credential(cred)
        return {"type": "credential.update", "ok": True,
                "credential_key": cred.get("credential_key") or cred.get("key")}

    def _h_credential_revoke(self, payload: dict) -> dict:
        key = payload.get("credential_key")
        removed = self.cache.remove_credential(key) if key else False
        return {"type": "credential.revoke", "ok": True, "credential_key": key, "removed": removed}

    def _h_design_update(self, payload: dict) -> dict:
        design = payload.get("design") or {}
        self.cache.put_design(design)
        return {"type": "design.update", "ok": True,
                "design_key": design.get("design_key") or design.get("key")}

    def _h_design_remove(self, payload: dict) -> dict:
        key = payload.get("design_key")
        removed = self.cache.remove_design(key) if key else False
        return {"type": "design.remove", "ok": True, "design_key": key, "removed": removed}

    def _h_sign_result(self, payload: dict) -> dict:
        """Xami empujo el resultado de la firma del chip. Ensambla el PAdES local
        (el documento nunca salio) y guarda el PDF firmado."""
        request_id = payload.get("requestId") or payload.get("request_id")
        if not request_id:
            return {"type": "sign.result", "ok": False, "error": "falta requestId"}
        err = payload.get("error")
        if err:
            self.results.mark_error(request_id, str(err))
            return {"type": "sign.result", "ok": False, "request_id": request_id, "error": err}
        signature = payload.get("signature")
        cert = payload.get("cert")
        algorithm = payload.get("algorithm")
        if not signature or not cert:
            self.results.mark_error(request_id, "resultado sin signature/cert")
            return {"type": "sign.result", "ok": False, "request_id": request_id,
                    "error": "resultado incompleto"}
        try:
            signed_pdf = self.engine.assemble(request_id, signature, cert, algorithm)
        except Exception as e:
            self.results.mark_error(request_id, f"ensamblaje fallo: {e}")
            return {"type": "sign.result", "ok": False, "request_id": request_id, "error": str(e)}
        # guardar el PDF firmado junto al resultado
        import os
        out_dir = os.path.join(self.config.base_dir, "signed")
        os.makedirs(out_dir, exist_ok=True)
        out_path = os.path.join(out_dir, f"{request_id}.pdf")
        with open(out_path, "wb") as f:
            f.write(signed_pdf)
        self.results.mark_done(request_id, out_path, {"algorithm": algorithm})
        self.engine.cleanup(request_id)
        return {"type": "sign.result", "ok": True, "request_id": request_id,
                "output_path": out_path}

    def _h_blockchain_result(self, payload: dict) -> dict:
        """Xami empujo la firma blockchain (r/s/v). Se guarda en results; el cliente
        ensambla la tx con su web3 (no hay ensamblaje local que hacer)."""
        request_id = payload.get("requestId") or payload.get("request_id")
        if not request_id:
            return {"type": "blockchain.result", "ok": False, "error": "falta requestId"}
        err = payload.get("error")
        if err:
            self.results.mark_error(request_id, str(err))
            return {"type": "blockchain.result", "ok": False, "request_id": request_id, "error": err}
        self.results.mark_done(request_id, None, {
            "r": payload.get("r"), "s": payload.get("s"), "v": payload.get("v"),
            "recid": payload.get("recid"), "signature": payload.get("signature"),
            "algorithm": payload.get("algorithm"), "kind": "blockchain"})
        return {"type": "blockchain.result", "ok": True, "request_id": request_id}

    def _h_ping(self, payload: dict) -> dict:
        return {"type": "pong", "ok": True, "nonce": payload.get("nonce")}


_HANDLERS = {
    "credential.update": MessageRouter._h_credential_update,
    "credential.revoke": MessageRouter._h_credential_revoke,
    "design.update":     MessageRouter._h_design_update,
    "design.remove":     MessageRouter._h_design_remove,
    "ping":              MessageRouter._h_ping,
    "sign.result":       MessageRouter._h_sign_result,
    "blockchain.result": MessageRouter._h_blockchain_result,
}
