"""MCP-native post-merge cleanup proof verifier (#517).

Post-merge cleanup of leases, comments, branches, and worktrees must be
proven through explicit MCP tools (or approved helpers), never raw scripts or
ad hoc git/API commands. Merger/reviewer sessions must hand cleanup to a
reconciler profile with the right capability proof.
"""

from __future__ import annotations

import re
from typing import Any

AUTHORIZED_CLEANUP_TOOLS = frozenset({
    "gitea_cleanup_post_merge_moot_lease",
    "gitea_cleanup_obsolete_reviewer_comment_lease",
    "gitea_reconcile_merged_cleanups",
    "gitea_delete_branch",
    "gitea_cleanup_merged_pr_branch",
    "gitea_audit_worktree_cleanup",
    "gitea_capture_branches_worktree_snapshot",
    "gitea_assess_worktree_cleanup_integrity",
    "gitea_authorize_reconciliation_cleanup_phase",
})

_RAW_BRANCH_DELETE_PATTERNS = (
    re.compile(r"\bgit(?:\s+-C\s+\S+)?\s+branch\s+-[dD]\b[^\n\r]*", re.I),
    re.compile(r"\bgit(?:\s+-C\s+\S+)?\s+push\b[^\n\r]*\s--delete\b[^\n\r]*", re.I),
    re.compile(r"\bgit(?:\s+-C\s+\S+)?\s+push\b[^\n\r]*\s:[^\s`]+", re.I),
)

_RAW_COMMENT_DELETE_PATTERNS = (
    re.compile(
        r"(?:curl|wget|httpie)\b[^\n\r]*\b(?:DELETE|delete)\b[^\n\r]*"
        r"(?:/comments/|issues/\d+/comments)",
        re.I,
    ),
    re.compile(
        r"\bDELETE\b[^\n\r]*/repos/[^\n\r]*/issues/\d+/comments/\d+",
        re.I,
    ),
    re.compile(
        r"\b(?:delete_issue_comment|remove_issue_comment|purge_comments?)\b",
        re.I,
    ),
    re.compile(
        r"\b(?:raw|ad hoc|adhoc)\b[^\n\r]{0,40}\bcomment\b[^\n\r]{0,40}\bdelete",
        re.I,
    ),
)

_CLEANUP_MUTATIONS_RE = re.compile(
    r"^\s*[-*]?\s*cleanup mutations\s*:\s*(.+)$",
    re.IGNORECASE | re.MULTILINE,
)
_GIT_REF_MUTATIONS_RE = re.compile(
    r"^\s*[-*]?\s*git ref mutations\s*:\s*(.+)$",
    re.IGNORECASE | re.MULTILINE,
)
_WORKTREE_MUTATIONS_RE = re.compile(
    r"^\s*[-*]?\s*worktree mutations\s*:\s*(.+)$",
    re.IGNORECASE | re.MULTILINE,
)
_MERGE_MUTATIONS_RE = re.compile(
    r"^\s*[-*]?\s*merge mutations\s*:\s*(.+)$",
    re.IGNORECASE | re.MULTILINE,
)
_NONE_LEDGER_VALUES = frozenset({"", "none", "n/a"})
_MUTATION_LEDGER_PATTERNS = (
    _CLEANUP_MUTATIONS_RE,
    _GIT_REF_MUTATIONS_RE,
    _WORKTREE_MUTATIONS_RE,
)
_RAW_WORKTREE_REMOVE_PATTERNS = (
    re.compile(r"\bgit(?:\s+-C\s+\S+)?\s+worktree\s+remove\b", re.I),
)
_AUTHORIZED_TOOL_RE = re.compile(
    r"\b(" + "|".join(re.escape(t) for t in sorted(AUTHORIZED_CLEANUP_TOOLS)) + r")\b",
    re.IGNORECASE,
)
_RECONCILER_CAPABILITY_RE = re.compile(
    r"(?:reconciler|gitea\.branch\.delete|delete_branch|reconcile_merged_cleanups)",
    re.IGNORECASE,
)
_MERGER_CLEANUP_ROLE_RE = re.compile(
    r"(?:merger|reviewer).{0,80}(?:deleted|removed|cleaned).{0,80}"
    r"(?:branch|worktree|comment|lease)",
    re.IGNORECASE,
)
_HANDOFF_TO_RECONCILER_RE = re.compile(
    r"(?:hand(?:ed)? off|defer(?:red)?|next actor).{0,60}reconciler",
    re.IGNORECASE,
)


