"""GitHub SARIF upload functionality."""

import base64
import gzip
import json
import re
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Optional

from logging_utils import get_tool_logger

MAX_SARIF_SIZE_BYTES = 10 * 1024 * 1024  # 10 MB limit
GITHUB_NAME_PATTERN = re.compile(r"^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$")
COMMIT_SHA_PATTERN = re.compile(r"^[a-fA-F0-9]{40}$")
BRANCH_NAME_PATTERN = re.compile(r"^[a-zA-Z0-9._/-]+$")


@dataclass
class GitHubUploadResult:
    """Result of a GitHub SARIF upload operation."""

    success: bool
    sarif_id: Optional[str] = None
    processing_url: Optional[str] = None
    security_tab_url: Optional[str] = None
    error: Optional[str] = None
    error_type: Optional[str] = None

    def to_dict(self) -> dict:
        """Convert result to dictionary for JSON response."""
        return {
            "success": self.success,
            "sarif_id": self.sarif_id,
            "processing_url": self.processing_url,
            "security_tab_url": self.security_tab_url,
            "error": self.error,
            "error_type": self.error_type,
        }


def _validate_github_name(name: str, field: str) -> None:
    """Validate GitHub owner or repo name format.

    Args:
        name: The name to validate
        field: Field name for error message (e.g., "owner", "repo")

    Raises:
        ValueError: If name contains invalid characters
    """
    if not name or len(name) > 100:
        raise ValueError(f"Invalid {field}: must be 1-100 characters")
    if not GITHUB_NAME_PATTERN.match(name):
        raise ValueError(
            f"Invalid {field} '{name}': contains invalid characters. "
            "Only alphanumeric, hyphens, underscores, and dots allowed."
        )


def _validate_commit_sha(sha: str) -> None:
    """Validate commit SHA format.

    Args:
        sha: The SHA to validate

    Raises:
        ValueError: If SHA format is invalid
    """
    if not COMMIT_SHA_PATTERN.match(sha):
        raise ValueError(f"Invalid commit SHA format: {sha[:20]}...")


def _validate_branch_name(branch: str) -> None:
    """Validate branch name format.

    Args:
        branch: The branch name to validate

    Raises:
        ValueError: If branch name contains invalid characters
    """
    if not branch or len(branch) > 255:
        raise ValueError("Invalid branch name: must be 1-255 characters")
    if not BRANCH_NAME_PATTERN.match(branch):
        raise ValueError(f"Invalid branch name '{branch}': contains invalid characters")
    if ".." in branch:
        raise ValueError("Invalid branch name: cannot contain '..'")


def _validate_path(path: Path, must_exist: bool = True) -> Path:
    """Validate and resolve a path safely.

    Args:
        path: Path to validate
        must_exist: Whether the path must exist

    Returns:
        Resolved absolute path

    Raises:
        ValueError: If path is invalid or doesn't exist when required
    """
    try:
        resolved = path.resolve()
    except (OSError, RuntimeError) as e:
        raise ValueError(f"Invalid path: {e}") from e

    if must_exist and not resolved.exists():
        raise ValueError(f"Path does not exist: {resolved}")

    return resolved


