"""
GitHub App Integration

Provides GitHub App authentication, webhook handling, and event processing.

Examples:
    # Create GitHub App
    config = GitHubAppConfig(
        app_id="12345",
        private_key="...",
        webhook_secret="...",
    )
    app = GitHubApp(config)

    # Handle webhook
    handler = WebhookHandler(app)
    event = handler.process_webhook(headers, payload)

    # Get installation token
    token = await app.get_installation_token(installation_id)
"""

import hashlib
import hmac
import json
import time
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Any, Callable, Optional

import httpx


class GitHubEventType(Enum):
    """Types of GitHub webhook events"""

    ISSUE_COMMENT = auto()
    PR_COMMENT = auto()  # Actually pull_request_review_comment or issue_comment on PR
    PULL_REQUEST = auto()
    ISSUES = auto()
    PUSH = auto()
    INSTALLATION = auto()
    UNKNOWN = auto()

    @classmethod
    def from_header(cls, event_type: str) -> "GitHubEventType":
        """Create from X-GitHub-Event header"""
        mapping = {
            "issue_comment": cls.ISSUE_COMMENT,
            "pull_request_review_comment": cls.PR_COMMENT,
            "pull_request": cls.PULL_REQUEST,
            "issues": cls.ISSUES,
            "push": cls.PUSH,
            "installation": cls.INSTALLATION,
        }
        return mapping.get(event_type, cls.UNKNOWN)


@dataclass
class GitHubEvent:
    """A GitHub webhook event"""

    event_type: GitHubEventType
    action: str
    payload: dict[str, Any]
    delivery_id: str
    installation_id: Optional[int] = None
    repository: Optional[str] = None  # "owner/repo" format
    sender: Optional[str] = None

    @classmethod
    def from_webhook(
        cls,
        event_type_header: str,
        payload: dict[str, Any],
        delivery_id: str,
    ) -> "GitHubEvent":
        """Create from webhook data"""
        event_type = GitHubEventType.from_header(event_type_header)
        action = payload.get("action", "")

        # Extract common fields
        installation_id = None
        if "installation" in payload:
            installation_id = payload["installation"].get("id")

        repository = None
        if "repository" in payload:
            repository = payload["repository"].get("full_name")

        sender = None
        if "sender" in payload:
            sender = payload["sender"].get("login")

        return cls(
            event_type=event_type,
            action=action,
            payload=payload,
            delivery_id=delivery_id,
            installation_id=installation_id,
            repository=repository,
            sender=sender,
        )

    @property
    def is_issue_comment(self) -> bool:
        """Check if this is an issue comment event"""
        return self.event_type == GitHubEventType.ISSUE_COMMENT

    @property
    def is_pr_comment(self) -> bool:
        """Check if this is a PR comment event"""
        if self.event_type == GitHubEventType.PR_COMMENT:
            return True
        # issue_comment on a PR
        if self.event_type == GitHubEventType.ISSUE_COMMENT:
            issue = self.payload.get("issue", {})
            return "pull_request" in issue
        return False

    @property
    def comment_body(self) -> Optional[str]:
        """Get comment body if this is a comment event"""
        if "comment" in self.payload:
            return self.payload["comment"].get("body")
        return None

    @property
    def issue_number(self) -> Optional[int]:
        """Get issue/PR number"""
        if "issue" in self.payload:
            return self.payload["issue"].get("number")
        if "pull_request" in self.payload:
            return self.payload["pull_request"].get("number")
        return None


@dataclass
class GitHubAppConfig:
    """Configuration for GitHub App"""

    app_id: str
    private_key: str
    webhook_secret: Optional[str] = None
    client_id: Optional[str] = None
    client_secret: Optional[str] = None
    base_url: str = "https://api.github.com"


@dataclass
class InstallationToken:
    """GitHub App installation token"""

    token: str
    expires_at: str
    permissions: dict[str, str] = field(default_factory=dict)
    repository_selection: str = "all"


