# Copyright (C) 2026 GoodData Corporation

"""Git helpers for workspace-aware commands."""

from __future__ import annotations

import logging
import subprocess
from collections.abc import Iterable
from pathlib import Path
from urllib.parse import urlparse

logger = logging.getLogger(__name__)


def _run_git_command(workspace_root: Path, args: Iterable[str]) -> list[str]:
    """Run a git command relative to workspace_root and return stripped stdout lines."""
    cmd = ["git", "-C", str(workspace_root), *args]
    try:
        result = subprocess.run(cmd, check=False, capture_output=True, text=True)
    except OSError as exc:
        logger.warning("Failed to execute %s: %s", " ".join(cmd), exc)
        return []

    if result.returncode != 0:
        logger.debug("Git command %s exited with %s: %s", cmd, result.returncode, result.stderr.strip())

    return [line.strip() for line in result.stdout.splitlines() if line.strip()]


def list_changed_files(workspace_root: Path, include_untracked: bool = True) -> list[str]:
    """
    Return changed files using git diff (staged + unstaged) and optionally untracked files.

    Args:
        workspace_root: Repository root path.
        include_untracked: Include untracked files detected via git ls-files.
    """
    changed: set[str] = set()

    # Unstaged changes
    for line in _run_git_command(workspace_root, ["diff", "--name-only"]):
        changed.add(line)

    # Staged changes
    for line in _run_git_command(workspace_root, ["diff", "--name-only", "--cached"]):
        changed.add(line)

    if include_untracked:
        for line in _run_git_command(workspace_root, ["ls-files", "--others", "--exclude-standard"]):
            changed.add(line)

    return sorted(changed)


def list_tracked_files_under(workspace_root: Path, roots: Iterable[str]) -> list[str]:
    """
    Return tracked files under the given repo-relative roots using `git ls-files`.

    This is intentionally repo-agnostic. Callers can treat roots as "targets" (e.g. validate a
    microservice/component even when Git has no changes) by expanding a directory to the tracked
    files under it.
    """
    files: set[str] = set()
    for root in roots:
        root_str = str(root).strip()
        if not root_str:
            continue
        # `--` prevents interpreting paths that start with '-' as flags.
        for line in _run_git_command(workspace_root, ["ls-files", "--", root_str]):
            files.add(line)
    return sorted(files)


def get_tracking_upstream_ref(workspace_root: Path) -> str | None:
    """Return the branch tracking upstream (e.g. 'origin/main') if configured."""
    lines = _run_git_command(workspace_root, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"])
    if not lines:
        return None
    ref = lines[0].strip()
    return ref if ref else None


def list_remotes(workspace_root: Path) -> list[str]:
    """List git remotes."""
    return _run_git_command(workspace_root, ["remote"])


def get_remote_default_branch_ref(workspace_root: Path, remote: str) -> str | None:
    """Best-effort get remote default branch ref like 'origin/main'.

    Uses `refs/remotes/<remote>/HEAD` symbolic ref, which tracks the remote's default branch.
    """
    if not remote:
        return None
    lines = _run_git_command(workspace_root, ["symbolic-ref", "--quiet", f"refs/remotes/{remote}/HEAD"])
    if not lines:
        return None
    raw = lines[0].strip()
    prefix = "refs/remotes/"
    if raw.startswith(prefix):
        raw = raw.removeprefix(prefix)
    return raw if raw else None


def list_changed_files_vs_upstream(
    workspace_root: Path,
    *,
    upstream_ref: str,
    head_ref: str = "HEAD",
) -> list[str]:
    """Return changed files between upstream merge-base and head_ref (no working tree)."""
    if not upstream_ref:
        return []
    return _run_git_command(workspace_root, ["diff", "--name-only", "--merge-base", upstream_ref, head_ref])


def list_commit_messages_vs_upstream(
    workspace_root: Path,
    *,
    upstream_ref: str,
    head_ref: str = "HEAD",
) -> list[tuple[str, str]]:
    """Return commits between upstream..head as (sha, message)."""
    if not upstream_ref:
        return []

    cmd = [
        "git",
        "-C",
        str(workspace_root),
        "log",
        "--format=%H%x00%B%x1e",
        "--reverse",
        f"{upstream_ref}..{head_ref}",
    ]
    try:
        result = subprocess.run(cmd, check=False, capture_output=True, text=True)
    except OSError as exc:
        logger.warning("Failed to execute %s: %s", " ".join(cmd), exc)
        return []
    if result.returncode != 0:
        logger.debug("Git command %s exited with %s: %s", cmd, result.returncode, result.stderr.strip())
        return []

    commits: list[tuple[str, str]] = []
    for record in result.stdout.split("\x1e"):
        cleaned = record.strip()
        if not cleaned or "\x00" not in cleaned:
            continue
        sha, msg = cleaned.split("\x00", 1)
        sha_norm = sha.strip()
        if not sha_norm:
            continue
        commits.append((sha_norm, msg.strip()))
    return commits


def get_repo_slug_from_git_remotes(workspace_root: Path) -> str | None:
    """Best-effort infer GitHub repo slug ('owner/repo') from git remotes.

    Supports common remote URL forms:
    - https://github.com/owner/repo.git
    - git@github.com:owner/repo.git
    """
    lines = _run_git_command(workspace_root, ["remote", "-v"])
    if not lines:
        return None

    # Prefer origin fetch, else any github.com remote.
    candidates: list[str] = []
    for line in lines:
        # Example: origin https://github.com/owner/repo.git (fetch)
        parts = line.split()
        if len(parts) < 3:
            continue
        remote, url, kind = parts[0], parts[1], parts[2]
        if kind != "(fetch)":
            continue
        if remote == "origin":
            candidates.insert(0, url)
        else:
            candidates.append(url)

    for url in candidates:
        slug = parse_github_slug(url)
        if slug:
            return slug
    return None


def parse_github_slug(remote_url: str) -> str | None:
    # SSH form: git@github.com:owner/repo.git
    if remote_url.startswith("git@github.com:"):
        path = remote_url.removeprefix("git@github.com:").removesuffix(".git")
        return path if "/" in path else None

    # HTTPS form: https://github.com/owner/repo.git
    try:
        parsed = urlparse(remote_url)
    except ValueError:
        return None
    if parsed.netloc != "github.com":
        return None
    path = (parsed.path or "").lstrip("/").removesuffix(".git")
    return path if "/" in path else None


# Backwards-compatible alias (internal callers may still use the old name).
_parse_github_slug = parse_github_slug
