"""Colony git operations — branch management and structured commits."""

from __future__ import annotations

import subprocess
from pathlib import Path

import structlog

from fliiq.runtime.colony.models import Proposal

log = structlog.get_logger()


class GitError(RuntimeError):
    """Raised when a git operation fails."""


def _run_git(args: list[str], cwd: Path) -> subprocess.CompletedProcess[str]:
    """Run a git command, raising GitError on failure."""
    result = subprocess.run(
        ["git", *args],
        cwd=cwd,
        capture_output=True,
        text=True,
    )
    if result.returncode != 0:
        raise GitError(f"git {' '.join(args)} failed: {result.stderr.strip()}")
    return result


def get_current_branch(project_root: Path) -> str:
    result = _run_git(["branch", "--show-current"], project_root)
    return result.stdout.strip()


def create_experiment_branch(
    project_root: Path, experiment_id: int, prefix: str = "experiment",
) -> str:
    """Create colony/{prefix}-NNN branch off current branch. Returns branch_name."""
    branch_name = f"colony/{prefix}-{experiment_id:03d}"
    parent = get_current_branch(project_root)

    _run_git(["checkout", "-b", branch_name], project_root)
    log.info("colony.branch_created", branch=branch_name, parent=parent)
    return branch_name


def switch_branch(project_root: Path, branch: str) -> None:
    _run_git(["checkout", branch], project_root)


def commit_files(project_root: Path, files: list[str], message: str) -> str:
    """Stage specific files and commit. Returns commit hash."""
    for f in files:
        _run_git(["add", f], project_root)

    _run_git(["commit", "-m", message], project_root)

    result = _run_git(["rev-parse", "HEAD"], project_root)
    return result.stdout.strip()[:12]


def commit_colony_state(project_root: Path, message: str = "[colony] Update state") -> str:
    """Commit all changes in .colony/ directory."""
    _run_git(["add", ".colony/"], project_root)

    # Check if there's anything staged
    result = subprocess.run(
        ["git", "diff", "--cached", "--quiet"],
        cwd=project_root,
        capture_output=True,
    )
    if result.returncode == 0:
        return ""  # Nothing to commit

    _run_git(["commit", "-m", message], project_root)
    result = _run_git(["rev-parse", "HEAD"], project_root)
    return result.stdout.strip()[:12]


def commit_proposal(project_root: Path, proposal: Proposal, files_changed: list[str]) -> str:
    """Commit an implemented proposal with structured message."""
    # Stage the changed files + .colony state
    all_files = files_changed + [".colony/"]
    for f in all_files:
        _run_git(["add", f], project_root)

    message = (
        f"[colony] {proposal.title}\n\n"
        f"Proposal: {proposal.id}\n"
        f"Type: {proposal.type.value}\n"
        f"Risk: {proposal.risk.value}\n"
        f"Proposer: {proposal.proposer.value}\n\n"
        f"{proposal.description[:500]}"
    )

    _run_git(["commit", "-m", message], project_root)
    result = _run_git(["rev-parse", "HEAD"], project_root)
    return result.stdout.strip()[:12]


def revert_commit(project_root: Path, commit_hash: str) -> None:
    _run_git(["revert", "--no-edit", commit_hash], project_root)


def get_diff_summary(project_root: Path, base: str = "main") -> str:
    """Get a summary of changes since base branch."""
    result = subprocess.run(
        ["git", "diff", "--stat", base],
        cwd=project_root,
        capture_output=True,
        text=True,
    )
    return result.stdout[:3000]


def get_commit_count(project_root: Path, base: str = "main") -> int:
    """Count commits since base branch."""
    result = subprocess.run(
        ["git", "rev-list", "--count", f"{base}..HEAD"],
        cwd=project_root,
        capture_output=True,
        text=True,
    )
    try:
        return int(result.stdout.strip())
    except ValueError:
        return 0
