# Copyright (c) 2026 MergeGuide, Inc. All rights reserved.
# SPDX-License-Identifier: Proprietary
"""Bitbucket Cloud webhook verification and event parsing.

Handles webhook requests from Bitbucket Cloud:
- HMAC-SHA256 signature verification
- Event parsing for pull request events
- Extraction of PR metadata

Bitbucket webhook events:
- pullrequest:created - New PR opened
- pullrequest:updated - PR updated (commits pushed, title changed)
- pullrequest:fulfilled - PR merged
- pullrequest:rejected - PR declined
- pullrequest:comment_created - Comment added

Security Note:
    Always verify webhook signatures before processing payloads to prevent
    forged requests from triggering unintended actions.
"""

from __future__ import annotations

import hashlib
import hmac
from dataclasses import dataclass


def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
    """Verify the HMAC-SHA256 signature of a Bitbucket webhook request.

    Bitbucket signs webhooks using HMAC-SHA256 and sends the signature
    in the X-Hub-Signature header (format: "sha256=<hex>").

    Args:
        payload: Raw request body bytes
        signature: Value of X-Hub-Signature header
        secret: Webhook secret configured in repository settings

    Returns:
        True if signature is valid, False otherwise
    """
    if not signature or not secret:
        return False

    # Remove algorithm prefix if present
    if signature.startswith("sha256="):
        signature = signature[7:]

    # Compute expected signature
    expected = hmac.new(
        secret.encode("utf-8"),
        payload,
        hashlib.sha256,
    ).hexdigest()

    # Constant-time comparison
    return hmac.compare_digest(expected, signature)


def parse_webhook_event(headers: dict[str, str], body: dict) -> WebhookEvent:
    """Parse Bitbucket webhook headers and body into structured event.

    Args:
        headers: HTTP request headers
        body: Parsed JSON body

    Returns:
        WebhookEvent with parsed data
    """
    headers_lower = {k.lower(): v for k, v in headers.items()}

    event_key = headers_lower.get("x-event-key", "")
    request_uuid = headers_lower.get("x-request-uuid", "")
    hook_uuid = headers_lower.get("x-hook-uuid", "")

    return WebhookEvent(
        event_key=event_key,
        request_uuid=request_uuid,
        hook_uuid=hook_uuid,
        payload=body,
    )


@dataclass
class WebhookEvent:
    """Parsed Bitbucket webhook event.

    Attributes:
        event_key: Bitbucket event type (e.g., "pullrequest:created")
        request_uuid: Unique ID for this webhook delivery
        hook_uuid: UUID of the webhook configuration
        payload: Full webhook payload
    """

    event_key: str
    request_uuid: str
    hook_uuid: str
    payload: dict

    @property
    def is_pull_request(self) -> bool:
        """Check if this is a pull request event."""
        return self.event_key.startswith("pullrequest:")

    @property
    def action(self) -> str:
        """Extract the action from the event key.

        Examples:
            pullrequest:created -> created
            pullrequest:updated -> updated
        """
        if ":" in self.event_key:
            return self.event_key.split(":", 1)[1]
        return self.event_key

    @property
    def should_process_pr(self) -> bool:
        """Determine if this PR event should trigger policy evaluation.

        We evaluate on:
        - created: New PR opened
        - updated: Commits pushed or PR updated
        """
        return self.is_pull_request and self.action in ("created", "updated")

    def get_repository_info(self) -> tuple[str, str] | None:
        """Extract workspace and repository slug.

        Returns:
            Tuple of (workspace, repo_slug) or None if not available
        """
        repo = self.payload.get("repository", {})
        workspace = repo.get("workspace", {}).get("slug", "")
        repo_slug = repo.get("slug", "")

        if workspace and repo_slug:
            return (workspace, repo_slug)

        # Try full_name format
        full_name = repo.get("full_name", "")
        if "/" in full_name:
            parts = full_name.split("/", 1)
            return (parts[0], parts[1])

        return None

    def get_pr_info(self) -> dict | None:
        """Extract pull request metadata.

        Returns:
            Dict containing:
            - pr_id: PR ID number
            - title: PR title
            - source_branch: Source branch name
            - source_commit: Source commit hash
            - destination_branch: Target branch name
            - destination_commit: Target commit hash
            - author: PR author username
            - state: PR state (OPEN, MERGED, DECLINED)

            Returns None if not a PR event
        """
        if not self.is_pull_request:
            return None

        pr = self.payload.get("pullrequest", {})
        source = pr.get("source", {})
        destination = pr.get("destination", {})

        return {
            "pr_id": pr.get("id"),
            "title": pr.get("title", ""),
            "source_branch": source.get("branch", {}).get("name", ""),
            "source_commit": source.get("commit", {}).get("hash", ""),
            "destination_branch": destination.get("branch", {}).get("name", ""),
            "destination_commit": destination.get("commit", {}).get("hash", ""),
            "author": pr.get("author", {}).get("username", ""),
            "state": pr.get("state", ""),
        }

    def get_actor_info(self) -> dict | None:
        """Extract information about who triggered the event.

        Returns:
            Dict with actor details or None
        """
        actor = self.payload.get("actor", {})
        if not actor:
            return None

        return {
            "username": actor.get("username", ""),
            "display_name": actor.get("display_name", ""),
            "uuid": actor.get("uuid", ""),
            "account_id": actor.get("account_id", ""),
        }
