"""Webhook Module."""

import hmac
from datetime import datetime, timezone
from hashlib import sha256

__all__ = [
    "NoSignatureError",
    "NoTimestampError",
    "SignatureMismatchError",
    "SignatureVerificationError",
    "TimestampOutsideToleranceError",
]


class SignatureVerificationError(ValueError):
    """Exceptions raised when webhook signature verification fails."""

    pass


class NoTimestampError(SignatureVerificationError):
    """Error raised when the timestamp is missing from the signature."""

    def __init__(self) -> None:
        """Initialize NoTimestampError."""
        super().__init__("No timestamp found")


class NoSignatureError(SignatureVerificationError):
    """Error raised when signatures are missing from the signature."""

    def __init__(self) -> None:
        """Initialize NoSignatureError."""
        super().__init__("No signatures found")


class SignatureMismatchError(SignatureVerificationError):
    """Error raised when the computed signature does not match signatures."""

    def __init__(self) -> None:
        """Initialize SignatureMismatchError."""
        super().__init__("No signatures found matching the expected signature")


class TimestampOutsideToleranceError(SignatureVerificationError):
    """Error raised when timestamp is outside the tolerance window."""

    def __init__(self) -> None:
        """Initialize TimestampOutsideToleranceError."""
        super().__init__("Timestamp outside the tolerance window")


def verify(
    payload: bytes,
    signature: str,
    secret: str,
    tolerance: int = 300,
    scheme: str = "v1",
) -> None:
    """Verify webhook signature.

    Args:
        payload: Raw webhook request body.
        signature: The signature to verify.
        secret: The webhook secret.
        tolerance: Maximum allowed age of the timestamp in seconds. Defaults to 300.
        scheme: Key for signatures in the signature. Defaults to "v1".

    Raises:
        NoTimestampError: Error raised when the timestamp is missing from the signature.
        NoSignatureError: Error raised when signatures are missing from the signature.
        SignatureMismatchError: Error raised when the computed signature does not match
            signatures.
        TimestampOutsideToleranceError: Error raised when timestamp is outside the
            tolerance window.
    """
    timestamp, signatures = _parse_signature(signature, scheme)

    _verify_timestamp(timestamp, tolerance)

    signed_payload = b"%d." % timestamp + payload

    expected_signature = _compute_signature(signed_payload, secret)

    _compare(expected_signature, signatures)


def _parse_signature(signature: str, scheme: str) -> tuple[int, list[str]]:
    """Parse the signature to extract timestamp and signatures.

    Args:
        signature: The signature to parse.
        scheme: Key for signatures in the signature.

    Returns:
        Tuple containing timestamp and signatures.

    Raises:
        NoTimestampError: Error raised when the timestamp is missing from the signature.
        NoSignatureError: Error raised when signatures are missing from the signature.
    """
    list_items = [item.split("=", 1) for item in signature.split(",")]

    timestamp_str = next(
        (item[1] for item in list_items if len(item) == 2 and item[0] == "t"),
        None,
    )
    if timestamp_str is None:
        raise NoTimestampError()

    signatures = [
        item[1] for item in list_items if len(item) == 2 and item[0] == scheme
    ]

    if not signatures:
        raise NoSignatureError()

    return int(timestamp_str), signatures


def _compute_signature(payload: bytes, secret: str) -> str:
    """Compute signature using payload and webhook secret.

    Args:
        payload: Payload bytes from the webhook request body.
        secret: The webhook secret.

    Returns:
        Computed signature.
    """
    mac = hmac.new(secret.encode("utf-8"), msg=payload, digestmod=sha256)
    return mac.hexdigest()


def _compare(expected_signature: str, signatures: list[str]) -> None:
    """Compare signatures with the expected signature.

    Args:
        expected_signature: The expected signature.
        signatures: List of signatures from the signature.

    Raises:
        SignatureMismatchError: Error raised when the expected signature does not match
            signatures.
    """
    for signature in signatures:
        if hmac.compare_digest(expected_signature, signature):
            return None
    raise SignatureMismatchError()


def _verify_timestamp(timestamp: int, tolerance: int) -> None:
    """Verify that the webhook timestamp is within the tolerance window.

    Args:
        timestamp: Timestamp from the webhook signature.
        tolerance: Maximum allowed age of the timestamp in seconds.

    Raises:
        TimestampOutsideToleranceError: Error raised when timestamp is outside the
            tolerance window.
    """
    now_utc = datetime.now(timezone.utc).timestamp()
    if timestamp < now_utc - tolerance or timestamp > now_utc + tolerance:
        raise TimestampOutsideToleranceError()