class GitHubApp:
    """
    GitHub App client for authentication and API calls

    Handles JWT generation for app authentication and
    installation token management.

    Example:
        app = GitHubApp(config)
        token = await app.get_installation_token(12345)
        # Use token for API calls
    """

    def __init__(self, config: GitHubAppConfig):
        """
        Initialize GitHub App

        Args:
            config: GitHub App configuration
        """
        self.config = config
        self._jwt_cache: Optional[tuple[str, float]] = None
        self._token_cache: dict[int, tuple[InstallationToken, float]] = {}

    def _generate_jwt(self) -> str:
        """
        Generate JWT for app authentication

        Returns:
            JWT string
        """
        # Check cache
        now = time.time()
        if self._jwt_cache:
            jwt, expiry = self._jwt_cache
            if now < expiry - 60:  # 60 second buffer
                return jwt

        try:
            import jwt as pyjwt
        except ImportError:
            raise ImportError(
                "PyJWT is required for GitHub App authentication. "
                "Install it with: pip install PyJWT"
            )

        # JWT payload
        payload = {
            "iat": int(now),
            "exp": int(now) + 600,  # 10 minutes max
            "iss": self.config.app_id,
        }

        # Sign with private key
        token = pyjwt.encode(
            payload,
            self.config.private_key,
            algorithm="RS256",
        )

        self._jwt_cache = (token, now + 600)
        return token

    async def get_installation_token(
        self,
        installation_id: int,
        client: Optional[httpx.AsyncClient] = None,
    ) -> InstallationToken:
        """
        Get installation access token

        Args:
            installation_id: GitHub App installation ID
            client: Optional HTTP client

        Returns:
            InstallationToken
        """
        # Check cache
        now = time.time()
        if installation_id in self._token_cache:
            token, expiry = self._token_cache[installation_id]
            if now < expiry - 300:  # 5 minute buffer
                return token

        jwt = self._generate_jwt()

        url = f"{self.config.base_url}/app/installations/{installation_id}/access_tokens"
        headers = {
            "Authorization": f"Bearer {jwt}",
            "Accept": "application/vnd.github+json",
            "X-GitHub-Api-Version": "2022-11-28",
        }

        close_client = client is None
        if client is None:
            client = httpx.AsyncClient()

        try:
            response = await client.post(url, headers=headers)
            response.raise_for_status()
            data = response.json()

            token = InstallationToken(
                token=data["token"],
                expires_at=data["expires_at"],
                permissions=data.get("permissions", {}),
                repository_selection=data.get("repository_selection", "all"),
            )

            # Cache token (expires in ~1 hour, cache for 55 minutes)
            self._token_cache[installation_id] = (token, now + 3300)

            return token
        finally:
            if close_client:
                await client.aclose()

    async def get_installation_client(
        self,
        installation_id: int,
    ) -> httpx.AsyncClient:
        """
        Get authenticated client for installation

        Args:
            installation_id: GitHub App installation ID

        Returns:
            Authenticated HTTP client
        """
        token = await self.get_installation_token(installation_id)

        return httpx.AsyncClient(
            base_url=self.config.base_url,
            headers={
                "Authorization": f"token {token.token}",
                "Accept": "application/vnd.github+json",
                "X-GitHub-Api-Version": "2022-11-28",
            },
        )

    def verify_webhook_signature(
        self,
        payload: bytes,
        signature: str,
    ) -> bool:
        """
        Verify webhook signature

        Args:
            payload: Raw request body
            signature: X-Hub-Signature-256 header value

        Returns:
            True if signature is valid
        """
        if not self.config.webhook_secret:
            return True  # No secret configured, skip verification

        if not signature or not signature.startswith("sha256="):
            return False

        expected = hmac.new(
            self.config.webhook_secret.encode(),
            payload,
            hashlib.sha256,
        ).hexdigest()

        actual = signature[7:]  # Remove "sha256=" prefix

        return hmac.compare_digest(expected, actual)


