"""
Webhook verification and parsing utilities.

Provides HMAC-SHA256 signature verification for incoming Wave webhooks,
supporting both Shared Secret and Signing Secret strategies.
"""

import hashlib
import hmac
import time
from typing import Any, Dict, Optional

from .exceptions import WaveWebhookError


class WebhookVerifier:
    """
    Verify and parse incoming Wave webhook requests.

    Supports two security strategies:
    - **Shared Secret**: Compares the Authorization Bearer token.
    - **Signing Secret** (recommended): Verifies HMAC-SHA256 signatures
      from the ``Wave-Signature`` header.

    Example (Flask)::

        from flask import Flask, request
        from wave_business_api import WebhookVerifier

        app = Flask(__name__)
        verifier = WebhookVerifier(signing_secret="wave_sn_WHS_...")

        @app.route("/webhook", methods=["POST"])
        def webhook():
            try:
                event = verifier.verify_and_parse(
                    raw_body=request.get_data(as_text=True),
                    wave_signature=request.headers.get("Wave-Signature"),
                )
            except WaveWebhookError as e:
                return str(e), 401

            if event["type"] == "checkout.session.completed":
                # Handle successful payment
                pass

            return "OK", 200

    Example (Django)::

        from wave_business_api import WebhookVerifier, WaveWebhookError

        verifier = WebhookVerifier(signing_secret="wave_sn_WHS_...")

        def webhook_view(request):
            try:
                event = verifier.verify_and_parse(
                    raw_body=request.body.decode("utf-8"),
                    wave_signature=request.META.get("HTTP_WAVE_SIGNATURE"),
                )
            except WaveWebhookError:
                return HttpResponse(status=401)
            # process event...
            return HttpResponse("OK")
    """

    def __init__(
        self,
        signing_secret: Optional[str] = None,
        shared_secret: Optional[str] = None,
        tolerance_seconds: int = 300,
    ):
        """
        Initialize the webhook verifier.

        Args:
            signing_secret: Your Wave webhook signing secret (recommended).
                            Used to verify the ``Wave-Signature`` header.
            shared_secret: Your Wave webhook shared secret (simpler but less secure).
                           Used to compare the ``Authorization`` Bearer token.
            tolerance_seconds: Maximum allowed age of webhook timestamp in seconds.
                               Defaults to 300 (5 minutes).

        Raises:
            ValueError: If neither secret is provided.
        """
        if not signing_secret and not shared_secret:
            raise ValueError("Provide either signing_secret or shared_secret.")
        self.signing_secret = signing_secret
        self.shared_secret = shared_secret
        self.tolerance_seconds = tolerance_seconds

    def verify_and_parse(
        self,
        raw_body: str,
        wave_signature: Optional[str] = None,
        authorization: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        Verify a webhook request and return the parsed event data.

        Use the appropriate parameters depending on your security strategy:
        - **Signing Secret**: Pass ``wave_signature`` (from ``Wave-Signature`` header).
        - **Shared Secret**: Pass ``authorization`` (from ``Authorization`` header).

        Args:
            raw_body: The raw (unparsed) request body string.
                      **Important**: Always use the raw body — do not parse JSON first.
            wave_signature: Value of the ``Wave-Signature`` header (signing strategy).
            authorization: Value of the ``Authorization`` header (shared secret strategy).

        Returns:
            Parsed event dict::

                {
                    "id": "EV_QvEZuDSQbLdI",
                    "type": "checkout.session.completed",
                    "data": {...}
                }

        Raises:
            WaveWebhookError: If signature verification fails.
            WaveWebhookError: If the timestamp is expired (signing strategy).
            ValueError: If required header is missing.

        Example::

            event = verifier.verify_and_parse(
                raw_body=request.body,
                wave_signature=request.headers["Wave-Signature"],
            )
            print(event["type"])  # "checkout.session.completed"
        """
        if self.signing_secret and wave_signature:
            self._verify_signing_secret(wave_signature, raw_body)
        elif self.shared_secret and authorization:
            self._verify_shared_secret(authorization)
        else:
            raise WaveWebhookError(
                "No valid authentication header provided. "
                "Pass wave_signature for Signing Secret or authorization for Shared Secret."
            )

        import json
        try:
            return json.loads(raw_body)
        except json.JSONDecodeError as e:
            raise WaveWebhookError(f"Failed to parse webhook body as JSON: {e}")

    def _verify_signing_secret(self, wave_signature: str, raw_body: str) -> None:
        """Verify HMAC-SHA256 signature from Wave-Signature header."""
        parts = wave_signature.split(",")
        if len(parts) < 2:
            raise WaveWebhookError(
                f"Malformed Wave-Signature header: '{wave_signature}'. "
                "Expected format: t={{timestamp}},v1={{signature}}"
            )

        # Extract timestamp
        try:
            timestamp_str = parts[0].split("=")[1]
            timestamp = int(timestamp_str)
        except (IndexError, ValueError):
            raise WaveWebhookError("Invalid timestamp in Wave-Signature header.")

        # Check timestamp freshness (skip if tolerance_seconds <= 0 — useful for testing)
        current_time = int(time.time())
        age = abs(current_time - timestamp)
        if self.tolerance_seconds > 0 and age > self.tolerance_seconds:
            raise WaveWebhookError(
                f"Webhook timestamp is too old ({age}s). "
                f"Maximum allowed: {self.tolerance_seconds}s."
            )

        # Extract all v1 signatures (supports key rotation — multiple may be present)
        signatures = []
        for part in parts[1:]:
            if part.startswith("v1="):
                signatures.append(part[3:])

        if not signatures:
            raise WaveWebhookError("No v1 signature found in Wave-Signature header.")

        # Compute expected signature
        payload = timestamp_str + raw_body
        expected = hmac.new(
            self.signing_secret.encode("utf-8"),
            payload.encode("utf-8"),
            hashlib.sha256,
        ).hexdigest()

        # Compare (timing-safe)
        if not any(hmac.compare_digest(expected, sig) for sig in signatures):
            raise WaveWebhookError(
                "Webhook signature mismatch. "
                "Verify you are using the correct signing secret and the raw request body."
            )

    def _verify_shared_secret(self, authorization: str) -> None:
        """Verify shared secret from Authorization Bearer header."""
        if authorization.startswith("Bearer "):
            token = authorization[7:]
        else:
            token = authorization

        if not hmac.compare_digest(token, self.shared_secret):
            raise WaveWebhookError("Webhook shared secret mismatch.")

    @staticmethod
    def get_event_type(event: Dict[str, Any]) -> str:
        """
        Extract the event type from a parsed webhook event.

        Args:
            event: Parsed event dict.

        Returns:
            Event type string (e.g., "checkout.session.completed").
        """
        return event.get("type", "")

    @staticmethod
    def get_event_data(event: Dict[str, Any]) -> Dict[str, Any]:
        """
        Extract the data payload from a parsed webhook event.

        Args:
            event: Parsed event dict.

        Returns:
            Event data dict.
        """
        return event.get("data", {})