def _ledger_field_body(text: str, pattern: re.Pattern[str]) -> str | None:
    match = pattern.search(text)
    if not match:
        return None
    return (match.group(1) or "").strip()


def _is_none_ledger_value(value: str) -> bool:
    return value.strip().lower() in _NONE_LEDGER_VALUES


def scoped_cleanup_mutation_ledger_text(text: str | None) -> str:
    """Return mutation-ledger bodies scoped to cleanup enforcement (#517)."""
    text = text or ""
    parts: list[str] = []
    for pattern in _MUTATION_LEDGER_PATTERNS:
        body = _ledger_field_body(text, pattern)
        if body is not None and not _is_none_ledger_value(body):
            parts.append(body)
    return "\n".join(parts)


def _git_ref_cleanup_claimed(body: str) -> bool:
    return bool(raw_branch_delete_commands(body))


def _worktree_cleanup_claimed(body: str) -> bool:
    for pattern in _RAW_WORKTREE_REMOVE_PATTERNS:
        if pattern.search(body):
            return True
    return bool(
        re.search(
            r"\b(?:removed|deleted)\b[^\n]{0,40}\bworktree\b",
            body,
            re.IGNORECASE,
        )
    )


def _cleanup_claiming_ledger_fields(text: str) -> list[tuple[str, str]]:
    claiming: list[tuple[str, str]] = []
    cleanup_body = _ledger_field_body(text, _CLEANUP_MUTATIONS_RE)
    if cleanup_body is not None and not _is_none_ledger_value(cleanup_body):
        claiming.append(("Cleanup mutations", cleanup_body))

    git_ref_body = _ledger_field_body(text, _GIT_REF_MUTATIONS_RE)
    if git_ref_body is not None and not _is_none_ledger_value(git_ref_body):
        if _git_ref_cleanup_claimed(git_ref_body):
            claiming.append(("Git ref mutations", git_ref_body))

    worktree_body = _ledger_field_body(text, _WORKTREE_MUTATIONS_RE)
    if worktree_body is not None and not _is_none_ledger_value(worktree_body):
        if _worktree_cleanup_claimed(worktree_body):
            claiming.append(("Worktree mutations", worktree_body))
    return claiming


def raw_branch_delete_commands(text: str | None) -> list[str]:
    """Return raw git branch-delete commands cited in *text*."""
    if not text:
        return []
    commands: list[str] = []
    for pattern in _RAW_BRANCH_DELETE_PATTERNS:
        commands.extend(match.group(0).strip("` ") for match in pattern.finditer(text))
    return list(dict.fromkeys(commands))


def raw_comment_delete_commands(text: str | None) -> list[str]:
    """Return raw comment-deletion commands/scripts cited in *text*."""
    if not text:
        return []
    commands: list[str] = []
    for pattern in _RAW_COMMENT_DELETE_PATTERNS:
        commands.extend(match.group(0).strip("` ") for match in pattern.finditer(text))
    return list(dict.fromkeys(commands))


def assess_raw_branch_delete_report(text: str | None) -> dict[str, Any]:
    """Fail closed when mutation ledgers cite raw git branch deletion."""
    commands = raw_branch_delete_commands(scoped_cleanup_mutation_ledger_text(text))
    reasons = [
        (
            "raw git branch deletion bypasses MCP branch.delete cleanup gates: "
            f"{command}"
        )
        for command in commands
    ]
    return {
        "proven": not reasons,
        "block": bool(reasons),
        "commands": commands,
        "reasons": reasons,
        "safe_next_action": (
            "use gitea_delete_branch, gitea_reconcile_merged_cleanups, or another "
            "approved MCP cleanup helper with explicit branch.delete capability"
            if reasons
            else "proceed"
        ),
    }


def assess_raw_comment_delete_report(text: str | None) -> dict[str, Any]:
    """Fail closed when mutation ledgers cite raw comment deletion."""
    commands = raw_comment_delete_commands(scoped_cleanup_mutation_ledger_text(text))
    reasons = [
        (
            "raw comment deletion bypasses MCP lease cleanup gates: "
            f"{command}"
        )
        for command in commands
    ]
    return {
        "proven": not reasons,
        "block": bool(reasons),
        "commands": commands,
        "reasons": reasons,
        "safe_next_action": (
            "use gitea_cleanup_post_merge_moot_lease (append-only release comment) "
            "or hand cleanup to a reconciler session; never delete lease comments"
            if reasons
            else "proceed"
        ),
    }


