"""Cached git status probing for worktree-bound claims.

The state endpoint uses this helper for active claims with a bound
worktree. Probing must never make `smriti state` fail: stale, broken, or
missing worktrees return None and the claim still renders normally.
"""
from __future__ import annotations

import logging
import subprocess
import time
from typing import Any

logger = logging.getLogger(__name__)

PROBE_TIMEOUT_SECONDS = 3
PROBE_CACHE_TTL_SECONDS = 60
_PROBE_CACHE: dict[str, tuple[float, dict[str, Any] | None]] = {}


def clear_probe_cache() -> None:
    """Clear the in-process probe cache. Used by tests."""
    _PROBE_CACHE.clear()


def _run_git(path: str, args: list[str]) -> subprocess.CompletedProcess[str]:
    return subprocess.run(
        ["git", "-C", path, *args],
        capture_output=True,
        text=True,
        check=False,
        timeout=PROBE_TIMEOUT_SECONDS,
    )


def _parse_dirty_paths(porcelain_output: str, limit: int = 3) -> list[str]:
    """Parse `git status --porcelain` output into the first dirty paths."""
    paths: list[str] = []
    for line in porcelain_output.splitlines():
        if not line.strip():
            continue
        if len(line) >= 3:
            paths.append(line[3:].strip())
        if len(paths) >= limit:
            break
    return paths


def _probe_worktree(worktree_id: str, path: str, branch: str) -> dict[str, Any] | None:
    """Return git drift information for a worktree, or None on any error.

    The cache is keyed by worktree_id so repeated `smriti state` calls do
    not shell out on every request. Failures are cached too for the same
    TTL to avoid repeatedly probing broken paths.
    """
    now = time.time()
    cached = _PROBE_CACHE.get(worktree_id)
    if cached and now - cached[0] < PROBE_CACHE_TTL_SECONDS:
        return cached[1]

    result = _probe_worktree_uncached(worktree_id, path, branch)
    _PROBE_CACHE[worktree_id] = (now, result)
    return result


def _probe_worktree_uncached(
    worktree_id: str,
    path: str,
    branch: str,
) -> dict[str, Any] | None:
    try:
        status = _run_git(path, ["status", "--porcelain"])
        if status.returncode != 0:
            _log_probe_failure(worktree_id, "status", status)
            return None
        dirty_files = len([line for line in status.stdout.splitlines() if line.strip()])
        dirty_paths = _parse_dirty_paths(status.stdout)

        counts = _run_git(path, ["rev-list", "--left-right", "--count", "HEAD...origin/main"])
        if counts.returncode != 0:
            _log_probe_failure(worktree_id, "rev-list", counts)
            return None
        parts = counts.stdout.strip().split()
        if len(parts) != 2:
            logger.warning(
                "Malformed worktree ahead/behind output for %s: %r",
                worktree_id,
                counts.stdout,
            )
            return None
        ahead, behind = int(parts[0]), int(parts[1])

        last = _run_git(path, ["log", "-1", "--format=%h %ar"])
        if last.returncode != 0:
            _log_probe_failure(worktree_id, "log", last)
            return None
        last_parts = last.stdout.strip().split(maxsplit=1)
        if len(last_parts) != 2:
            logger.warning(
                "Malformed worktree last-commit output for %s: %r",
                worktree_id,
                last.stdout,
            )
            return None

        return {
            "id": worktree_id,
            "path": path,
            "branch": branch,
            "dirty_files": dirty_files,
            "dirty_paths": dirty_paths,
            "ahead": ahead,
            "behind": behind,
            "last_commit_sha": last_parts[0],
            "last_commit_relative": last_parts[1],
        }
    except (FileNotFoundError, subprocess.TimeoutExpired, ValueError) as exc:
        logger.warning("Worktree probe failed for %s: %s", worktree_id, exc)
        return None


def _log_probe_failure(
    worktree_id: str,
    command: str,
    result: subprocess.CompletedProcess[str],
) -> None:
    detail = (result.stderr or result.stdout or "").strip()
    logger.warning(
        "Worktree probe command %s failed for %s: %s",
        command,
        worktree_id,
        detail or f"exit {result.returncode}",
    )
