"""
Client di esempio per le API del tuo account ConsensoSicuro (Python 3.9+,
libreria requests).

È un punto di partenza da copiare nel tuo gestionale, non una libreria
ufficiale. Riferimento completo: https://consensosicuro.it/integrazioni/api

    pip install requests
    export CONSENSOSICURO_API_KEY=ak_test_...
    python client.py
"""
import hashlib
import hmac
import os
import re
import time

import requests

BASE_URL = "https://api.consensosicuro.it/api/v1/account"


class ConsensoSicuroError(Exception):
    """Errore dell'API: status HTTP e code stabile (quando c'è)."""

    def __init__(self, status, body):
        self.status = status
        self.body = body if isinstance(body, dict) else {}
        self.code = self.body.get("code")
        super().__init__(self.body.get("error") or f"HTTP {status}")


class ConsensoSicuroClient:
    def __init__(self, api_key, base_url=BASE_URL):
        self.base_url = base_url
        self.session = requests.Session()
        self.session.headers["Authorization"] = f"Bearer {api_key}"

    def _request(self, method, path, json=None, idempotency_key=None, sede_id=None):
        headers = {}
        if idempotency_key:
            # Stessa chiave e stesso corpo entro 24 ore: ritorna la risposta della prima volta.
            headers["X-Idempotency-Key"] = idempotency_key
        if sede_id:
            # Strutture: la sede su cui lavorare.
            headers["X-Structure-Sede-Id"] = sede_id
        res = self.session.request(method, self.base_url + path, json=json, headers=headers, timeout=30)
        data = res.json() if res.content else None
        if not res.ok:
            raise ConsensoSicuroError(res.status_code, data)
        return data

    def send_consent(self, body, idempotency_key=None, sede_id=None):
        """POST /consents: invia un consenso. Risponde 201 con il dettaglio."""
        return self._request("POST", "/consents", json=body, idempotency_key=idempotency_key, sede_id=sede_id)

    def get_consent(self, consent_id):
        """GET /consents/{id}: stato, firmatari, consegna, documenti."""
        return self._request("GET", f"/consents/{consent_id}")

    def consent_pdf_link(self, consent_id):
        """GET /consents/{id}/pdf: link firmato al PDF, valido un'ora."""
        return self._request("GET", f"/consents/{consent_id}/pdf")


def verify_webhook_signature(raw_body: bytes, signature_header: str, timestamp: str, secret: str, tolerance: int = 300) -> bool:
    """
    Verifica una consegna del webhook.

    - timestamp = intestazione X-ConsensoSicuro-Timestamp (secondi Unix
      dell'invio): deve essere entro 5 minuti dal tuo orologio. Ogni riprova
      ne porta uno nuovo.
    - signature_header = intestazione X-ConsensoSicuro-Signature: uno o due
      valori "v1=<hex>" separati da virgola (due per 24 ore dopo una rotazione
      del segreto). Ognuno è l'HMAC-SHA256 di "timestamp.corpo" con il segreto
      whsec_... Basta che UNO torni.
    - raw_body sono i byte ricevuti, prima di json.loads.

    Confronto a tempo costante: una firma malformata dà False, mai un'eccezione.
    """
    if not isinstance(timestamp, str) or not timestamp.isdigit():
        return False
    if abs(int(time.time()) - int(timestamp)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    for part in (signature_header or "").split(","):
        scheme, _, value = part.strip().partition("=")
        if scheme == "v1" and re.fullmatch(r"[0-9a-fA-F]{64}", value) and hmac.compare_digest(expected, value.lower()):
            return True
    return False


# Protezione dal replay, oltre al timestamp: deduplica su
# X-ConsensoSicuro-Delivery-Id, che resta uguale fra un tentativo e le sue
# riprove. Ricorda gli id elaborati per almeno 30 giorni (il «Riprova» della
# dashboard vale 30 giorni); qui un set in memoria come esempio, nel
# gestionale una tabella con indice unico.
_processed_deliveries = set()


def is_first_delivery(delivery_id: str) -> bool:
    if not delivery_id or delivery_id in _processed_deliveries:
        return False
    _processed_deliveries.add(delivery_id)
    return True


if __name__ == "__main__":
    cs = ConsensoSicuroClient(os.environ["CONSENSOSICURO_API_KEY"])
    # Con una chiave ak_test_ il consenso nasce ma nessun messaggio parte.
    consent = cs.send_consent(
        {
            "patient": {"firstName": "Mario", "lastName": "Rossi", "phone": "+393331234567"},
            "template": {"treatments": ["psi-colloquio"]},
            "delivery": {"channel": "WHATSAPP", "language": "it"},
            "metadata": {"cartella": "CRT-119"},
        },
        idempotency_key="visita-119",
    )
    print(consent["id"], consent["status"])
    print(cs.get_consent(consent["id"])["status"])
