"""Safe wrapper around git CLI.

Provides read-only git operations: clone, tag listing, checkout,
diff, log. Used for fetching CloudSense source repos and performing
version-to-version diff analysis.
"""

import logging
import subprocess
from pathlib import Path
from typing import Any

logger = logging.getLogger(__name__)


def check_git_installed() -> dict[str, Any]:
    """Check if git is installed and return version info."""
    try:
        result = subprocess.run(
            ["git", "--version"],
            capture_output=True, text=True, timeout=10,
        )
        if result.returncode == 0:
            version = result.stdout.strip()
            return {"installed": True, "version": version}
        return {"installed": False, "version": None}
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return {"installed": False, "version": None}


def check_github_access() -> dict[str, bool | str | None]:
    """Check if GitHub CLI is authenticated."""
    try:
        result = subprocess.run(
            ["gh", "auth", "status"],
            capture_output=True, text=True, timeout=15,
        )
        if result.returncode == 0:
            output = result.stdout + result.stderr
            username = None
            for line in output.splitlines():
                if "Logged in to" in line or "account" in line.lower():
                    parts = line.split()
                    for p in parts:
                        if not p.startswith("-") and p not in (
                            "Logged", "in", "to", "as", "account",
                            "✓", "github.com",
                        ):
                            username = p.strip("()")
                            break
            return {
                "authenticated": True,
                "method": "gh CLI",
                "username": username,
                "message": "GitHub CLI authenticated",
            }
        return {
            "authenticated": False,
            "method": None,
            "username": None,
            "message": (
                "GitHub CLI (gh) is not authenticated. "
                "Run: gh auth login"
            ),
        }
    except FileNotFoundError:
        return {
            "authenticated": False,
            "method": None,
            "username": None,
            "message": (
                "GitHub CLI (gh) is not installed. "
                "Install from https://cli.github.com/ then run: gh auth login"
            ),
        }
    except subprocess.TimeoutExpired:
        return {
            "authenticated": False,
            "method": None,
            "username": None,
            "message": "GitHub CLI auth check timed out.",
        }


class GitCliError(Exception):
    """Raised when a git command fails."""

    def __init__(self, message: str, command: str, exit_code: int, stderr: str):
        super().__init__(message)
        self.command = command
        self.exit_code = exit_code
        self.stderr = stderr


def run_git(
    args: list[str],
    *,
    cwd: Path | str | None = None,
    timeout: int = 300,
) -> str:
    """Run a git command and return stdout.

    Args:
        args: Git arguments (without the 'git' prefix).
        cwd: Working directory.
        timeout: Timeout in seconds.

    Returns:
        Stripped stdout string.

    Raises:
        GitCliError on any failure.
    """
    cmd = ["git"] + args
    logger.info("Running: %s (cwd=%s)", " ".join(cmd), cwd or ".")

    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            cwd=str(cwd) if cwd else None,
            timeout=timeout,
        )
    except subprocess.TimeoutExpired:
        raise GitCliError(
            f"git command timed out after {timeout}s",
            command=" ".join(cmd),
            exit_code=-1,
            stderr="TimeoutExpired",
        )
    except FileNotFoundError:
        raise GitCliError(
            "git is not installed or not on PATH.",
            command=" ".join(cmd),
            exit_code=-1,
            stderr="FileNotFoundError",
        )

    if result.returncode != 0:
        stderr = result.stderr.strip() or result.stdout.strip()
        raise GitCliError(
            f"git command failed (exit {result.returncode}): {stderr}",
            command=" ".join(cmd),
            exit_code=result.returncode,
            stderr=stderr,
        )

    return result.stdout.strip()


def ls_remote_tags(
    url: str, *, timeout: int = 60,
) -> list[str]:
    """List tags from a remote repo without cloning.

    Returns clean tag names (e.g., ['R37', 'v38.0', '38.0.1']).
    """
    output = run_git(
        ["ls-remote", "--tags", url],
        timeout=timeout,
    )
    if not output:
        return []

    tags = []
    for line in output.splitlines():
        ref = line.split("\t", 1)[-1]
        if ref.endswith("^{}"):
            continue
        tag = ref.removeprefix("refs/tags/")
        tags.append(tag)
    return tags


def ls_remote_branches(
    url: str, *, timeout: int = 60,
) -> list[str]:
    """List branches from a remote repo without cloning.

    Returns clean branch names (e.g., ['R37', 'R38', 'main']).
    """
    output = run_git(
        ["ls-remote", "--heads", url],
        timeout=timeout,
    )
    if not output:
        return []

    branches = []
    for line in output.splitlines():
        ref = line.split("\t", 1)[-1]
        branch = ref.removeprefix("refs/heads/")
        branches.append(branch)
    return branches


