from __future__ import annotations

import json
import os
import re
import shutil
import subprocess
from pathlib import Path

from skills._shared.python.skill_testing import assert_skill_contract


REQUIRED_MIN_VERSION = (0, 10, 2)


def skill_root() -> Path:
    return Path(__file__).resolve().parents[1]


def skill_text() -> str:
    return (skill_root() / "SKILL.md").read_text(encoding="utf-8")


def parse_semver(value: str) -> tuple[int, int, int]:
    match = re.search(r"(\d+)\.(\d+)\.(\d+)", value)
    if not match:
        raise AssertionError(f"unable to parse semver from: {value!r}")
    return int(match.group(1)), int(match.group(2)), int(match.group(3))


def write_entry(
    path: Path,
    *,
    status: str = "open",
    next_action: str = "Decide routing.",
    raw_record: str = "out/fixture/skill-usage.record.json",
) -> None:
    path.write_text(
        f"""# Fixture Gap

## Status

- Status: {status}
- First observed: 2026-05-19
- Area: tests
- Severity: low

## Signal

The fixture records a deterministic smoke-test gap.

## Evidence

- Raw record: `{raw_record}`
- Summary: smoke fixture

## Impact

It protects the heuristic-inbox workflow contract.

## Current Workaround

None.

## Promotion Criteria

The workflow contract is covered by tests.

## Next Action

{next_action}
""",
        encoding="utf-8",
    )


def require_heuristic_inbox() -> None:
    if shutil.which("heuristic-inbox") is None:
        import pytest

        pytest.xfail("heuristic-inbox not installed; install nils-cli >= 0.10.2")

    proc = subprocess.run(
        ["heuristic-inbox", "--version"],
        text=True,
        capture_output=True,
        timeout=10,
    )
    assert proc.returncode == 0, proc.stderr
    version = parse_semver(proc.stdout.strip() or proc.stderr.strip())
    assert version >= REQUIRED_MIN_VERSION, (
        f"heuristic-inbox version {version} < required {REQUIRED_MIN_VERSION}"
    )


def test_workflows_heuristic_system_heuristic_error_inbox_contract() -> None:
    assert_skill_contract(skill_root())


def test_workflows_heuristic_system_heuristic_error_inbox_documents_command_surface() -> None:
    text = skill_text()

    expected = [
        "heuristic-inbox list",
        "heuristic-inbox verify <case-folder|ENTRY.md|RECORD.md>",
        "heuristic-inbox new --from-skill-usage <record-dir> --slug <slug>",
        "heuristic-inbox set-status <case-folder|ENTRY.md> --status open|promoted|wontfix",
        "heuristic-inbox ingest-evidence <case-folder|ENTRY.md> --from <path>",
        "heuristic-inbox archive <case-folder|ENTRY.md>",
    ]
    for command in expected:
        assert command in text, f"missing command surface entry: {command}"


def test_workflows_heuristic_system_heuristic_error_inbox_documents_release_boundary() -> None:
    text = skill_text()

    expected = [
        "nils-cli\n  0.10.2 or newer",
        "Homebrew nils-cli 0.10.2",
        "heuristic-inbox --version",
        "brew tap sympoies/tap && brew install nils-cli",
        "Automatic execution logs for each write operation",
    ]
    for needle in expected:
        assert needle in text, f"missing release-boundary phrase: {needle!r}"


def test_workflows_heuristic_system_heuristic_error_inbox_documents_guardrails() -> None:
    text = skill_text()

    expected = [
        "skill body owns judgment",
        "Deterministic operations route through",
        "redaction guardrail",
        "Bare `git mv` on a single file is discouraged",
        "`archived` lifecycle status",
    ]
    for needle in expected:
        assert needle in text, f"missing guardrail phrase: {needle!r}"