class WebhookHandler:
    """
    Handler for GitHub webhook events

    Processes incoming webhooks, verifies signatures,
    and dispatches to registered handlers.

    Example:
        handler = WebhookHandler(app)
        handler.on_issue_comment = lambda event: handle_comment(event)
        event = handler.process_webhook(headers, payload)
    """

    def __init__(self, app: GitHubApp):
        """
        Initialize webhook handler

        Args:
            app: GitHub App instance
        """
        self.app = app

        # Event handlers
        self.on_issue_comment: Optional[Callable[[GitHubEvent], None]] = None
        self.on_pr_comment: Optional[Callable[[GitHubEvent], None]] = None
        self.on_pull_request: Optional[Callable[[GitHubEvent], None]] = None
        self.on_issues: Optional[Callable[[GitHubEvent], None]] = None
        self.on_push: Optional[Callable[[GitHubEvent], None]] = None
        self.on_installation: Optional[Callable[[GitHubEvent], None]] = None
        self.on_any: Optional[Callable[[GitHubEvent], None]] = None

    def process_webhook(
        self,
        headers: dict[str, str],
        payload: bytes | str | dict,
    ) -> Optional[GitHubEvent]:
        """
        Process incoming webhook

        Args:
            headers: Request headers
            payload: Request body (bytes, string, or parsed dict)

        Returns:
            GitHubEvent if successful, None if verification fails
        """
        # Get required headers
        event_type = headers.get("X-GitHub-Event", headers.get("x-github-event", ""))
        delivery_id = headers.get("X-GitHub-Delivery", headers.get("x-github-delivery", ""))
        signature = headers.get("X-Hub-Signature-256", headers.get("x-hub-signature-256", ""))

        # Handle payload
        if isinstance(payload, dict):
            payload_bytes = json.dumps(payload).encode()
            payload_dict = payload
        elif isinstance(payload, str):
            payload_bytes = payload.encode()
            payload_dict = json.loads(payload)
        else:
            payload_bytes = payload
            payload_dict = json.loads(payload.decode())

        # Verify signature
        if not self.app.verify_webhook_signature(payload_bytes, signature):
            return None

        # Create event
        event = GitHubEvent.from_webhook(event_type, payload_dict, delivery_id)

        # Dispatch to handlers
        self._dispatch_event(event)

        return event

    def _dispatch_event(self, event: GitHubEvent) -> None:
        """Dispatch event to appropriate handler"""
        # Call specific handler
        handlers = {
            GitHubEventType.ISSUE_COMMENT: self.on_issue_comment,
            GitHubEventType.PR_COMMENT: self.on_pr_comment,
            GitHubEventType.PULL_REQUEST: self.on_pull_request,
            GitHubEventType.ISSUES: self.on_issues,
            GitHubEventType.PUSH: self.on_push,
            GitHubEventType.INSTALLATION: self.on_installation,
        }

        handler = handlers.get(event.event_type)
        if handler:
            handler(event)

        # Call generic handler
        if self.on_any:
            self.on_any(event)


# App manifest for GitHub App setup
APP_MANIFEST = {
    "name": "groknroll",
    "url": "https://github.com/groknroll/groknroll",
    "hook_attributes": {"url": "https://your-domain.com/webhooks/github"},
    "redirect_url": "https://your-domain.com/oauth/callback",
    "callback_urls": ["https://your-domain.com/oauth/callback"],
    "setup_url": "https://your-domain.com/setup",
    "description": "AI-powered coding assistant that responds to issues and PRs",
    "public": True,
    "default_events": [
        "issue_comment",
        "pull_request_review_comment",
        "pull_request",
        "issues",
    ],
    "default_permissions": {
        "contents": "write",
        "issues": "write",
        "pull_requests": "write",
        "metadata": "read",
    },
}


def create_github_app(
    app_id: str,
    private_key: str,
    webhook_secret: Optional[str] = None,
    **kwargs: Any,
) -> GitHubApp:
    """
    Create GitHub App instance

    Args:
        app_id: GitHub App ID
        private_key: Private key for signing JWTs
        webhook_secret: Secret for verifying webhooks
        **kwargs: Additional config options

    Returns:
        GitHubApp instance
    """
    config = GitHubAppConfig(
        app_id=app_id,
        private_key=private_key,
        webhook_secret=webhook_secret,
        **kwargs,
    )
    return GitHubApp(config)
