"""Service for looking up git diffs associated with beads task IDs."""

import asyncio
import re
from dataclasses import dataclass
from pathlib import Path

from hangar.validation import validate_task_id


@dataclass
class TaskCommit:
    """A git commit associated with a beads task."""

    hash: str
    short_hash: str
    subject: str
    author: str
    date: str


@dataclass
class TaskDiff:
    """Git diff information for a beads task."""

    task_id: str
    commits: list[TaskCommit]
    diff_stat: str
    full_diff: str


class GitDiffService:
    """Looks up git diffs by beads task ID."""

    def __init__(self, project_path: Path) -> None:
        self.project_path = project_path

    async def _run_git(self, *args: str) -> str:
        """Run a git command and return stdout."""
        proc = await asyncio.create_subprocess_exec(
            "git",
            *args,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
            cwd=self.project_path,
        )
        stdout, stderr = await proc.communicate()

        if proc.returncode != 0:
            error_msg = stderr.decode().strip()
            raise RuntimeError(f"git {' '.join(args)} failed: {error_msg}")

        return stdout.decode()

    async def find_commits_for_task(self, task_id: str) -> list[TaskCommit]:
        """Find all git commits that mention a beads task ID."""
        # Search commit messages for the task ID
        try:
            output = await self._run_git(
                "log",
                "--all",
                f"--grep={task_id}",
                "--format=%H|%h|%s|%an|%ai",
            )
        except RuntimeError:
            return []

        commits = []
        for line in output.strip().split("\n"):
            if not line:
                continue
            parts = line.split("|", 4)
            if len(parts) >= 5:
                commits.append(
                    TaskCommit(
                        hash=parts[0],
                        short_hash=parts[1],
                        subject=parts[2],
                        author=parts[3],
                        date=parts[4],
                    )
                )

        return commits

    async def get_diff_stat(self, commit_hashes: list[str]) -> str:
        """Get a combined diff stat for a list of commits."""
        if not commit_hashes:
            return ""

        stats = []
        for h in commit_hashes:
            try:
                output = await self._run_git("diff-tree", "--no-commit-id", "--stat", "-r", h)
                if output.strip():
                    stats.append(f"# {h[:7]}\n{output.strip()}")
            except RuntimeError:
                continue

        return "\n\n".join(stats)

    async def get_full_diff(self, commit_hashes: list[str], max_lines: int = 500) -> str:
        """Get the combined diff for commits, truncated to max_lines."""
        if not commit_hashes:
            return ""

        diffs = []
        total_lines = 0
        for h in commit_hashes:
            try:
                output = await self._run_git("diff-tree", "--no-commit-id", "-p", "-r", h)
                lines = output.split("\n")
                remaining = max_lines - total_lines
                if remaining <= 0:
                    diffs.append(f"\n... truncated ({len(commit_hashes)} commits total) ...")
                    break
                if len(lines) > remaining:
                    diffs.append("\n".join(lines[:remaining]))
                    diffs.append(f"\n... truncated (showing {remaining}/{len(lines)} lines) ...")
                    total_lines = max_lines
                else:
                    diffs.append(output)
                    total_lines += len(lines)
            except RuntimeError:
                continue

        return "\n".join(diffs)

    async def get_task_diff(self, task_id: str) -> TaskDiff:
        """Get the full diff information for a beads task."""
        validate_task_id(task_id)
        commits = await self.find_commits_for_task(task_id)
        hashes = [c.hash for c in commits]

        diff_stat = await self.get_diff_stat(hashes)
        full_diff = await self.get_full_diff(hashes)

        return TaskDiff(
            task_id=task_id,
            commits=commits,
            diff_stat=diff_stat,
            full_diff=full_diff,
        )
