"""Git operations for tracking commits and diffs."""

from __future__ import annotations

import subprocess
from pathlib import Path


class GitError(Exception):
    """Error running git command."""

    pass


def run_git(
    *args: str,
    cwd: Path | None = None,
    check: bool = True,
) -> subprocess.CompletedProcess:
    """Run a git command.

    Args:
        *args: Git command arguments.
        cwd: Working directory.
        check: Whether to raise on non-zero exit.

    Returns:
        Completed process with stdout/stderr.

    Raises:
        GitError: If check=True and command fails.
    """
    result = subprocess.run(
        ["git", *args],
        cwd=cwd,
        capture_output=True,
        text=True,
    )

    if check and result.returncode != 0:
        raise GitError(f"git {' '.join(args)} failed: {result.stderr}")

    return result


def get_current_commit(cwd: Path | None = None, short: bool = True) -> str:
    """Get the current HEAD commit SHA.

    Args:
        cwd: Working directory.
        short: If True, return short SHA (7 chars).

    Returns:
        Commit SHA string.
    """
    args = ["rev-parse"]
    if short:
        args.append("--short")
    args.append("HEAD")

    result = run_git(*args, cwd=cwd)
    return result.stdout.strip()


def get_full_commit(short_sha: str, cwd: Path | None = None) -> str | None:
    """Expand a short SHA to full SHA.

    Args:
        short_sha: Short commit SHA.
        cwd: Working directory.

    Returns:
        Full commit SHA, or None if not found.
    """
    result = run_git("rev-parse", short_sha, cwd=cwd, check=False)
    if result.returncode != 0:
        return None
    return result.stdout.strip()


def has_changes_since(
    filepath: str,
    since_commit: str | None,
    cwd: Path | None = None,
) -> bool:
    """Check if a file has changed since a given commit.

    Args:
        filepath: Path to the file (relative to repo root).
        since_commit: Commit SHA to compare against.
        cwd: Working directory.

    Returns:
        True if the file has changes, False otherwise.
    """
    if not since_commit:
        return True  # Never reviewed means "has changes"

    full_commit = get_full_commit(since_commit, cwd=cwd)
    if not full_commit:
        return True  # Can't find commit, assume changes

    result = run_git(
        "diff",
        "--quiet",
        full_commit,
        "HEAD",
        "--",
        filepath,
        cwd=cwd,
        check=False,
    )

    return result.returncode != 0  # Non-zero means changes exist


def get_changed_files(
    base: str = "HEAD~1",
    head: str = "HEAD",
    cwd: Path | None = None,
) -> list[str]:
    """Get list of files changed between two commits.

    Args:
        base: Base commit.
        head: Head commit.
        cwd: Working directory.

    Returns:
        List of changed file paths.
    """
    result = run_git(
        "diff",
        "--name-only",
        f"{base}...{head}",
        cwd=cwd,
        check=False,
    )

    if result.returncode != 0:
        return []

    return [f for f in result.stdout.strip().split("\n") if f]


def get_remote_url(cwd: Path | None = None) -> str | None:
    """Get the origin remote URL.

    Args:
        cwd: Working directory.

    Returns:
        Remote URL or None.
    """
    result = run_git("remote", "get-url", "origin", cwd=cwd, check=False)
    if result.returncode != 0:
        return None
    return result.stdout.strip()


def get_gitlab_project(cwd: Path | None = None) -> str | None:
    """Extract GitLab project path from remote URL.

    Args:
        cwd: Working directory.

    Returns:
        Project path (e.g., "user/project") or None.
    """
    url = get_remote_url(cwd=cwd)
    if not url:
        return None

    # Handle SSH: git@git.ligo.org:user/project.git
    if url.startswith("git@"):
        path = url.split(":")[-1]
    # Handle HTTPS: https://git.ligo.org/user/project.git
    elif "://" in url:
        path = "/".join(url.split("/")[-2:])
    else:
        return None

    return path.removesuffix(".git")


def get_gitlab_host(cwd: Path | None = None) -> str | None:
    """Extract GitLab host from remote URL.

    Args:
        cwd: Working directory.

    Returns:
        Host (e.g., "git.ligo.org") or None.
    """
    url = get_remote_url(cwd=cwd)
    if not url:
        return None

    # Handle SSH: git@git.ligo.org:user/project.git
    if url.startswith("git@"):
        return url.split("@")[1].split(":")[0]
    # Handle HTTPS: https://git.ligo.org/user/project.git
    elif "://" in url:
        return url.split("/")[2]
    else:
        return None


def count_lines(filepath: str, cwd: Path | None = None) -> int:
    """Count the number of lines in a file.

    Args:
        filepath: Path to the file.
        cwd: Working directory.

    Returns:
        Number of lines, or 0 if file doesn't exist.
    """
    full_path = (cwd or Path.cwd()) / filepath
    if not full_path.exists():
        return 0

    with open(full_path) as f:
        return sum(1 for _ in f)
