"""Orquestador de firma del xamiSDK.

Coordina el flujo de firma PAdES:
  1. prepare (LOCAL): calcula el hash del ByteRange (el documento no sale).
  2. envia el HASH a Xami (el chip firma).
  3. el resultado llega por PUSH (sign.result); el router lo ensambla (LOCAL).

sign() devuelve el request_id de inmediato; el PDF firmado queda disponible en el
store de resultados cuando llega el push. El CLI en modo --wait consulta el store.

Ver docs/servicios_xamiSDK.md (§5.1) y wrapper_xamiSDK_php.md.
"""
from __future__ import annotations
import json
import urllib.request
import urllib.error

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


class SignerError(Exception):
    pass


class Signer:
    def __init__(self, config: Config):
        self.config = config
        self.cache = Cache(config.base_dir)
        self.results = Results(config.base_dir)
        self.engine = PadesEngine(config.base_dir)
        self.state = config.load_state()

    def sign_pdf(self, pdf_bytes: bytes, credential_key: str, *,
                 design_key: str | None = None, variables: dict | None = None,
                 reason: str | None = None, location: str | None = None,
                 signer_name: str | None = None) -> str:
        """Prepara el PDF (local), marca pending y envia el hash a Xami. Devuelve el
        request_id. El PDF firmado llegara por push y quedara en results."""
        # resolver el diseno desde la cache (para la apariencia)
        design = None
        if credential_key:
            cred = self.cache.get_credential(credential_key)
            if cred and not design_key:
                design_key = cred.get("default_design_key")
        if design_key:
            design = self.cache.get_design(design_key)

        # 1. prepare local
        prep = self.engine.prepare(pdf_bytes, reason=reason, location=location,
                                   signer_name=signer_name, design=design,
                                   variables=variables)
        request_id = prep["request_id"]
        digest = prep["digest"]
        self.results.mark_pending(request_id, {"credential_key": credential_key,
                                               "design_key": design_key})

        # 2. enviar el hash a Xami
        try:
            self._send_digest(request_id, digest, credential_key)
        except Exception as e:
            self.results.mark_error(request_id, f"envio a Xami fallo: {e}")
            raise SignerError(str(e)) from None
        return request_id

    def _send_digest(self, request_id: str, digest: str, credential_key: str):
        endpoint = (self.state.get("endpoint") or "https://api.xami.run").rstrip("/")
        token = self.state.get("access_token")
        url = endpoint + "/v1/sdk/sign"
        body = json.dumps({"request_id": request_id, "digest": digest,
                          "credential_key": credential_key}).encode()
        req = urllib.request.Request(url, data=body, headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {token}",
        })
        try:
            with urllib.request.urlopen(req, timeout=20) as r:
                return json.loads(r.read().decode())
        except urllib.error.HTTPError as e:
            detail = e.read().decode(errors="replace")
            raise SignerError(f"HTTP {e.code}: {detail}") from None

    def sign_blockchain(self, tx_hash: str, credential_key: str, *,
                        chain_id: int | None = None) -> str:
        """Envia el hash (keccak256) de una tx a Xami. El chip firma con la clave EVM y
        el resultado (r/s/v) llega por push. Devuelve el request_id. El cliente
        ensambla la tx con su web3."""
        import uuid as _uuid
        request_id = "bcx_" + _uuid.uuid4().hex[:16]
        self.results.mark_pending(request_id, {"credential_key": credential_key,
                                               "kind": "blockchain"})
        endpoint = (self.state.get("endpoint") or "https://api.xami.run").rstrip("/")
        token = self.state.get("access_token")
        body = json.dumps({"request_id": request_id, "hash": tx_hash,
                          "credential_key": credential_key, "chain_id": chain_id}).encode()
        req = urllib.request.Request(endpoint + "/v1/sdk/sign-blockchain", data=body,
            headers={"Content-Type": "application/json", "Authorization": f"Bearer {token}"})
        try:
            with urllib.request.urlopen(req, timeout=20) as r:
                json.loads(r.read().decode())
        except urllib.error.HTTPError as e:
            detail = e.read().decode(errors="replace")
            self.results.mark_error(request_id, f"HTTP {e.code}: {detail}")
            raise SignerError(f"HTTP {e.code}: {detail}") from None
        return request_id

    def result(self, request_id: str) -> dict | None:
        return self.results.get(request_id)

    def wait(self, request_id: str, timeout: float = 60.0, poll: float = 1.0) -> dict:
        """Bloquea hasta que el resultado este DONE/ERROR o venza el timeout."""
        import time
        waited = 0.0
        while waited < timeout:
            rec = self.results.get(request_id)
            if rec and rec.get("status") in ("DONE", "ERROR"):
                return rec
            time.sleep(poll)
            waited += poll
        return {"status": "TIMEOUT", "request_id": request_id}
