"""Canonical post-merge (moot) validation outcome and wording gate (#529).

When a PR is already merged or closed *before* a reviewer submits their
verdict, an ordinary "approve" is misleading: the review never gated the
merge, so a normal active approval overstates controller confidence. The
sanctioned outcome for that case is a **post-merge moot validation** — the
reviewer records what validation found, but classifies it as moot rather than
an active approval.

This module defines the four canonical validation distinctions #529 requires
and enforces the post-merge case:

- ``CLEAN_PASS_OUTCOME`` — clean validation pass on an open, reviewable PR.
- ``BASELINE_ACCEPTED_OUTCOME`` — validation pass with a baseline-proven
  unrelated failure (proof handled by :mod:`premerge_baseline_proof`).
- ``BLOCKED_OUTCOME`` — validation blocked by an unresolved failure.
- ``POST_MERGE_MOOT_OUTCOME`` — the PR was already merged/closed before review
  submission; validation is recorded as post-merge moot, not an active
  approval.

The gate blocks two mistakes:

1. Claiming an *active approval* on a PR that was already merged/closed before
   review submission (must be recorded as post-merge moot validation instead).
2. Claiming post-merge moot validation without proof the PR is actually
   merged/closed (a merged-state field plus a merge commit SHA).

Each outcome maps to a durable ``validation:*`` process-state label so PRs and
issues carry the distinction (#529 acceptance criterion).
"""

from __future__ import annotations

import re
from typing import Any

CLEAN_PASS_OUTCOME = "clean validation pass"
BASELINE_ACCEPTED_OUTCOME = "validation pass with baseline-proven unrelated failure"
BLOCKED_OUTCOME = "validation blocked by unresolved failure"
POST_MERGE_MOOT_OUTCOME = "post-merge moot validation"

# Canonical validation status label a reviewer writes in the report body for the
# post-merge case; kept in sync with validation_status_vocabulary.
POST_MERGE_MOOT_STATUS = "post-merge moot validation"

# Durable process-state labels (registered in issue_workflow_labels).
LABEL_CLEAN_PASS = "validation:clean-pass"
LABEL_BASELINE_ACCEPTED = "validation:baseline-accepted"
LABEL_BLOCKED = "validation:blocked"
LABEL_POST_MERGE_MOOT = "validation:post-merge-moot"

_OUTCOME_TO_LABEL: dict[str, str] = {
    CLEAN_PASS_OUTCOME: LABEL_CLEAN_PASS,
    BASELINE_ACCEPTED_OUTCOME: LABEL_BASELINE_ACCEPTED,
    BLOCKED_OUTCOME: LABEL_BLOCKED,
    POST_MERGE_MOOT_OUTCOME: LABEL_POST_MERGE_MOOT,
}

# The PR was already merged/closed before the review was submitted.
_MERGED_BEFORE_REVIEW_RE = re.compile(
    r"(?:already[- ]merged"
    r"|merged\s+before\s+review"
    r"|closed\s+before\s+review"
    r"|pr\s+(?:is|was)\s+(?:already\s+)?(?:merged|closed)"
    r"|pr\s+state\s*[:=]\s*(?:merged|closed)"
    r"|merged\s*/\s*closed)",
    re.IGNORECASE,
)
# An active approval verdict is being claimed.
_ACTIVE_APPROVAL_RE = re.compile(
    r"(?:review\s+decision\s*[:=]\s*approve"
    r"|submitted\s+['\"]?approve['\"]?"
    r"|active\s+approval"
    r"|approving\s+the\s+pr"
    r"|posting\s+an?\s+approval)",
    re.IGNORECASE,
)
# The sanctioned post-merge moot validation wording.
_POST_MERGE_MOOT_RE = re.compile(
    r"post[- ]merge\s+(?:moot\s+)?validation"
    r"|post[- ]merge\s+moot"
    r"|moot\s+validation",
    re.IGNORECASE,
)
# Proof the PR is genuinely merged/closed.
_MERGED_STATE_PROOF_RE = re.compile(
    r"(?:pr\s+state\s*[:=]\s*(?:merged|closed)"
    r"|merged_at\s*[:=]\s*\S"
    r"|closed_at\s*[:=]\s*\S"
    r"|state\s*[:=]\s*(?:merged|closed))",
    re.IGNORECASE,
)
_MERGE_COMMIT_SHA_RE = re.compile(
    r"merge\s+commit(?:\s+sha)?\s*[:=]\s*[0-9a-f]{7,40}",
    re.IGNORECASE,
)