def assess_merge_cleanup_mutation_separation(text: str | None) -> dict[str, Any]:
    """Require merge mutations and cleanup mutations in separate ledger fields."""
    text = text or ""
    merge_match = _MERGE_MUTATIONS_RE.search(text)
    cleanup_match = _CLEANUP_MUTATIONS_RE.search(text)
    reasons: list[str] = []

    if cleanup_match and not merge_match:
        combined = re.search(
            r"merge.{0,40}cleanup mutations|cleanup.{0,40}merge mutations",
            text,
            re.IGNORECASE,
        )
        if combined:
            reasons.append(
                "merge and cleanup mutations must use separate 'Merge mutations' "
                "and 'Cleanup mutations' ledger fields"
            )

    if merge_match and cleanup_match:
        merge_val = (merge_match.group(1) or "").strip().lower()
        cleanup_val = (cleanup_match.group(1) or "").strip().lower()
        if merge_val == cleanup_val and merge_val not in {"", "none"}:
            reasons.append(
                "merge mutations and cleanup mutations must not duplicate the "
                "same ledger entry"
            )

    return {
        "proven": not reasons,
        "block": bool(reasons),
        "reasons": reasons,
        "safe_next_action": (
            "split merge mutations (gitea_merge_pr) from cleanup mutations "
            "(reconciler MCP tools) in the controller handoff"
            if reasons
            else "proceed"
        ),
    }


def assess_authorized_reconciler_cleanup_path(text: str | None) -> dict[str, Any]:
    """Validate cleanup claims cite authorized MCP tools and reconciler capability."""
    text = text or ""
    claiming = _cleanup_claiming_ledger_fields(text)
    if not claiming:
        return {
            "proven": True,
            "block": False,
            "reasons": [],
            "safe_next_action": "proceed",
        }

    reasons: list[str] = []
    for field_name, body in claiming:
        if not _AUTHORIZED_TOOL_RE.search(body):
            reasons.append(
                f"{field_name} cleanup must name an authorized MCP cleanup tool "
                f"({', '.join(sorted(AUTHORIZED_CLEANUP_TOOLS))})"
            )
    if not _RECONCILER_CAPABILITY_RE.search(text):
        reasons.append(
            "post-merge cleanup requires reconciler capability proof "
            "(reconciler profile, gitea.branch.delete, or reconcile_merged_cleanups)"
        )

    return {
        "proven": not reasons,
        "block": bool(reasons),
        "reasons": reasons,
        "safe_next_action": (
            "hand cleanup to a prgs-reconciler session and cite the exact MCP tool "
            "plus delete_branch/reconcile_merged_cleanups capability proof"
            if reasons
            else "proceed"
        ),
    }


def assess_merger_cleanup_handoff_guidance(text: str | None) -> dict[str, Any]:
    """Merger sessions that performed cleanup must hand off to reconciler."""
    text = text or ""
    if not _MERGER_CLEANUP_ROLE_RE.search(text):
        return {
            "proven": True,
            "block": False,
            "reasons": [],
            "safe_next_action": "proceed",
        }
    if _HANDOFF_TO_RECONCILER_RE.search(text):
        return {
            "proven": True,
            "block": False,
            "reasons": [],
            "safe_next_action": "proceed",
        }
    return {
        "proven": False,
        "block": True,
        "reasons": [
            "merger/reviewer session performed cleanup but did not hand off to "
            "reconciler for MCP-native cleanup"
        ],
        "safe_next_action": (
            "merger sessions must end with cleanup handed to prgs-reconciler; "
            "do not perform ad hoc branch/comment/worktree cleanup as merger"
        ),
    }


def assess_mcp_native_cleanup_proof(report_text: str | None) -> dict[str, Any]:
    """Composite #517 verifier for MCP-native post-merge cleanup proof."""
    text = report_text or ""
    checks = (
        assess_raw_branch_delete_report(text),
        assess_raw_comment_delete_report(text),
        assess_merge_cleanup_mutation_separation(text),
        assess_authorized_reconciler_cleanup_path(text),
        assess_merger_cleanup_handoff_guidance(text),
    )
    reasons: list[str] = []
    safe_next = "proceed"
    for result in checks:
        reasons.extend(result.get("reasons") or [])
        if result.get("block") and result.get("safe_next_action"):
            safe_next = result["safe_next_action"]

    block = bool(reasons)
    return {
        "proven": not block,
        "block": block,
        "reasons": reasons,
        "safe_next_action": safe_next,
        "raw_branch_commands": raw_branch_delete_commands(
            scoped_cleanup_mutation_ledger_text(text)
        ),
        "raw_comment_commands": raw_comment_delete_commands(
            scoped_cleanup_mutation_ledger_text(text)
        ),
    }