def test_workflows_heuristic_system_heuristic_error_inbox_documents_hand_author_template() -> None:
    text = skill_text()

    expected = [
        "hand-author",
        "## Status",
        "## Signal",
        "## Evidence",
        "## Impact",
        "## Current Workaround",
        "## Promotion Criteria",
        "## Next Action",
        "heuristic-inbox verify heuristic-system/error-inbox/<slug>/ --strict",
    ]
    for needle in expected:
        assert needle in text, f"missing hand-author template phrase: {needle!r}"


def test_workflows_heuristic_system_heuristic_error_inbox_cli_available_on_path() -> None:
    require_heuristic_inbox()


def test_workflows_heuristic_system_heuristic_error_inbox_cli_help_lists_subcommands() -> None:
    require_heuristic_inbox()

    proc = subprocess.run(
        ["heuristic-inbox", "--help"],
        text=True,
        capture_output=True,
        timeout=10,
    )
    assert proc.returncode == 0, proc.stderr
    for subcommand in ("list", "verify", "new", "set-status", "ingest-evidence", "archive"):
        assert subcommand in proc.stdout, f"--help missing subcommand: {subcommand}"


def test_workflows_heuristic_system_heuristic_error_inbox_strict_verify_rejects_body_home_path(
    tmp_path: Path,
) -> None:
    require_heuristic_inbox()

    case_dir = tmp_path / "heuristic-system" / "error-inbox" / "fixture-gap"
    case_dir.mkdir(parents=True)
    (case_dir / "evidence").mkdir()
    raw_record = f"{Path.home()}/out/fixture/skill-usage.record.json"
    write_entry(case_dir / "ENTRY.md", raw_record=raw_record)

    proc = subprocess.run(
        ["heuristic-inbox", "verify", str(case_dir), "--strict", "--format", "json"],
        text=True,
        capture_output=True,
        timeout=10,
    )

    assert proc.returncode == 1
    payload = json.loads(proc.stdout)
    details = payload["error"]["details"]
    assert details["strict"] is True
    assert any(
        violation["kind"] == "body_absolute_home_path"
        for violation in details["body_violations"]
    )


def test_workflows_heuristic_system_heuristic_error_inbox_cli_auto_logs_write_ops(
    tmp_path: Path,
) -> None:
    require_heuristic_inbox()

    subprocess.run(["git", "init"], cwd=tmp_path, check=True, capture_output=True, text=True)

    agent_home = tmp_path / ".agent-home"
    case_dir = tmp_path / "heuristic-system" / "error-inbox" / "fixture-gap"
    case_dir.mkdir(parents=True)
    (case_dir / "evidence").mkdir()
    write_entry(case_dir / "ENTRY.md")

    env = os.environ.copy()
    env["AGENT_HOME"] = str(agent_home)

    proc = subprocess.run(
        [
            "heuristic-inbox",
            "set-status",
            str(case_dir),
            "--status",
            "promoted",
            "--link",
            "docs/plans/fixture-gap.md",
            "--format",
            "json",
        ],
        cwd=tmp_path,
        env=env,
        text=True,
        capture_output=True,
        timeout=10,
    )

    assert proc.returncode == 0, proc.stderr

    invocation_logs = sorted((agent_home / "out" / "projects").rglob("invocation.json"))
    assert len(invocation_logs) == 1

    log_dir = invocation_logs[0].parent
    assert log_dir.name.endswith("-heuristic-inbox")

    invocation = json.loads(invocation_logs[0].read_text(encoding="utf-8"))
    before = json.loads((log_dir / "before.json").read_text(encoding="utf-8"))
    after = json.loads((log_dir / "after.json").read_text(encoding="utf-8"))

    assert invocation["command"] == "heuristic-inbox set-status"
    assert invocation["exit_code"] == 0
    assert before["targets"][0]["case"]["fields"]["status"] == "open"
    assert after["targets"][0]["case"]["fields"]["status"] == "promoted"
    assert "deterministic smoke-test gap" not in json.dumps(before)
    assert "deterministic smoke-test gap" not in json.dumps(after)