POST_MERGE_VALIDATION_TEMPLATE = (
    "Validation status: post-merge moot validation\n"
    "PR state: merged\n"
    "Merge commit sha: <40-hex merge commit>\n"
    "Validation finding: <what the validation run showed, for the record>\n"
    "Note: PR merged/closed before review submission; recorded as post-merge "
    "moot validation, not an active approval."
)


def process_state_label(outcome: str) -> str | None:
    """Return the durable ``validation:*`` label for a canonical outcome."""
    return _OUTCOME_TO_LABEL.get(outcome)


def assess_post_merge_validation(
    report_text: str,
    *,
    pr_merged_or_closed: bool | None = None,
) -> dict[str, Any]:
    """Assess post-merge moot validation wording and proof (#529).

    Args:
        report_text: The reviewer final report text.
        pr_merged_or_closed: Optional structured signal that the PR is already
            merged/closed. When ``True`` it forces the merged-before-review
            path even if the report text omits the phrasing; proof fields are
            still required in the report body for durability.

    Returns a dict with ``proven``, ``block``, ``outcome``,
    ``process_state_label``, ``reasons``, ``skipped`` and ``safe_next_action``.
    Only blocks when the report either claims an active approval on a
    merged/closed PR, or claims post-merge moot validation without proof.
    """
    text = report_text or ""

    merged_signal = bool(_MERGED_BEFORE_REVIEW_RE.search(text)) or bool(
        pr_merged_or_closed
    )
    active_approval = bool(_ACTIVE_APPROVAL_RE.search(text))
    moot_wording = bool(_POST_MERGE_MOOT_RE.search(text))

    # Nothing about merged-state or moot validation — not applicable here.
    if not merged_signal and not moot_wording:
        return {
            "proven": True,
            "block": False,
            "outcome": None,
            "process_state_label": None,
            "reasons": [],
            "skipped": True,
            "safe_next_action": "",
        }

    # An active approval on a PR already merged/closed before review, without
    # the sanctioned moot wording, overstates confidence.
    if merged_signal and active_approval and not moot_wording:
        return {
            "proven": False,
            "block": True,
            "outcome": POST_MERGE_MOOT_OUTCOME,
            "process_state_label": LABEL_POST_MERGE_MOOT,
            "reasons": [
                "PR was already merged/closed before review submission; an "
                "active approval overstates confidence — record 'post-merge "
                "moot validation' instead of a normal approval"
            ],
            "skipped": False,
            "safe_next_action": (
                "classify the outcome as post-merge moot validation (not an "
                "active approval); cite PR state merged/closed and the merge "
                "commit SHA. Template:\n" + POST_MERGE_VALIDATION_TEMPLATE
            ),
        }

    # Post-merge moot validation claimed — require merged-state + commit proof.
    if moot_wording:
        reasons: list[str] = []
        if not _MERGED_STATE_PROOF_RE.search(text):
            reasons.append(
                "post-merge moot validation claimed without merged/closed state "
                "proof (state: merged/closed, or merged_at/closed_at)"
            )
        if not _MERGE_COMMIT_SHA_RE.search(text):
            reasons.append(
                "post-merge moot validation claimed without a merge commit SHA"
            )
        if reasons:
            return {
                "proven": False,
                "block": True,
                "outcome": POST_MERGE_MOOT_OUTCOME,
                "process_state_label": LABEL_POST_MERGE_MOOT,
                "reasons": reasons,
                "skipped": False,
                "safe_next_action": (
                    "prove the PR is merged/closed: cite PR state and the merge "
                    "commit SHA. Template:\n" + POST_MERGE_VALIDATION_TEMPLATE
                ),
            }
        return {
            "proven": True,
            "block": False,
            "outcome": POST_MERGE_MOOT_OUTCOME,
            "process_state_label": LABEL_POST_MERGE_MOOT,
            "reasons": [],
            "skipped": False,
            "safe_next_action": "",
        }

    # Merged signal present but no approval claim and no moot wording yet —
    # guide toward the moot outcome without blocking.
    return {
        "proven": True,
        "block": False,
        "outcome": POST_MERGE_MOOT_OUTCOME,
        "process_state_label": LABEL_POST_MERGE_MOOT,
        "reasons": [],
        "skipped": False,
        "safe_next_action": (
            "PR appears merged/closed; if submitting a verdict, record "
            "post-merge moot validation rather than an active approval"
        ),
    }
