"""Recevoir un webhook bancaire (événements ``financing.*``) et relire le dossier.

Receive a bank webhook (``financing.*`` events) and fetch the request.

L'événement est mince (identifiants, statut, horodatages ; aucun montant, aucun taux) :
on vérifie la signature, on dédoublonne par ``webhook-id``, puis on relit le dossier
par ``data.links.self`` (``…/public/v1/bank/requests/{id}``) avec un jeton du client
``bank`` (scopes ``bank:*``). La relecture applique les mêmes contrôles que le portail
banque : dossier adressé à l'institution, destinataire n'ayant pas décliné.

Le jeton n'est envoyé qu'à l'API Tauraco : un lien qui ne commence pas exactement par
``TAURACO_API_BASE + "/bank/requests/"`` est ignoré (un événement forgé ne doit pas
faire partir le jeton vers un autre hôte). Le secret du webhook est obligatoire.

Usage : TAURACO_WEBHOOK_SECRET=… TAURACO_CLIENT_ID=… TAURACO_CLIENT_SECRET=… \\
        python3 recepteur_webhook_banque.py
"""
from __future__ import annotations

import json
import os
import re
from http.server import BaseHTTPRequestHandler, HTTPServer
from typing import Optional

from client_credentials import API_BASE, TauracoError, get_token, http_transport
from verifier_webhook import AlreadySeen, secret_key, verify_signature

FINANCING_EVENTS = {"financing.requested", "financing.status_changed", "financing.funded", "contract.signed"}
#: Seuls liens suivis : l'API Tauraco configurée, ressource ``/bank/requests/{id}``.
REQUEST_PREFIX = API_BASE.rstrip("/") + "/bank/requests/"
_REQUEST_ID = re.compile(r"[A-Za-z0-9_-]{1,128}")


def request_link(event: dict) -> Optional[str]:
    """Lien de relecture du dossier, ou ``None`` (événement hors financement, lien absent)."""
    if event.get("type") not in FINANCING_EVENTS:
        return None
    links = (event.get("data") or {}).get("links") or {}
    link = links.get("self")
    if not isinstance(link, str) or not link.startswith(REQUEST_PREFIX):
        return None  # autre hôte, autre chemin : jamais de jeton envoyé
    return link if _REQUEST_ID.fullmatch(link[len(REQUEST_PREFIX):]) else None


def fetch_request(link: str) -> dict:
    if request_link({"type": "financing.requested", "data": {"links": {"self": link}}}) != link:
        raise ValueError("lien de relecture hors de l'API Tauraco : refusé")
    token = get_token(os.environ["TAURACO_CLIENT_ID"], os.environ["TAURACO_CLIENT_SECRET"],
                      "bank:requests:read")["access_token"]
    status, _headers, raw = http_transport("GET", link, {"Authorization": f"Bearer {token}",
                                                          "Accept": "application/json"}, None)
    payload = json.loads(raw or b"{}")
    if status != 200:
        raise TauracoError(status, payload)
    return payload


def make_bank_handler(secret: str) -> type[BaseHTTPRequestHandler]:
    """Récepteur lié à ``secret`` ; ``ValueError`` si le secret est vide ou illisible."""
    if secret_key(secret) is None:
        raise ValueError("secret de webhook vide ou illisible (whsec_ + base64 attendu)")
    seen = AlreadySeen()

    class BankReceiver(BaseHTTPRequestHandler):
        def do_POST(self) -> None:  # noqa: N802
            body = self.rfile.read(int(self.headers.get("Content-Length") or 0))
            headers = dict(self.headers.items())
            if not verify_signature(secret, headers, body):
                self.send_response(401)
                self.end_headers()
                return
            self.send_response(204)
            self.end_headers()
            event = json.loads(body)
            if not seen.first_time(event["id"]):
                return  # reprise ou rejeu : déjà traité
            link = request_link(event)
            if link:
                dossier = fetch_request(link)
                print(event["type"], event["data"].get("request_id"), dossier.get("status"))

        def log_message(self, *_args) -> None:
            return

    return BankReceiver


if __name__ == "__main__":
    handler = make_bank_handler(os.environ["TAURACO_WEBHOOK_SECRET"])  # aucune valeur par défaut
    HTTPServer(("127.0.0.1", 8788), handler).serve_forever()