class GitHubSarifUploader:
    """Uploads SARIF files to GitHub Code Scanning via gh CLI."""

    def __init__(self) -> None:
        self.logger = get_tool_logger("github_upload")

    def _check_gh_installed(self) -> Optional[str]:
        """Check if gh CLI is installed."""
        try:
            result = subprocess.run(
                ["gh", "--version"],
                capture_output=True,
                text=True,
                timeout=5,
            )
            if result.returncode != 0:
                return "GitHub CLI (gh) not installed. Install from https://cli.github.com"
            return None
        except FileNotFoundError:
            return "GitHub CLI (gh) not installed. Install from https://cli.github.com"
        except subprocess.TimeoutExpired:
            return "GitHub CLI (gh) timed out"

    def _check_gh_authenticated(self) -> Optional[str]:
        """Check if gh CLI is authenticated."""
        try:
            result = subprocess.run(
                ["gh", "auth", "status"],
                capture_output=True,
                text=True,
                timeout=10,
            )
            if result.returncode != 0:
                return "Not logged in to GitHub. Run 'gh auth login' first"
            return None
        except subprocess.TimeoutExpired:
            return "GitHub authentication check timed out"

    def get_git_info(self, project_root: Path) -> dict:
        """Get commit_sha, ref, and owner/repo from git.

        Args:
            project_root: Path to the project root directory

        Returns:
            Dictionary with commit_sha, ref, owner, and repo

        Raises:
            ValueError: If not a git repository or cannot determine remote
        """
        resolved_root = _validate_path(project_root, must_exist=True)
        root_str = str(resolved_root)

        try:
            commit_result = subprocess.run(
                ["git", "-C", root_str, "rev-parse", "--", "HEAD"],
                capture_output=True,
                text=True,
                timeout=5,
            )
            if commit_result.returncode != 0:
                raise ValueError("Not a git repository")
            commit_sha = commit_result.stdout.strip()
            _validate_commit_sha(commit_sha)

            branch_result = subprocess.run(
                ["git", "-C", root_str, "rev-parse", "--abbrev-ref", "--", "HEAD"],
                capture_output=True,
                text=True,
                timeout=5,
            )
            if branch_result.returncode != 0:
                raise ValueError("Cannot determine current branch")
            branch = branch_result.stdout.strip()
            _validate_branch_name(branch)
            ref = f"refs/heads/{branch}"

            remote_result = subprocess.run(
                ["git", "-C", root_str, "remote", "get-url", "--", "origin"],
                capture_output=True,
                text=True,
                timeout=5,
            )
            if remote_result.returncode != 0:
                raise ValueError("Cannot determine git remote origin")

            remote_url = remote_result.stdout.strip()
            owner, repo = self._parse_remote_url(remote_url)

            _validate_github_name(owner, "owner")
            _validate_github_name(repo, "repository")

            return {
                "commit_sha": commit_sha,
                "ref": ref,
                "owner": owner,
                "repo": repo,
            }

        except subprocess.TimeoutExpired as e:
            raise ValueError(f"Git command timed out: {e}") from e

    def _parse_remote_url(self, remote_url: str) -> tuple[str, str]:
        """Parse owner and repo from git remote URL.

        Supports:
        - git@github.com:owner/repo.git
        - https://github.com/owner/repo.git
        - https://github.com/owner/repo
        """
        remote_url = remote_url.strip()

        if remote_url.startswith("git@github.com:"):
            path = remote_url[len("git@github.com:") :]
        elif remote_url.startswith("https://github.com/"):
            path = remote_url[len("https://github.com/") :]
        elif remote_url.startswith("git://github.com/"):
            path = remote_url[len("git://github.com/") :]
        else:
            raise ValueError(f"Unsupported remote URL format: {remote_url}")

        if path.endswith(".git"):
            path = path[:-4]

        parts = path.split("/")
        if len(parts) < 2:
            raise ValueError(f"Cannot parse owner/repo from: {remote_url}")

        return parts[0], parts[1]

    def encode_sarif(self, sarif_path: Path) -> str:
        """Gzip compress and base64 encode SARIF content.

        Args:
            sarif_path: Path to the SARIF file

        Returns:
            Base64-encoded gzipped SARIF content

        Raises:
            ValueError: If file is too large
        """
        resolved_path = _validate_path(sarif_path, must_exist=True)

        file_size = resolved_path.stat().st_size
        if file_size > MAX_SARIF_SIZE_BYTES:
            raise ValueError(
                f"SARIF file too large: {file_size / 1024 / 1024:.1f} MB "
                f"(max {MAX_SARIF_SIZE_BYTES / 1024 / 1024:.0f} MB)"
            )

        content = resolved_path.read_bytes()
        compressed = gzip.compress(content)
        return base64.b64encode(compressed).decode("ascii")

    def upload(self, sarif_path: Path, project_root: Path) -> GitHubUploadResult:
        """Upload SARIF to GitHub Code Scanning via gh CLI.

        Args:
            sarif_path: Path to the SARIF file to upload
            project_root: Path to the project root directory

        Returns:
            GitHubUploadResult with success status and relevant URLs/errors
        """
        self.logger.debug("Starting GitHub upload for SARIF file")

        error = self._check_gh_installed()
        if error:
            self.logger.warn(f"gh CLI check failed: {error}")
            return GitHubUploadResult(
                success=False, error=error, error_type="GhNotInstalled"
            )

        error = self._check_gh_authenticated()
        if error:
            self.logger.warn(f"gh auth check failed: {error}")
            return GitHubUploadResult(
                success=False, error=error, error_type="GhNotAuthenticated"
            )

        try:
            git_info = self.get_git_info(project_root)
        except ValueError as e:
            self.logger.warn(f"Git info retrieval failed: {e}")
            return GitHubUploadResult(
                success=False, error=str(e), error_type="GitError"
            )

        try:
            resolved_sarif = _validate_path(sarif_path, must_exist=True)
        except ValueError as e:
            return GitHubUploadResult(
                success=False,
                error=str(e),
                error_type="FileNotFound",
            )

        try:
            encoded_sarif = self.encode_sarif(resolved_sarif)
        except ValueError as e:
            self.logger.warn(f"SARIF validation failed: {e}")
            return GitHubUploadResult(
                success=False,
                error=str(e),
                error_type="ValidationError",
            )
        except Exception as e:
            self.logger.error(f"Failed to encode SARIF: {e}")
            return GitHubUploadResult(
                success=False,
                error=f"Failed to encode SARIF file: {e}",
                error_type="EncodingError",
            )

        owner = git_info["owner"]
        repo = git_info["repo"]
        commit_sha = git_info["commit_sha"]
        ref = git_info["ref"]

        api_endpoint = f"/repos/{owner}/{repo}/code-scanning/sarifs"

        self.logger.info(f"Uploading SARIF to {owner}/{repo}")

        try:
            resolved_root = _validate_path(project_root, must_exist=True)

            result = subprocess.run(
                [
                    "gh",
                    "api",
                    "--method",
                    "POST",
                    "--",
                    api_endpoint,
                    "-f",
                    f"commit_sha={commit_sha}",
                    "-f",
                    f"ref={ref}",
                    "-f",
                    f"sarif={encoded_sarif}",
                ],
                capture_output=True,
                text=True,
                timeout=60,
                cwd=str(resolved_root),
            )

            if result.returncode != 0:
                error_msg = result.stderr.strip() or result.stdout.strip()

                if "403" in error_msg or "permission" in error_msg.lower():
                    return GitHubUploadResult(
                        success=False,
                        error="No permission to upload to this repository. "
                        "Ensure your token has 'security_events' scope.",
                        error_type="PermissionDenied",
                    )

                if "404" in error_msg:
                    return GitHubUploadResult(
                        success=False,
                        error="Repository not found or Code Scanning not enabled. "
                        "Enable GitHub Advanced Security for private repos.",
                        error_type="NotFound",
                    )

                return GitHubUploadResult(
                    success=False,
                    error=f"GitHub API error: {error_msg}",
                    error_type="ApiError",
                )

            response_data = json.loads(result.stdout) if result.stdout.strip() else {}
            sarif_id = response_data.get("id")
            processing_url = response_data.get("url")

            security_tab_url = f"https://github.com/{owner}/{repo}/security/code-scanning"

            self.logger.info(f"SARIF uploaded successfully to {owner}/{repo}")

            return GitHubUploadResult(
                success=True,
                sarif_id=sarif_id,
                processing_url=processing_url,
                security_tab_url=security_tab_url,
            )

        except subprocess.TimeoutExpired:
            return GitHubUploadResult(
                success=False,
                error="GitHub API request timed out",
                error_type="Timeout",
            )
        except json.JSONDecodeError:
            return GitHubUploadResult(
                success=True,
                security_tab_url=f"https://github.com/{owner}/{repo}/security/code-scanning",
            )
        except Exception as e:
            self.logger.error(f"Unexpected error during upload: {type(e).__name__}")
            return GitHubUploadResult(
                success=False,
                error="Unexpected error during upload",
                error_type="UnexpectedError",
            )
