"""
Webhook generation service for mock mode.

Reads GitHub installations from the database and generates realistic
webhook payloads that can be sent to the application's webhook endpoint.
"""

from __future__ import annotations

import hashlib
import hmac
import json
import logging
import random
import uuid
from datetime import datetime
from typing import Any, Literal

import requests
from django.conf import settings

logger = logging.getLogger(__name__)


# Event type definitions
WebhookEventType = Literal[
    "issues",
    "pull_request",
    "issue_comment",
    "discussion",
    "pull_request_review",
]


class WebhookGenerator:
    """Service for generating and sending mock GitHub webhooks."""

    def __init__(self, webhook_url: str | None = None, secret: str | None = None):
        """Initialize the webhook generator.

        Args:
            webhook_url: URL to send webhooks to (defaults to settings)
            secret: Webhook secret for signing (defaults to settings)
        """
        self.webhook_url = webhook_url or getattr(
            settings, "GITHUB_WEBHOOK_URL", "http://localhost:8000/integrations/github/webhook/"
        )
        self.secret = secret or getattr(settings, "GITHUB_WEBHOOK_SECRET", "mock-webhook-secret")

    def generate_signature(self, payload: bytes) -> str:
        """Generate HMAC-SHA256 signature for payload.

        Args:
            payload: Raw payload bytes

        Returns:
            Signature in "sha256={hex}" format
        """
        signature = hmac.new(
            self.secret.encode("utf-8"),
            payload,
            hashlib.sha256,
        ).hexdigest()
        return f"sha256={signature}"

    def send_webhook(
        self,
        event_type: str,
        payload: dict[str, Any],
        *,
        dry_run: bool = False,
    ) -> dict[str, Any]:
        """Send a webhook to the configured endpoint.

        Args:
            event_type: GitHub event type (e.g., "issues", "pull_request")
            payload: The webhook payload
            dry_run: If True, don't actually send the webhook

        Returns:
            Result dict with status, event_type, and response info
        """
        payload_bytes = json.dumps(payload).encode("utf-8")
        delivery_id = str(uuid.uuid4())

        headers = {
            "X-GitHub-Event": event_type,
            "X-GitHub-Delivery": delivery_id,
            "X-Hub-Signature-256": self.generate_signature(payload_bytes),
            "Content-Type": "application/json",
        }

        result = {
            "event_type": event_type,
            "delivery_id": delivery_id,
            "payload_preview": {
                "action": payload.get("action"),
                "repository": payload.get("repository", {}).get("full_name"),
            },
        }

        if dry_run:
            result["status"] = "dry_run"
            result["would_send_to"] = self.webhook_url
            return result

        try:
            response = requests.post(
                self.webhook_url,
                data=payload_bytes,
                headers=headers,
                timeout=30,
            )
            result["status"] = "sent"
            result["response_status"] = response.status_code
            result["response_body"] = response.text[:200] if response.text else None
        except requests.RequestException as e:
            result["status"] = "error"
            result["error"] = str(e)

        return result


