"""Core branch analysis logic. No CLI dependencies."""

from __future__ import annotations

import contextlib
import json
import subprocess
from dataclasses import dataclass, field
from datetime import UTC, datetime

PROTECTED_BRANCHES = ["main", "master", "develop"]


def _run_git(*args: str) -> str:
    """Run a git command and return stripped stdout."""
    result = subprocess.run(
        ["git", *args],
        capture_output=True,
        text=True,
        check=True,
    )
    return result.stdout.strip()


@dataclass
class BranchInfo:
    name: str
    last_commit_date: datetime
    is_merged: bool = False
    is_remote: bool = False
    is_current: bool = False

    @property
    def age_days(self) -> int:
        delta = datetime.now(tz=UTC) - self.last_commit_date
        return delta.days

    @property
    def status(self) -> str:
        if self.is_merged:
            return "merged"
        if self.age_days > 90:
            return "stale"
        return "active"


@dataclass
class PruneResult:
    branches: list[BranchInfo] = field(default_factory=list)
    protected: list[str] = field(default_factory=lambda: list(PROTECTED_BRANCHES))

    def to_dict(self) -> dict:
        return {
            "branches": [
                {
                    "name": b.name,
                    "last_commit_date": b.last_commit_date.isoformat(),
                    "age_days": b.age_days,
                    "is_merged": b.is_merged,
                    "is_remote": b.is_remote,
                    "is_current": b.is_current,
                    "status": b.status,
                }
                for b in self.branches
            ],
            "protected": self.protected,
            "total": len(self.branches),
        }

    def to_json(self) -> str:
        return json.dumps(self.to_dict(), indent=2)


@dataclass
class DeleteResult:
    branch: str
    success: bool
    error: str | None = None


def get_current_branch() -> str:
    """Return the name of the current branch."""
    return _run_git("rev-parse", "--abbrev-ref", "HEAD")


def get_branches(include_remote: bool = False) -> list[BranchInfo]:
    """List all branches with their last commit date."""
    current = get_current_branch()
    merged_into_main: set[str] = set()
    with contextlib.suppress(subprocess.CalledProcessError):
        merged_into_main = set(get_merged_branches("main"))

    branches: list[BranchInfo] = []

    # Local branches
    raw = _run_git(
        "for-each-ref",
        "--format=%(refname:short)\t%(committerdate:iso-strict)",
        "refs/heads/",
    )
    for line in raw.splitlines():
        if not line.strip():
            continue
        parts = line.split("\t", 1)
        if len(parts) != 2:
            continue
        name, date_str = parts
        commit_date = datetime.fromisoformat(date_str)
        branches.append(
            BranchInfo(
                name=name,
                last_commit_date=commit_date,
                is_merged=name in merged_into_main,
                is_remote=False,
                is_current=name == current,
            )
        )

    if include_remote:
        raw = _run_git(
            "for-each-ref",
            "--format=%(refname:short)\t%(committerdate:iso-strict)",
            "refs/remotes/",
        )
        for line in raw.splitlines():
            if not line.strip():
                continue
            parts = line.split("\t", 1)
            if len(parts) != 2:
                continue
            name, date_str = parts
            if name.endswith("/HEAD"):
                continue
            commit_date = datetime.fromisoformat(date_str)
            # Strip origin/ prefix for display
            short_name = name.split("/", 1)[1] if "/" in name else name
            branches.append(
                BranchInfo(
                    name=name,
                    last_commit_date=commit_date,
                    is_merged=short_name in merged_into_main,
                    is_remote=True,
                    is_current=False,
                )
            )

    return branches


def get_merged_branches(target: str = "main") -> list[str]:
    """Return branch names that have been merged into target."""
    raw = _run_git("branch", "--merged", target, "--format=%(refname:short)")
    return [
        name.strip()
        for name in raw.splitlines()
        if name.strip() and name.strip() != target
    ]


def find_stale_branches(
    days: int = 90,
    include_remote: bool = False,
) -> list[BranchInfo]:
    """Return branches with no commits in the last N days."""
    all_branches = get_branches(include_remote=include_remote)
    return [b for b in all_branches if b.age_days > days]


def find_pruneable_branches(
    days: int = 90,
    merged_only: bool = False,
    target: str = "main",
    include_remote: bool = False,
) -> PruneResult:
    """Find branches eligible for pruning.

    This is the main entry point for branch analysis.
    Protected branches and the current branch are always excluded.
    """
    current = get_current_branch()
    all_branches = get_branches(include_remote=include_remote)

    # Re-check merged status against the specified target
    if target != "main":
        try:
            merged_names = set(get_merged_branches(target))
        except subprocess.CalledProcessError:
            merged_names = set()
        for branch in all_branches:
            short_name = (
                branch.name.split("/", 1)[1]
                if branch.is_remote and "/" in branch.name
                else branch.name
            )
            branch.is_merged = short_name in merged_names

    protected = list(PROTECTED_BRANCHES)
    if current not in protected:
        protected.append(current)

    candidates: list[BranchInfo] = []
    for branch in all_branches:
        # Skip protected and current branches
        short_name = (
            branch.name.split("/", 1)[1]
            if branch.is_remote and "/" in branch.name
            else branch.name
        )
        if short_name in protected or branch.is_current:
            continue

        if merged_only:
            if branch.is_merged:
                candidates.append(branch)
        elif branch.is_merged or branch.age_days > days:
            candidates.append(branch)

    return PruneResult(branches=candidates, protected=protected)


def delete_branches(
    branches: list[str],
    remote: bool = False,
    dry_run: bool = True,
) -> list[DeleteResult]:
    """Delete the given branches.

    If dry_run is True, no branches are actually deleted.
    """
    results: list[DeleteResult] = []

    for branch in branches:
        if dry_run:
            results.append(DeleteResult(branch=branch, success=True))
            continue

        try:
            if remote:
                # remote branch format: origin/branch-name
                remote_name, _, branch_name = branch.partition("/")
                if not branch_name:
                    branch_name = remote_name
                    remote_name = "origin"
                _run_git("push", remote_name, "--delete", branch_name)
            else:
                _run_git("branch", "-D", branch)
            results.append(DeleteResult(branch=branch, success=True))
        except subprocess.CalledProcessError as exc:
            results.append(
                DeleteResult(
                    branch=branch,
                    success=False,
                    error=exc.stderr.strip() if exc.stderr else str(exc),
                )
            )

    return results
