"""Webhook helpers — signature verification + event parsing."""

from __future__ import annotations

import hashlib
import hmac
import json
import time
from typing import Union

from .errors import WebhookSignatureError
from .types import WebhookEvent

_DEFAULT_TOLERANCE_SECONDS = 300  # 5 minutes — matches backend WebhookSignerService


class WebhooksResource:
    """Verify a webhook payload and return the parsed event.

    Example::

        event = client.webhooks.construct_event(
            payload=request.body,                         # raw bytes, NOT json.loads
            sig_header=request.headers["Gate-Signature"],
            secret=os.environ["WEBHOOK_SECRET"],
        )

    Raises :class:`WebhookSignatureError` on any failure (malformed
    header, missing parts, stale timestamp, signature mismatch).

    IMPORTANT: pass the *raw* request body — Flask's ``request.get_data()``
    is right; ``request.json`` re-parses + re-serialises and breaks
    verification. Django: use ``request.body``. FastAPI: read with
    ``await request.body()`` BEFORE calling ``await request.json()``.
    """

    def construct_event(
        self,
        payload: Union[str, bytes],
        sig_header: str,
        secret: str,
        *,
        tolerance: int = _DEFAULT_TOLERANCE_SECONDS,
    ) -> WebhookEvent:
        if not secret or not isinstance(secret, str):
            raise WebhookSignatureError("webhook secret is required")
        if not sig_header or not isinstance(sig_header, str):
            raise WebhookSignatureError("Gate-Signature header missing")

        timestamp, v1 = self._parse_header(sig_header)
        now = int(time.time())
        if abs(now - timestamp) > tolerance:
            raise WebhookSignatureError(
                f"webhook timestamp is outside tolerance ({tolerance}s). "
                "Reject as replayed or clock-skewed."
            )

        payload_bytes = payload.encode("utf-8") if isinstance(payload, str) else payload
        signed = f"{timestamp}.".encode("utf-8") + payload_bytes
        expected = hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()

        if not hmac.compare_digest(expected, v1):
            raise WebhookSignatureError("webhook signature verification failed")

        try:
            return json.loads(payload_bytes)  # type: ignore[no-any-return]
        except ValueError as err:
            raise WebhookSignatureError(
                f"signed payload was not valid JSON: {err}"
            ) from err

    # ---- internals ---------------------------------------------------

    def _parse_header(self, header: str) -> tuple[int, str]:
        # Stripe-compatible: `t=<unix-ts>,v1=<hex>`.
        parts: dict[str, str] = {}
        for segment in header.split(","):
            if "=" not in segment:
                continue
            key, _, value = segment.partition("=")
            key = key.strip()
            value = value.strip()
            if key:
                parts[key] = value

        t_raw = parts.get("t")
        v1 = parts.get("v1")
        if not t_raw or not v1:
            raise WebhookSignatureError(
                "Gate-Signature header is malformed — expected `t=<ts>,v1=<hex>`"
            )
        try:
            timestamp = int(t_raw)
        except ValueError as err:
            raise WebhookSignatureError(
                "Gate-Signature `t=` value is not an integer"
            ) from err
        if timestamp <= 0:
            raise WebhookSignatureError(
                "Gate-Signature `t=` value is not positive"
            )
        return timestamp, v1


__all__ = ["WebhooksResource"]