class MockWebhookService:
    """High-level service for generating mock webhooks from database installations."""

    def __init__(self):
        """Initialize the mock webhook service."""
        self.generator = WebhookGenerator()

    def _generate_user(self, user_id: int | None = None) -> dict[str, Any]:
        """Generate a mock GitHub user."""
        from faker import Faker

        fake = Faker()

        if user_id is None:
            user_id = random.randint(1000000, 99999999)

        login = f"{fake.first_name().lower()}{random.randint(1, 999)}"

        return {
            "login": login,
            "id": user_id,
            "node_id": f"U_{user_id}",
            "avatar_url": f"https://avatars.githubusercontent.com/u/{user_id}",
            "type": "User",
        }

    def _generate_label(self) -> dict[str, Any]:
        """Generate a mock GitHub label."""
        labels = [
            ("bug", "d73a4a"),
            ("enhancement", "a2eeef"),
            ("documentation", "0075ca"),
            ("good first issue", "7057ff"),
            ("help wanted", "008672"),
        ]
        name, color = random.choice(labels)
        return {
            "id": random.randint(100000, 9999999),
            "name": name,
            "color": color,
        }

    def _generate_repository_payload(
        self,
        repo_id: int,
        full_name: str,
        owner_login: str,
        owner_id: int,
        owner_type: str = "Organization",
    ) -> dict[str, Any]:
        """Generate a repository object for webhook payloads."""
        name = full_name.split("/")[-1]
        return {
            "id": repo_id,
            "node_id": f"R_{repo_id}",
            "name": name,
            "full_name": full_name,
            "private": False,
            "owner": {
                "login": owner_login,
                "id": owner_id,
                "type": owner_type,
            },
            "html_url": f"https://github.com/{full_name}",
        }

    def generate_issue_webhook(
        self,
        installation,
        repository,
        *,
        action: str = "opened",
        issue_number: int | None = None,
    ) -> dict[str, Any]:
        """Generate an issue webhook payload.

        Args:
            installation: GitHubInstallation model instance
            repository: GitHubRepository model instance
            action: Issue action ("opened", "closed", "edited", etc.)
            issue_number: Specific issue number (auto-generated if None)

        Returns:
            Complete issue webhook payload
        """
        from faker import Faker

        fake = Faker()

        if issue_number is None:
            issue_number = random.randint(1, 500)

        issue_id = random.randint(1000000000, 9999999999)
        author = self._generate_user()
        created_at = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")

        issue = {
            "id": issue_id,
            "node_id": f"I_{issue_id}",
            "number": issue_number,
            "title": f"Bug: {fake.sentence(nb_words=5).rstrip('.')}",
            "body": f"## Description\n\n{fake.paragraph(nb_sentences=3)}",
            "state": "open" if action == "opened" else "closed",
            "user": author,
            "labels": [self._generate_label() for _ in range(random.randint(0, 2))],
            "created_at": created_at,
            "updated_at": created_at,
            "html_url": f"https://github.com/{repository.full_name}/issues/{issue_number}",
        }

        if action == "closed":
            issue["state_reason"] = random.choice(["completed", "not_planned"])
            issue["closed_at"] = created_at

        repo_payload = self._generate_repository_payload(
            repository.repo_id,
            repository.full_name,
            installation.account_login,
            installation.account_id,
            installation.account_type,
        )

        return {
            "action": action,
            "issue": issue,
            "repository": repo_payload,
            "sender": author if action == "opened" else self._generate_user(),
        }

    def generate_pr_webhook(
        self,
        installation,
        repository,
        *,
        action: str = "opened",
        pr_number: int | None = None,
        merged: bool = False,
    ) -> dict[str, Any]:
        """Generate a pull request webhook payload.

        Args:
            installation: GitHubInstallation model instance
            repository: GitHubRepository model instance
            action: PR action ("opened", "closed", "edited", etc.)
            pr_number: Specific PR number (auto-generated if None)
            merged: Whether the PR was merged (for closed action)

        Returns:
            Complete pull request webhook payload
        """
        from faker import Faker

        fake = Faker()

        if pr_number is None:
            pr_number = random.randint(1, 500)

        pr_id = random.randint(1000000000, 9999999999)
        author = self._generate_user()
        created_at = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
        branch = f"feature/{fake.word()}-{random.randint(1, 99)}"
        sha = hashlib.sha1(str(random.random()).encode()).hexdigest()

        pr = {
            "id": pr_id,
            "node_id": f"PR_{pr_id}",
            "number": pr_number,
            "title": f"feat: {fake.sentence(nb_words=4).rstrip('.')}",
            "body": f"## Summary\n\n{fake.paragraph(nb_sentences=2)}",
            "state": "open" if action == "opened" else "closed",
            "merged": merged,
            "draft": False,
            "user": author,
            "labels": [self._generate_label() for _ in range(random.randint(0, 2))],
            "head": {
                "ref": branch,
                "sha": sha,
            },
            "base": {
                "ref": "main",
                "sha": hashlib.sha1(str(random.random()).encode()).hexdigest(),
            },
            "created_at": created_at,
            "updated_at": created_at,
            "html_url": f"https://github.com/{repository.full_name}/pull/{pr_number}",
        }

        if action == "closed":
            pr["closed_at"] = created_at
            if merged:
                pr["merged_at"] = created_at
                pr["merged_by"] = self._generate_user()
                pr["merge_commit_sha"] = hashlib.sha1(str(random.random()).encode()).hexdigest()

        repo_payload = self._generate_repository_payload(
            repository.repo_id,
            repository.full_name,
            installation.account_login,
            installation.account_id,
            installation.account_type,
        )

        return {
            "action": action,
            "number": pr_number,
            "pull_request": pr,
            "repository": repo_payload,
            "sender": author if action == "opened" else self._generate_user(),
        }

    def generate_issue_comment_webhook(
        self,
        installation,
        repository,
        *,
        action: str = "created",
        issue_number: int | None = None,
        is_pr: bool = False,
    ) -> dict[str, Any]:
        """Generate an issue_comment webhook payload.

        Args:
            installation: GitHubInstallation model instance
            repository: GitHubRepository model instance
            action: Comment action ("created", "edited", "deleted")
            issue_number: Issue/PR number (auto-generated if None)
            is_pr: Whether the comment is on a PR

        Returns:
            Complete issue_comment webhook payload
        """
        from faker import Faker

        fake = Faker()

        if issue_number is None:
            issue_number = random.randint(1, 500)

        comment_id = random.randint(1000000000, 9999999999)
        commenter = self._generate_user()
        created_at = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")

        issue = {
            "number": issue_number,
            "title": f"{'feat:' if is_pr else 'Bug:'} {fake.sentence(nb_words=4).rstrip('.')}",
            "state": "open",
        }

        if is_pr:
            issue["pull_request"] = {
                "url": f"https://api.github.com/repos/{repository.full_name}/pulls/{issue_number}",
            }

        comment = {
            "id": comment_id,
            "node_id": f"IC_{comment_id}",
            "body": fake.paragraph(nb_sentences=2),
            "user": commenter,
            "created_at": created_at,
            "updated_at": created_at,
            "html_url": f"https://github.com/{repository.full_name}/{'pull' if is_pr else 'issues'}/{issue_number}#issuecomment-{comment_id}",
        }

        repo_payload = self._generate_repository_payload(
            repository.repo_id,
            repository.full_name,
            installation.account_login,
            installation.account_id,
            installation.account_type,
        )

        return {
            "action": action,
            "issue": issue,
            "comment": comment,
            "repository": repo_payload,
            "sender": commenter,
        }

    def generate_random_webhook(self, installation, repository) -> tuple[str, dict[str, Any]]:
        """Generate a random webhook for the given installation/repository.

        Args:
            installation: GitHubInstallation model instance
            repository: GitHubRepository model instance

        Returns:
            Tuple of (event_type, payload)
        """
        event_choices = [
            ("issues", lambda: self.generate_issue_webhook(installation, repository)),
            ("issues", lambda: self.generate_issue_webhook(installation, repository, action="closed")),
            ("pull_request", lambda: self.generate_pr_webhook(installation, repository)),
            ("pull_request", lambda: self.generate_pr_webhook(installation, repository, action="closed", merged=True)),
            ("issue_comment", lambda: self.generate_issue_comment_webhook(installation, repository)),
            ("issue_comment", lambda: self.generate_issue_comment_webhook(installation, repository, is_pr=True)),
        ]

        event_type, generator = random.choice(event_choices)
        return event_type, generator()

    def generate_webhooks_for_installation(
        self,
        installation,
        *,
        count: int = 5,
        event_types: list[str] | None = None,
        dry_run: bool = False,
    ) -> list[dict[str, Any]]:
        """Generate and send multiple webhooks for an installation.

        Args:
            installation: GitHubInstallation model instance
            count: Number of webhooks to generate
            event_types: Specific event types to generate (random if None)
            dry_run: If True, don't actually send webhooks

        Returns:
            List of results from sending each webhook
        """
        results = []
        repositories = list(installation.repositories.filter(sync_status="active"))

        if not repositories:
            logger.warning(f"No active repositories for installation {installation.installation_id}")
            return results

        for _ in range(count):
            repo = random.choice(repositories)
            event_type, payload = self.generate_random_webhook(installation, repo)

            # Filter by event types if specified
            if event_types and event_type not in event_types:
                continue

            result = self.generator.send_webhook(event_type, payload, dry_run=dry_run)
            result["installation_id"] = installation.installation_id
            result["repository"] = repo.full_name
            results.append(result)

        return results

    def generate_webhooks_for_all_installations(
        self,
        *,
        count_per_installation: int = 5,
        event_types: list[str] | None = None,
        dry_run: bool = False,
    ) -> dict[str, Any]:
        """Generate and send webhooks for all installations in the database.

        Args:
            count_per_installation: Webhooks per installation
            event_types: Specific event types to generate
            dry_run: If True, don't actually send webhooks

        Returns:
            Summary dict with results per installation
        """
        from integrations.models import GitHubInstallation

        installations = GitHubInstallation.objects.filter(suspended_at__isnull=True)
        summary = {
            "total_installations": 0,
            "total_webhooks_sent": 0,
            "installations": {},
        }

        for installation in installations:
            summary["total_installations"] += 1
            results = self.generate_webhooks_for_installation(
                installation,
                count=count_per_installation,
                event_types=event_types,
                dry_run=dry_run,
            )
            summary["installations"][installation.installation_id] = {
                "account": installation.account_login,
                "webhooks_sent": len(results),
                "results": results,
            }
            summary["total_webhooks_sent"] += len(results)

        return summary
