"""Slack request signature verifier"""

import hashlib
import hmac
from time import time
from typing import Dict, Optional, Union, TYPE_CHECKING

# Fallback to Dict for Python 3.7/3.8 compatibility (safe to remove once these versions are no longer supported)
if TYPE_CHECKING:
    from collections.abc import Mapping
else:
    Mapping = Dict


class Clock:
    def now(self) -> float:
        return time()


class SignatureVerifier:
    def __init__(self, signing_secret: str, clock: Clock = Clock()):
        """Slack request signature verifier

        Slack signs its requests using a secret that's unique to your app.
        With the help of signing secrets, your app can more confidently verify
        whether requests from us are authentic.
        https://docs.slack.dev/authentication/verifying-requests-from-slack/
        """
        self.signing_secret = signing_secret
        self.clock = clock

    @property
    def signing_secret(self) -> str:
        return self._signing_secret

    @signing_secret.setter
    def signing_secret(self, value: str) -> None:
        if not isinstance(value, str):
            raise ValueError("signing_secret must be a string")
        if not value.strip():
            raise ValueError("signing_secret must not be empty.")
        self._signing_secret = value

    def is_valid_request(
        self,
        body: Union[str, bytes],
        headers: Mapping[str, str],
    ) -> bool:
        """Verifies if the given signature is valid"""
        if headers is None:
            return False
        normalized_headers = {k.lower(): v for k, v in headers.items()}
        return self.is_valid(
            body=body,
            timestamp=normalized_headers.get("x-slack-request-timestamp", None),
            signature=normalized_headers.get("x-slack-signature", None),
        )

    def is_valid(
        self,
        body: Union[str, bytes],
        timestamp: Optional[str],
        signature: Optional[str],
    ) -> bool:
        """Verifies if the given signature is valid"""
        if timestamp is None or signature is None:
            return False

        if abs(self.clock.now() - int(timestamp)) > 60 * 5:
            return False

        calculated_signature = self.generate_signature(timestamp=timestamp, body=body)
        if calculated_signature is None:
            return False
        return hmac.compare_digest(calculated_signature, signature)

    def generate_signature(self, *, timestamp: str, body: Union[str, bytes]) -> Optional[str]:
        """Generates a signature"""
        if timestamp is None:
            return None
        if body is None:
            body = ""
        if isinstance(body, bytes):
            body = body.decode("utf-8")

        format_req = str.encode(f"v0:{timestamp}:{body}")
        encoded_secret = str.encode(self.signing_secret)
        request_hash = hmac.new(encoded_secret, format_req, hashlib.sha256).hexdigest()
        calculated_signature = f"v0={request_hash}"
        return calculated_signature
