"""Policy checks as pure functions."""

from __future__ import annotations

import re
from typing import Any


def check_no_apply_no_write(apply_mode: bool, action: str) -> tuple[bool, str]:
    """In dry-run mode, refuse apply-type actions.
    Returns (allowed, message). allowed=False means the action is blocked."""
    forbidden = {"apply_patch", "exec_command"}
    if not apply_mode and action in forbidden:
        return (
            False,
            f"Action '{action}' blocked: --apply not active or CONFIRM APPLY missing.",
        )
    return True, "OK"


def check_diff_only(diff_text: str) -> tuple[bool, str]:
    """Validate that patches.diff looks like a unified diff."""
    if not diff_text or not diff_text.strip():
        return True, "Empty diff (acceptable for audit-only runs)."
    has_minus = bool(re.search(r"^---\s", diff_text, re.MULTILINE))
    has_plus = bool(re.search(r"^\+\+\+\s", diff_text, re.MULTILINE))
    has_hunk = bool(re.search(r"^@@\s", diff_text, re.MULTILINE))
    if has_minus and has_plus and has_hunk:
        return True, "Valid unified diff."
    return (
        False,
        "patches.diff does not look like a unified diff (missing ---/+++/@@ markers).",
    )


def check_evidence_first(evidence_text: str | None) -> tuple[bool, str]:
    """Fix cannot run unless evidence.md exists and is non-empty."""
    if not evidence_text or not evidence_text.strip():
        return False, "evidence.md is missing or empty. Run audit first."
    return True, "Evidence present."


def check_one_tool_per_step(summary_text: str) -> tuple[bool, str]:
    """Validate that each step in the plan has exactly one 'tool:' entry."""
    if not summary_text or not summary_text.strip():
        return True, "No plan to validate."
    steps = re.split(r"^###\s+Step\s+", summary_text, flags=re.MULTILINE)
    if len(steps) <= 1:
        return True, "No step sections found (OK for non-audit)."
    for step_block in steps[1:]:
        tool_matches = re.findall(r"^-\s*tool:\s*", step_block, re.MULTILINE)
        if len(tool_matches) != 1:
            step_name = step_block.split("\n", 1)[0].strip()
            return (
                False,
                f"Step '{step_name}' has {len(tool_matches)} tool entries (expected 1).",
            )
    return True, "One-tool-per-step OK."


def run_policy_gate(
    apply_mode: bool,
    diff_text: str,
    evidence_text: str | None,
    summary_text: str,
) -> dict[str, Any]:
    """Run all policy checks, return structured report."""
    results: dict[str, Any] = {}

    ok, msg = check_diff_only(diff_text)
    results["diff_only"] = {"pass": ok, "message": msg}

    ok, msg = check_evidence_first(evidence_text)
    results["evidence_first"] = {"pass": ok, "message": msg}

    ok, msg = check_one_tool_per_step(summary_text)
    results["one_tool_per_step"] = {"pass": ok, "message": msg}

    ok, msg = check_no_apply_no_write(apply_mode, "apply_patch")
    # In dry-run, blocking apply is the desired behavior, not a gate failure.
    guard_pass = ok if apply_mode else True
    results["no_apply_guard"] = {"pass": guard_pass, "enforced": not ok, "message": msg}

    results["all_pass"] = all(
        v["pass"] for v in results.values() if isinstance(v, dict)
    )
    return results