def github_search_repo(
    org: str,
    query: str,
    *,
    timeout: int = 30,
) -> list[dict[str, str]]:
    """Search GitHub repos in an org by name keyword.

    Tries `gh search repos` first (more reliable), falls back to
    `gh api /search/repositories` if needed.
    Returns list of {name, full_name, url, description}.
    """
    import json as _json

    cmd = [
        "gh", "search", "repos",
        f"--owner={org}",
        "--match=name",
        query,
        "--json", "name,fullName,url,description",
        "--limit", "5",
    ]
    logger.info("GitHub search: %s", " ".join(cmd))

    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout,
        )
    except FileNotFoundError:
        logger.warning("gh CLI not installed -- GitHub search unavailable")
        return []
    except subprocess.TimeoutExpired:
        logger.warning("GitHub search timed out")
        return []

    if result.returncode == 0 and result.stdout.strip():
        try:
            items = _json.loads(result.stdout)
            if isinstance(items, list) and items:
                return [
                    {
                        "name": item.get("name", ""),
                        "full_name": item.get("fullName", ""),
                        "url": item.get("url", ""),
                        "description": item.get("description") or "",
                    }
                    for item in items
                ]
        except _json.JSONDecodeError:
            pass

    # Fallback: gh api with leading slash
    search_query = f"org:{org} {query} in:name"
    cmd_api = [
        "gh", "api", "/search/repositories",
        "-f", f"q={search_query}",
        "-f", "per_page=5",
    ]
    logger.info("GitHub API search fallback: %s", " ".join(cmd_api))

    try:
        result = subprocess.run(
            cmd_api,
            capture_output=True,
            text=True,
            timeout=timeout,
        )
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return []

    if result.returncode != 0:
        logger.warning("GitHub search failed: %s", result.stderr.strip())
        return []

    try:
        data = _json.loads(result.stdout)
    except _json.JSONDecodeError:
        return []

    items = data.get("items", [])
    return [
        {
            "name": item.get("name", ""),
            "full_name": item.get("full_name", ""),
            "url": item.get("html_url", ""),
            "description": item.get("description") or "",
        }
        for item in items
    ]


def clone_shallow(
    url: str,
    dest: Path,
    *,
    branch: str | None = None,
    depth: int = 1,
    timeout: int = 300,
) -> Path:
    """Shallow-clone a repo."""
    args = ["clone", "--depth", str(depth)]
    if branch:
        args.extend(["--branch", branch])
    args.extend([url, str(dest)])
    run_git(args, timeout=timeout)
    return dest


def clone_sparse(
    url: str,
    dest: Path,
    files: list[str],
    *,
    timeout: int = 120,
) -> Path:
    """Clone a repo and check out only specific files (minimal disk use)."""
    run_git(
        ["clone", "--depth", "1", "--filter=blob:none",
         "--no-checkout", url, str(dest)],
        timeout=timeout,
    )
    run_git(
        ["checkout", "HEAD", "--"] + files,
        cwd=dest,
        timeout=30,
    )
    return dest


def list_tags(
    repo_dir: Path, pattern: str | None = None
) -> list[str]:
    """List tags, optionally filtered by glob pattern."""
    args = ["tag", "--list"]
    if pattern:
        args.append(pattern)
    output = run_git(args, cwd=repo_dir)
    if not output:
        return []
    return output.splitlines()


def checkout(repo_dir: Path, ref: str) -> None:
    """Check out a specific ref (tag, branch, commit)."""
    run_git(["checkout", ref], cwd=repo_dir)


def diff_stat(
    repo_dir: Path, from_ref: str, to_ref: str
) -> str:
    """Return diff --stat between two refs."""
    return run_git(["diff", "--stat", from_ref, to_ref], cwd=repo_dir)


def diff_name_status(
    repo_dir: Path, from_ref: str, to_ref: str
) -> list[dict[str, str]]:
    """Return file-level changes (added/modified/deleted) between refs."""
    output = run_git(
        ["diff", "--name-status", from_ref, to_ref],
        cwd=repo_dir,
    )
    changes = []
    for line in output.splitlines():
        parts = line.split("\t", 1)
        if len(parts) == 2:
            changes.append({"status": parts[0], "file": parts[1]})
    return changes
