import json
import subprocess
import sys
from pathlib import Path
from sys import stderr
from typing import Any

import pygit2

from pygittools.hooks import HookResult, PreCommit

"""
Pre-commit hook: run Ruff lint and format checks with machine-readable JSON on stdout.

Uses ``ruff check --output-format=json`` and
``ruff format --check --preview --output-format=json`` (preview is required for JSON on ``format``).
Parses JSON and prints a short summary to stderr on failure.
"""


def _ruff_base() -> list[str]:
    return [sys.executable, "-m", "ruff"]


def _parse_diagnostic_list(raw: str) -> list[dict[str, Any]]:
    s = raw.strip()
    if not s:
        return []
    data = json.loads(s)
    if isinstance(data, list):
        return [x for x in data if isinstance(x, dict)]
    return []


def _format_location(loc: object) -> str:
    if not isinstance(loc, dict):
        return "?:?"
    row = loc.get("row", "?")
    col = loc.get("column", "?")
    return f"{row}:{col}"


def _summarize_diagnostics(label: str, rows: list[dict[str, Any]]) -> str:
    lines = [f"{label}:"]
    for row in rows:
        fn = row.get("filename", "?")
        msg = row.get("message", row.get("code", "?"))
        loc = row.get("location")
        lines.append(f"  {fn}:{_format_location(loc)}: {msg}")
    return "\n".join(lines)


def _run_ruff_json(
    args: list[str],
    cwd: Path,
) -> tuple[int, list[dict[str, Any]], str]:
    proc = subprocess.run(
        _ruff_base() + args,
        cwd=cwd,
        capture_output=True,
        text=True,
    )
    stderr_extra = proc.stderr or ""
    try:
        rows = _parse_diagnostic_list(proc.stdout)
    except json.JSONDecodeError:
        return (
            1,
            [],
            f"invalid JSON from ruff (stdout):\n{proc.stdout!r}\nstderr:\n{stderr_extra}",
        )
    return proc.returncode, rows, stderr_extra


class PreCommitRuff(PreCommit):
    def __init__(self, repo: pygit2.Repository, paths: list[str] | None = None):
        super().__init__(repo)
        self.paths = paths if paths is not None else ["."]

    def run(self) -> HookResult:
        workdir = self.repo.workdir
        if workdir is None:
            stderr.write("PreCommitRuff: bare repository has no working tree; skipping ruff.\n")
            return HookResult.SUCCESS
        root = Path(workdir)

        rc, rows, extra = _run_ruff_json(["check", "--output-format=json", *self.paths], root)
        if rc != 0:
            stderr.write(_summarize_diagnostics("ruff check", rows) + "\n")
            if extra.strip():
                stderr.write(extra)
            return HookResult.FAILURE

        rc, rows, extra = _run_ruff_json(
            ["format", "--check", "--preview", "--output-format=json", *self.paths],
            root,
        )
        if rc != 0:
            stderr.write(_summarize_diagnostics("ruff format", rows) + "\n")
            if extra.strip():
                stderr.write(extra)
            return HookResult.FAILURE

        return HookResult.SUCCESS