"""Exact per-mutation capability proof verifier for reviewer reports (#405).

A reviewer final report may prove ``review_pr`` capability and then also merge
a PR or delete a remote branch. Merge and branch deletion are separate
mutations that require their own exact capability proof — a nearby capability
must never authorize a different operation. This verifier requires a
mutation-capability table pairing every performed mutation with the exact
task/permission resolved *before* that mutation.
"""

from __future__ import annotations

import re

# Mutations this verifier tracks, with the exact capability tokens that
# authorize each. A row for the mutation must cite one of its own tokens;
# tokens from a different mutation (a "nearby capability") never count.
_REVIEW_TOKENS = ("review_pr", "gitea.pr.review", "gitea.pr.approve",
                  "gitea.pr.request_changes", "request_changes_pr", "approve_pr")
_MERGE_TOKENS = ("merge_pr", "gitea.pr.merge")
_DELETE_TOKENS = ("delete_branch", "gitea.branch.delete")

# Detect that a mutation was actually performed (not merely mentioned as a
# non-goal or skipped).
_MERGE_PERFORMED = re.compile(
    r"(?:gitea_merge_pr\b(?![^\n]*\b(?:not called|skipped|blocked)\b)|"
    r"^\s*[-*]?\s*merge result\s*:\s*merged\b|"
    r"\bpr merged\b|\bmerge commit\s*(?:sha)?\s*[:=]?\s*[0-9a-f]{7,})",
    re.IGNORECASE | re.MULTILINE,
)
_DELETE_PERFORMED = re.compile(
    r"(?:gitea_delete_branch\b(?![^\n]*\b(?:not called|skipped|blocked)\b)|"
    r"^\s*[-*]?\s*(?:remote )?branch deleted\s*:|"
    r"\bdeleted (?:the )?(?:remote )?branch\b|"
    r"^\s*[-*]?\s*branch deletion\s*:\s*(?!skipped|none|not)\S)",
    re.IGNORECASE | re.MULTILINE,
)
_REVIEW_PERFORMED = re.compile(
    r"(?:gitea_submit_pr_review\b|gitea_mark_final_review_decision\b|"
    r"^\s*[-*]?\s*review (?:decision|verdict|mutation)\s*:\s*"
    r"(?:approved|request[_ ]changes)\b|\breview submitted\b)",
    re.IGNORECASE | re.MULTILINE,
)

# Post-hoc proof: capability resolved *after* the mutation is never valid.
_POST_HOC = re.compile(
    r"capabilit(?:y|ies)\s+(?:resolved|proven|checked)\s+(?:after|post[- ])\s*"
    r"(?:the\s+)?(?:merge|deletion|delete|mutation|review)",
    re.IGNORECASE,
)

# The report must carry an explicit mutation-capability table.
_TABLE_MARKER = re.compile(
    r"mutation[- ]capability(?:\s+table)?|capability[- ]per[- ]mutation",
    re.IGNORECASE,
)


def _tokens_present(text: str, tokens: tuple[str, ...]) -> bool:
    low = text.lower()
    return any(tok.lower() in low for tok in tokens)


def assess_mutation_capability_proof(report_text: str) -> dict:
    """Validate exact per-mutation capability proof in a reviewer report.

    Returns ``{proven, block, reasons, safe_next_action}``. A report that
    performs no mutation beyond an ordinary review passes only when its
    review capability is cited; merge/delete each demand their own exact
    capability row. Fail closed on nearby-capability substitution, a
    missing table, missing rows, or post-hoc proof.
    """
    text = report_text or ""
    reasons: list[str] = []

    merged = bool(_MERGE_PERFORMED.search(text))
    deleted = bool(_DELETE_PERFORMED.search(text))
    reviewed = bool(_REVIEW_PERFORMED.search(text))

    extra_mutation = merged or deleted

    if _POST_HOC.search(text):
        reasons.append(
            "capability proof recorded after the mutation; exact capability "
            "must be resolved before each mutation"
        )

    # A review-only report needs its review capability cited; no table required.
    if reviewed and not _tokens_present(text, _REVIEW_TOKENS):
        reasons.append(
            "review mutation performed without exact review capability proof "
            "(review_pr / gitea.pr.review)"
        )

    if extra_mutation and not _TABLE_MARKER.search(text):
        reasons.append(
            "mutation beyond review performed without a mutation-capability "
            "table (mutation, exact task/capability, result, order-before)"
        )

    if merged:
        if not _tokens_present(text, _MERGE_TOKENS):
            reasons.append(
                "merge performed without exact merge capability proof "
                "(merge_pr / gitea.pr.merge); nearby review_pr does not "
                "authorize merge"
            )

    if deleted:
        if not _tokens_present(text, _DELETE_TOKENS):
            reasons.append(
                "branch deletion performed without exact delete capability "
                "proof (delete_branch / gitea.branch.delete); nearby "
                "merge_pr does not authorize branch deletion"
            )

    proven = not reasons
    return {
        "proven": proven,
        "block": not proven,
        "reasons": reasons,
        "safe_next_action": (
            "proceed"
            if proven
            else "add a mutation-capability table with the exact resolved "
            "task/permission and pre-mutation order for every mutation; "
            "skip any mutation whose exact capability is unproven"
        ),
    }
