"""GitHub webhook event handlers."""

import logging
from collections.abc import Callable
from typing import Any

import django_rq

from .models import GitHubInstallation, GitHubRepository
from .services.github_sync import GitHubSyncService

logger = logging.getLogger(__name__)

# Type alias for webhook handler functions
WebhookHandler = Callable[[dict[str, Any]], dict[str, Any]]


class GitHubWebhookHandler:
    """Handler for GitHub webhook events."""

    def __init__(self):
        self.handlers: dict[str, WebhookHandler] = {
            "issues": self._handle_issue_event,
            "pull_request": self._handle_pull_request_event,
            "discussion": self._handle_discussion_event,
            "issue_comment": self._handle_issue_comment_event,
            "pull_request_review": self._handle_pr_review_event,
            "pull_request_review_comment": self._handle_pr_review_comment_event,
            "installation": self._handle_installation_event,
            "installation_repositories": self._handle_installation_repos_event,
        }

    def handle_event(self, event_type: str, payload: dict[str, Any]) -> dict[str, Any]:
        """Route and handle a webhook event.

        Args:
            event_type: The X-GitHub-Event header value
            payload: The webhook payload

        Returns:
            Result dictionary with status and any errors
        """
        handler = self.handlers.get(event_type)
        if not handler:
            logger.debug(f"Ignoring unhandled event type: {event_type}")
            return {"status": "ignored", "event_type": event_type}

        try:
            return handler(payload)
        except Exception as e:
            logger.exception(f"Error handling {event_type} webhook: {e}")
            return {"status": "error", "error": str(e)}

    def _get_repository(self, payload: dict[str, Any]) -> GitHubRepository | None:
        """Get the GitHubRepository for a webhook payload."""
        repo_data = payload.get("repository")
        if not repo_data:
            return None

        try:
            return GitHubRepository.objects.select_related("installation", "source__workspace").get(
                repo_id=repo_data["id"], is_archived=False
            )
        except GitHubRepository.DoesNotExist:
            logger.debug(f"Repository {repo_data['full_name']} not connected")
            return None

    def _handle_issue_event(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle issue events (opened, closed, reopened, labeled, etc.)."""
        action = payload.get("action")
        issue = payload.get("issue")

        if not issue:
            return {"status": "ignored", "reason": "no issue data"}

        repo = self._get_repository(payload)
        if not repo:
            return {"status": "ignored", "reason": "repository not connected"}

        # Process the issue update
        sync_service = GitHubSyncService(repo)
        thread = sync_service._upsert_thread_from_issue(issue)

        logger.info(f"Processed issue webhook: {repo.full_name}#{issue['number']} " f"action={action}")

        return {
            "status": "processed",
            "action": action,
            "thread_id": str(thread.pk),
            "issue_number": issue["number"],
        }

    def _handle_pull_request_event(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events (opened, closed, merged, reviewed)."""
        action = payload.get("action")
        pr = payload.get("pull_request")

        if not pr:
            return {"status": "ignored", "reason": "no pull_request data"}

        repo = self._get_repository(payload)
        if not repo:
            return {"status": "ignored", "reason": "repository not connected"}

        # Process the PR update
        sync_service = GitHubSyncService(repo)
        thread = sync_service._upsert_thread_from_pr(pr)

        logger.info(f"Processed PR webhook: {repo.full_name}#{pr['number']} " f"action={action}")

        return {
            "status": "processed",
            "action": action,
            "thread_id": str(thread.pk),
            "pr_number": pr["number"],
        }

    def _handle_discussion_event(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle discussion events (created, answered, commented)."""
        action = payload.get("action")
        discussion = payload.get("discussion")

        if not discussion:
            return {"status": "ignored", "reason": "no discussion data"}

        repo = self._get_repository(payload)
        if not repo:
            return {"status": "ignored", "reason": "repository not connected"}

        # Process the discussion update
        sync_service = GitHubSyncService(repo)
        thread = sync_service._upsert_thread_from_discussion(discussion)

        logger.info(f"Processed discussion webhook: {repo.full_name}#{discussion['number']} " f"action={action}")

        return {
            "status": "processed",
            "action": action,
            "thread_id": str(thread.pk),
            "discussion_number": discussion["number"],
        }

    def _handle_issue_comment_event(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle issue comment events."""
        action = payload.get("action")
        issue = payload.get("issue")
        comment = payload.get("comment")

        if not issue or not comment:
            return {"status": "ignored", "reason": "missing issue or comment data"}

        if action == "deleted":
            # We don't delete comments, just ignore
            return {"status": "ignored", "reason": "comment deleted"}

        repo = self._get_repository(payload)
        if not repo:
            return {"status": "ignored", "reason": "repository not connected"}

        # Determine if this is an issue or PR comment
        is_pr = "pull_request" in issue

        # Get or create the thread first
        sync_service = GitHubSyncService(repo)
        if is_pr:
            # Fetch the full PR data and update
            # For now, just update activity_at on the thread
            external_id = f"pr:{issue['number']}"
        else:
            external_id = f"issue:{issue['number']}"

        from messages.models import Thread

        try:
            thread = Thread.objects.get(
                source=repo.source,
                external_id=external_id,
            )
        except Thread.DoesNotExist:
            # Thread doesn't exist yet, trigger a sync
            queue = django_rq.get_queue("default")
            queue.enqueue(
                "integrations.tasks.sync_github_repository",
                repository_id=str(repo.pk),
                historical_days=90,
            )
            return {"status": "queued_sync", "reason": "thread not found"}

        # Add the comment
        message = sync_service._upsert_message_from_comment(thread, comment)

        # Update thread activity_at
        from django.utils import timezone

        thread.activity_at = timezone.now()
        thread.save(update_fields=["activity_at", "updated_at"])

        logger.info(f"Processed comment webhook: {repo.full_name}#{issue['number']} " f"action={action}")

        return {
            "status": "processed",
            "action": action,
            "thread_id": str(thread.pk),
            "message_id": str(message.pk),
        }

    def _handle_pr_review_event(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request review events."""
        action = payload.get("action")
        pr = payload.get("pull_request")
        review = payload.get("review")

        if not pr or not review:
            return {"status": "ignored", "reason": "missing pr or review data"}

        repo = self._get_repository(payload)
        if not repo:
            return {"status": "ignored", "reason": "repository not connected"}

        # Update review state in metadata
        from messages.models import Thread

        external_id = f"pr:{pr['number']}"
        try:
            thread = Thread.objects.get(
                source=repo.source,
                external_id=external_id,
            )
            if thread.metadata.get("github"):
                thread.metadata["github"]["review_state"] = review.get("state")
                thread.save(update_fields=["metadata", "updated_at"])
        except Thread.DoesNotExist:
            pass

        logger.info(
            f"Processed PR review webhook: {repo.full_name}#{pr['number']} "
            f"action={action} state={review.get('state')}"
        )

        return {"status": "processed", "action": action}

    def _handle_pr_review_comment_event(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request review comment events."""
        action = payload.get("action")
        pr = payload.get("pull_request")
        comment = payload.get("comment")

        if not pr or not comment:
            return {"status": "ignored", "reason": "missing pr or comment data"}

        if action == "deleted":
            return {"status": "ignored", "reason": "comment deleted"}

        repo = self._get_repository(payload)
        if not repo:
            return {"status": "ignored", "reason": "repository not connected"}

        # Add the review comment to the thread
        from messages.models import Thread

        external_id = f"pr:{pr['number']}"
        try:
            thread = Thread.objects.get(
                source=repo.source,
                external_id=external_id,
            )
        except Thread.DoesNotExist:
            return {"status": "ignored", "reason": "thread not found"}

        sync_service = GitHubSyncService(repo)
        message = sync_service._upsert_message_from_comment(thread, comment)

        # Update thread activity_at
        from django.utils import timezone

        thread.activity_at = timezone.now()
        thread.save(update_fields=["activity_at", "updated_at"])

        logger.info(f"Processed PR review comment webhook: {repo.full_name}#{pr['number']} " f"action={action}")

        return {
            "status": "processed",
            "action": action,
            "message_id": str(message.pk),
        }

    def _handle_installation_event(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle installation events (created, deleted, suspended, unsuspended)."""
        action = payload.get("action")
        installation = payload.get("installation")

        if not installation:
            return {"status": "ignored", "reason": "no installation data"}

        installation_id = installation["id"]

        if action == "deleted":
            # Mark installation as deleted
            try:
                inst = GitHubInstallation.objects.get(installation_id=installation_id)
                # Archive all repositories
                inst.repositories.update(is_archived=True, sync_status="disabled")
                inst.delete()
                logger.info(f"Deleted installation {installation_id}")
            except GitHubInstallation.DoesNotExist:
                pass
            return {"status": "processed", "action": action}

        if action in ("suspended", "unsuspended"):
            from django.utils import timezone

            try:
                inst = GitHubInstallation.objects.get(installation_id=installation_id)
                if action == "suspended":
                    inst.suspended_at = timezone.now()
                else:
                    inst.suspended_at = None
                inst.save(update_fields=["suspended_at", "updated_at"])
                logger.info(f"Installation {installation_id} {action}")
            except GitHubInstallation.DoesNotExist:
                pass
            return {"status": "processed", "action": action}

        return {"status": "ignored", "action": action}

    def _handle_installation_repos_event(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle installation_repositories events (added, removed)."""
        action = payload.get("action")
        installation = payload.get("installation")
        repos_added = payload.get("repositories_added", [])
        repos_removed = payload.get("repositories_removed", [])

        if not installation:
            return {"status": "ignored", "reason": "no installation data"}

        installation_id = installation["id"]

        try:
            inst = GitHubInstallation.objects.get(installation_id=installation_id)
        except GitHubInstallation.DoesNotExist:
            return {"status": "ignored", "reason": "installation not found"}

        # Handle removed repositories
        for repo_data in repos_removed:
            try:
                repo = GitHubRepository.objects.get(
                    installation=inst,
                    repo_id=repo_data["id"],
                )
                repo.is_archived = True
                repo.sync_status = "disabled"
                repo.save(update_fields=["is_archived", "sync_status", "updated_at"])
                logger.info(f"Archived repository {repo_data['full_name']}")
            except GitHubRepository.DoesNotExist:
                pass

        # Note: We don't auto-connect added repositories
        # The user must explicitly select them

        return {
            "status": "processed",
            "action": action,
            "added": len(repos_added),
            "removed": len(repos_removed),
        }


# Global handler instance
webhook_handler = GitHubWebhookHandler()


def process_webhook(event_type: str, payload: dict[str, Any]) -> dict[str, Any]:
    """Process a GitHub webhook event.

    This is the main entry point for webhook processing.

    Args:
        event_type: The X-GitHub-Event header value
        payload: The webhook payload

    Returns:
        Result dictionary
    """
    return webhook_handler.handle_event(event_type, payload)
