"""
AIPTX GitHub Key Validator
==========================

Validates GitHub Personal Access Tokens and enumerates permissions.

Supports:
- Classic PATs (ghp_)
- Fine-grained PATs (github_pat_)
- OAuth tokens (gho_)
- User-to-server tokens (ghu_)
- Server-to-server tokens (ghs_)
"""

from __future__ import annotations

import re
import time
import logging
from typing import Any

import httpx

from ..base import (
    APIKeyInfo,
    ValidationResult,
    BaseKeyValidator,
    KeyType,
    RiskLevel,
)

logger = logging.getLogger(__name__)


class GitHubKeyValidator(BaseKeyValidator):
    """
    Validate GitHub tokens and enumerate permissions.

    Uses GitHub REST API to validate tokens and discover:
    - User identity and organizations
    - Repository access
    - OAuth scopes (for classic PATs)
    """

    API_BASE = "https://api.github.com"

    def get_key_type(self) -> KeyType:
        return KeyType.GITHUB

    def get_key_patterns(self) -> dict[str, re.Pattern]:
        return {
            "github_pat_classic": re.compile(r'ghp_[A-Za-z0-9]{36}'),
            "github_pat_fine_grained": re.compile(r'github_pat_[A-Za-z0-9_]{22,}'),
            "github_oauth": re.compile(r'gho_[A-Za-z0-9]{36}'),
            "github_user_server": re.compile(r'ghu_[A-Za-z0-9]{36}'),
            "github_server_server": re.compile(r'ghs_[A-Za-z0-9]{36}'),
            "github_refresh": re.compile(r'ghr_[A-Za-z0-9]{36}'),
        }

    async def validate(self, key_info: APIKeyInfo) -> ValidationResult:
        """
        Validate GitHub token against API.

        Calls GET /user to validate and retrieve identity.
        """
        start_time = time.time()

        try:
            async with httpx.AsyncClient(timeout=self.timeout) as client:
                # Validate token by getting user info
                response = await client.get(
                    f"{self.API_BASE}/user",
                    headers={
                        "Authorization": f"Bearer {key_info.key_value}",
                        "Accept": "application/vnd.github.v3+json",
                        "User-Agent": "AIPTX-KeyValidator/1.0",
                    },
                )

                duration_ms = (time.time() - start_time) * 1000

                if response.status_code == 200:
                    user_data = response.json()

                    # Get OAuth scopes from response headers
                    scopes = self._parse_scopes(response.headers.get("X-OAuth-Scopes", ""))

                    # Build identity info
                    identity_info = {
                        "login": user_data.get("login"),
                        "user_id": user_data.get("id"),
                        "name": user_data.get("name"),
                        "email": user_data.get("email"),
                        "type": user_data.get("type"),
                        "site_admin": user_data.get("site_admin", False),
                        "created_at": user_data.get("created_at"),
                    }

                    # Enumerate additional info
                    repos, orgs, emails = await self._enumerate_access(
                        client, key_info.key_value
                    )

                    # Build permissions list from scopes
                    permissions = self._scopes_to_permissions(scopes)

                    # Build resources list
                    resources = []
                    resources.extend([f"repo:{r}" for r in repos[:20]])
                    resources.extend([f"org:{o}" for o in orgs])

                    # Add emails to identity
                    if emails:
                        identity_info["emails"] = emails

                    # Assess risk
                    result = ValidationResult(
                        key_info=key_info,
                        is_valid=True,
                        validation_method="GET /user",
                        validation_endpoint=f"{self.API_BASE}/user",
                        identity_info=identity_info,
                        scopes=scopes,
                        permissions=permissions,
                        resources_accessible=resources,
                        raw_response=user_data,
                        validated_at=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
                        validation_duration_ms=duration_ms,
                    )

                    # Assess risk and exploitation potential
                    result.risk_level = self._assess_github_risk(scopes, repos, orgs, identity_info)
                    result.exploitation_potential = self._get_exploitation_potential(result)
                    result.attack_vectors = self._get_attack_vectors(scopes, repos, orgs)

                    return result

                elif response.status_code == 401:
                    return self._create_error_result(
                        key_info,
                        error="Invalid or expired token",
                        error_code="401_UNAUTHORIZED",
                        duration_ms=duration_ms,
                    )
                elif response.status_code == 403:
                    return self._create_error_result(
                        key_info,
                        error="Token lacks required permissions",
                        error_code="403_FORBIDDEN",
                        duration_ms=duration_ms,
                    )
                else:
                    return self._create_error_result(
                        key_info,
                        error=f"Unexpected status: {response.status_code}",
                        error_code=str(response.status_code),
                        duration_ms=duration_ms,
                    )

        except httpx.TimeoutException:
            duration_ms = (time.time() - start_time) * 1000
            return self._create_error_result(
                key_info,
                error="Request timeout",
                error_code="TIMEOUT",
                duration_ms=duration_ms,
            )
        except Exception as e:
            duration_ms = (time.time() - start_time) * 1000
            logger.error(f"GitHub validation error: {e}")
            return self._create_error_result(
                key_info,
                error=str(e),
                error_code="ERROR",
                duration_ms=duration_ms,
            )

    async def _enumerate_access(
        self,
        client: httpx.AsyncClient,
        token: str,
    ) -> tuple[list[str], list[str], list[str]]:
        """Enumerate repositories, organizations, and emails."""
        headers = {
            "Authorization": f"Bearer {token}",
            "Accept": "application/vnd.github.v3+json",
            "User-Agent": "AIPTX-KeyValidator/1.0",
        }

        repos = []
        orgs = []
        emails = []

        try:
            # Get repositories (first page only)
            response = await client.get(
                f"{self.API_BASE}/user/repos?per_page=100&sort=pushed",
                headers=headers,
            )
            if response.status_code == 200:
                repos = [r["full_name"] for r in response.json()]

            # Get organizations
            response = await client.get(
                f"{self.API_BASE}/user/orgs",
                headers=headers,
            )
            if response.status_code == 200:
                orgs = [o["login"] for o in response.json()]

            # Get emails
            response = await client.get(
                f"{self.API_BASE}/user/emails",
                headers=headers,
            )
            if response.status_code == 200:
                emails = [e["email"] for e in response.json() if e.get("verified")]

        except Exception as e:
            logger.debug(f"GitHub enumeration error: {e}")

        return repos, orgs, emails

    def _parse_scopes(self, scope_header: str) -> list[str]:
        """Parse OAuth scopes from header."""
        if not scope_header:
            return []
        return [s.strip() for s in scope_header.split(",") if s.strip()]

    def _scopes_to_permissions(self, scopes: list[str]) -> list[str]:
        """Convert GitHub scopes to permission descriptions."""
        scope_descriptions = {
            "repo": "Full repository access (public and private)",
            "repo:status": "Access commit status",
            "repo_deployment": "Access deployment status",
            "public_repo": "Access public repositories",
            "repo:invite": "Access repository invitations",
            "security_events": "Read/write security events",
            "admin:repo_hook": "Full control of repository hooks",
            "write:repo_hook": "Write repository hooks",
            "read:repo_hook": "Read repository hooks",
            "admin:org": "Full control of orgs and teams",
            "write:org": "Read and write org membership",
            "read:org": "Read org membership",
            "admin:public_key": "Full control of user public keys",
            "write:public_key": "Write user public keys",
            "read:public_key": "Read user public keys",
            "admin:org_hook": "Full control of org hooks",
            "gist": "Create gists",
            "notifications": "Access notifications",
            "user": "Read/write user profile data",
            "read:user": "Read user profile data",
            "user:email": "Access user email addresses",
            "user:follow": "Follow and unfollow users",
            "delete_repo": "Delete repositories",
            "write:discussion": "Read and write discussions",
            "read:discussion": "Read discussions",
            "admin:enterprise": "Full control of enterprise",
            "workflow": "Update GitHub Action workflow files",
            "admin:gpg_key": "Full control of GPG keys",
            "write:gpg_key": "Write GPG keys",
            "read:gpg_key": "Read GPG keys",
            "codespace": "Full control of codespaces",
            "copilot": "Access Copilot",
            "project": "Full control of projects",
        }

        permissions = []
        for scope in scopes:
            desc = scope_descriptions.get(scope, f"{scope} scope")
            permissions.append(desc)

        return permissions

    def _assess_github_risk(
        self,
        scopes: list[str],
        repos: list[str],
        orgs: list[str],
        identity: dict,
    ) -> RiskLevel:
        """Assess risk level based on token capabilities."""
        # Critical: admin scopes or site admin
        critical_scopes = {"admin:org", "admin:enterprise", "delete_repo", "admin:repo_hook"}
        if any(s in scopes for s in critical_scopes) or identity.get("site_admin"):
            return RiskLevel.CRITICAL

        # High: full repo access or workflow modification
        high_scopes = {"repo", "workflow", "write:org"}
        if any(s in scopes for s in high_scopes):
            return RiskLevel.HIGH

        # Medium: write access or org membership
        if scopes or orgs or len(repos) > 10:
            return RiskLevel.MEDIUM

        # Low: read-only or limited access
        if repos:
            return RiskLevel.LOW

        return RiskLevel.INFO

    def _get_exploitation_potential(self, result: ValidationResult) -> str:
        """Get exploitation potential description."""
        if result.risk_level == RiskLevel.CRITICAL:
            return "Critical - Can delete repos, modify org settings, or is site admin"
        elif result.risk_level == RiskLevel.HIGH:
            return "High - Full repository access including private repos and workflows"
        elif result.risk_level == RiskLevel.MEDIUM:
            return "Medium - Access to repositories and organization information"
        elif result.risk_level == RiskLevel.LOW:
            return "Low - Limited read access to repositories"
        return "Minimal - Token has very limited permissions"

    def _get_attack_vectors(
        self,
        scopes: list[str],
        repos: list[str],
        orgs: list[str],
    ) -> list[str]:
        """Identify potential attack vectors."""
        vectors = []

        if "repo" in scopes or "public_repo" in scopes:
            vectors.append("Clone private repositories")
            vectors.append("Push malicious code to repositories")

        if "workflow" in scopes:
            vectors.append("Modify GitHub Actions to inject backdoors")
            vectors.append("Exfiltrate secrets via workflow runs")

        if any("admin" in s for s in scopes):
            vectors.append("Modify organization/repository settings")
            vectors.append("Add malicious webhooks")

        if "delete_repo" in scopes:
            vectors.append("Delete repositories (destructive)")

        if orgs:
            vectors.append("Enumerate organization members and repos")

        if "user:email" in scopes:
            vectors.append("Access user email addresses for phishing")

        return vectors
