"""Deal Desk config mutation helpers and receipts."""

from __future__ import annotations

import hashlib
import json
import secrets
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Callable

from pydantic import BaseModel, ConfigDict, Field, computed_field

from .config import CertificationStepPolicyMode, DealDeskConfig
from .store import DealDeskStore
from .triggers import PostCloseTriggerFailureMode, PostCloseTriggerRunV1


class _MutationModel(BaseModel):
    model_config = ConfigDict(extra="ignore")

    def to_dict(self) -> dict[str, Any]:
        return self.model_dump(exclude_none=False, by_alias=True)


class DealDeskConfigMutationChangeV1(_MutationModel):
    path: str
    before: Any = None
    after: Any = None


class DealDeskPendingPreviewContextV1(_MutationModel):
    trigger_run_id: str
    opportunity_id: str
    pending_run_id: str | None = None
    pending_plan_path: str | None = None
    observed_at: str | None = None
    status: str | None = None


class DealDeskConfigMutationReceiptV1(_MutationModel):
    version: int = 1
    receipt_id: str
    receipt_path: str
    created_at: str
    actor_id: str
    actor_email: str | None = None
    action: str
    mutation_key: str
    crm_org_id: str | None = None
    org_alias: str | None = None
    target_kind: str = "post_close_trigger"
    trigger_key: str | None = None
    trigger_label: str | None = None
    step_key: str | None = None
    step_label: str | None = None
    config_source_before: str
    config_path_before: str
    config_path_after: str
    plan_summary: str
    risk_summary: str
    apply_summary: str
    before_config: DealDeskConfig
    after_config: DealDeskConfig
    changes: list[DealDeskConfigMutationChangeV1] = Field(default_factory=list)
    unblocked_pending: list[DealDeskPendingPreviewContextV1] = Field(default_factory=list)
    notes: list[str] = Field(default_factory=list)

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "DealDeskConfigMutationReceipt/v1"


class ApprovePostCloseTriggerPreviewRequestV1(_MutationModel):
    version: int = 1
    crm_org_id: str | None = None
    org_alias: str | None = None
    opportunity_id: str | None = None
    trigger_run_id: str | None = None

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "ApprovePostCloseTriggerPreviewRequest/v1"


class ApprovePostCloseTriggerPreviewResultV1(_MutationModel):
    version: int = 1
    status: str
    message: str
    receipt: DealDeskConfigMutationReceiptV1 | None = None

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "ApprovePostCloseTriggerPreviewResult/v1"


class _TriggerScopeRequestV1(_MutationModel):
    version: int = 1
    crm_org_id: str | None = None
    org_alias: str | None = None


class SetPostCloseTriggerEnabledRequestV1(_TriggerScopeRequestV1):
    enabled: bool

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "SetPostCloseTriggerEnabledRequest/v1"


class SetPostCloseTriggerOnFailureRequestV1(_TriggerScopeRequestV1):
    on_failure: PostCloseTriggerFailureMode

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "SetPostCloseTriggerOnFailureRequest/v1"


class SetCertificationStepModeRequestV1(_TriggerScopeRequestV1):
    mode: CertificationStepPolicyMode

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "SetCertificationStepModeRequest/v1"


class PostCloseTriggerConfigMutationResultV1(_MutationModel):
    version: int = 1
    status: str
    message: str
    receipt: DealDeskConfigMutationReceiptV1 | None = None

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "PostCloseTriggerConfigMutationResult/v1"


class CertificationStepPolicyMutationResultV1(_MutationModel):
    version: int = 1
    status: str
    message: str
    receipt: DealDeskConfigMutationReceiptV1 | None = None

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "CertificationStepPolicyMutationResult/v1"


def approve_post_close_trigger_preview(
    state_dir: str | Path | None,
    *,
    trigger_key: str,
    request: ApprovePostCloseTriggerPreviewRequestV1,
    actor_id: str,
    actor_email: str | None = None,
) -> ApprovePostCloseTriggerPreviewResultV1:
    store = DealDeskStore(state_dir)
    normalized_trigger_key = str(trigger_key or "").strip()
    normalized_crm_org_id = str(request.crm_org_id or "").strip() or None
    normalized_org_alias = str(request.org_alias or "").strip() or None
    requested_trigger_run_id = str(request.trigger_run_id or "").strip() or None
    requested_opportunity_id = str(request.opportunity_id or "").strip() or None

    if not normalized_trigger_key:
        raise RuntimeError("trigger_key is required for preview approval.")
    if normalized_crm_org_id is None and normalized_org_alias is None:
        raise RuntimeError("crm_org_id or org_alias is required for preview approval.")

    before_config, config_source_before, config_path_before = store.load_effective_config_info(
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
    )
    before_payload = before_config.model_dump(exclude_none=False)
    trigger_entry, trigger_index = _find_trigger_entry(before_config, normalized_trigger_key)

    if bool(trigger_entry.preview_approved):
        existing_receipt = store.find_latest_config_mutation_receipt(
            crm_org_id=normalized_crm_org_id,
            org_alias=normalized_org_alias,
            action="approve_preview",
            trigger_key=normalized_trigger_key,
        )
        message = f"Preview is already approved for {normalized_trigger_key}."
        if existing_receipt is not None:
            message = f"{message} Returning the latest approval receipt."
        return ApprovePostCloseTriggerPreviewResultV1(
            status="noop",
            message=message,
            receipt=existing_receipt,
        )

    pending_preview_runs = _collect_pending_preview_runs(
        store=store,
        trigger_key=normalized_trigger_key,
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
        requested_opportunity_id=requested_opportunity_id,
        requested_trigger_run_id=requested_trigger_run_id,
    )
    if not pending_preview_runs:
        raise RuntimeError(
            f"No staged preview was found for trigger {normalized_trigger_key} in the selected Deal Desk scope."
        )

    after_config = before_config.model_copy(deep=True)
    after_config.post_close.triggers[trigger_index].preview_approved = True
    after_payload = after_config.model_dump(exclude_none=False)
    changed_paths = _config_diff(before_payload, after_payload)
    after_config_path = store.save_generated_config(
        after_config,
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
    )

    created_at = datetime.now(timezone.utc).isoformat()
    receipt_id = f"ddcfg_{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%S%fZ')}_{secrets.token_hex(4)}"
    receipt_path = store.config_mutation_receipt_path(
        receipt_id=receipt_id,
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
    )
    pending_contexts = [
        DealDeskPendingPreviewContextV1(
            trigger_run_id=item.run_id,
            opportunity_id=item.opportunity_id,
            pending_run_id=str(item.pending_run_id or "").strip() or None,
            pending_plan_path=str(item.pending_plan_path or "").strip() or None,
            observed_at=item.observed_at,
            status=item.status,
        )
        for item in pending_preview_runs
    ]
    notes: list[str] = []
    if requested_trigger_run_id:
        notes.append(f"Requested preview trigger run: {requested_trigger_run_id}")
    if len(pending_contexts) > 1:
        notes.append(
            f"Approval is org-scoped for trigger {normalized_trigger_key} and unblocks {len(pending_contexts)} staged preview plans."
        )

    receipt = DealDeskConfigMutationReceiptV1(
        receipt_id=receipt_id,
        receipt_path=str(receipt_path),
        created_at=created_at,
        actor_id=str(actor_id or "").strip() or "unknown",
        actor_email=str(actor_email or "").strip() or None,
        action="approve_preview",
        mutation_key=f"approve_preview:{normalized_trigger_key}:{_stable_payload_hash(before_payload)}",
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
        target_kind="post_close_trigger",
        trigger_key=normalized_trigger_key,
        trigger_label=(
            pending_preview_runs[0].trigger_label
            if pending_preview_runs
            else trigger_entry.trigger_key.replace("_", " ").title()
        ),
        config_source_before=config_source_before,
        config_path_before=config_path_before,
        config_path_after=str(after_config_path),
        plan_summary=(
            f"Approve preview execution for post-close trigger {normalized_trigger_key} by setting "
            f"post_close.triggers[{trigger_index}].preview_approved to true."
        ),
        risk_summary=(
            "This is an org-scoped Deal Desk config change. The next close-certification run can execute any staged "
            f"pending plan for {normalized_trigger_key} in this scope."
        ),
        apply_summary=(
            f"Saved the generated Deal Desk config to {after_config_path} and persisted a config-mutation receipt."
        ),
        before_config=before_config,
        after_config=after_config,
        changes=changed_paths,
        unblocked_pending=pending_contexts,
        notes=notes,
    )
    store.save_config_mutation_receipt(
        receipt,
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
    )
    _annotate_pending_preview_runs(
        store=store,
        pending_preview_runs=pending_preview_runs,
        receipt=receipt,
        actor_label=str(actor_email or actor_id or "").strip() or "unknown",
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
    )
    return ApprovePostCloseTriggerPreviewResultV1(
        status="applied",
        message=(
            f"Approved preview for {normalized_trigger_key}. "
            f"{len(pending_contexts)} staged preview plan(s) are now eligible for live execution on the next close-certification run."
        ),
        receipt=receipt,
    )


def set_post_close_trigger_enabled(
    state_dir: str | Path | None,
    *,
    trigger_key: str,
    request: SetPostCloseTriggerEnabledRequestV1,
    actor_id: str,
    actor_email: str | None = None,
) -> PostCloseTriggerConfigMutationResultV1:
    normalized_enabled = bool(request.enabled)
    return _apply_trigger_config_mutation(
        state_dir,
        trigger_key=trigger_key,
        crm_org_id=str(request.crm_org_id or "").strip() or None,
        org_alias=str(request.org_alias or "").strip() or None,
        actor_id=actor_id,
        actor_email=actor_email,
        action="set_enabled",
        target_signature=f"enabled={str(normalized_enabled).lower()}",
        matches_target=lambda entry: bool(entry.enabled) is normalized_enabled,
        apply_change=lambda config, index: setattr(config.post_close.triggers[index], "enabled", normalized_enabled),
        noop_message=(
            f"Trigger {str(trigger_key or '').strip()} is already "
            f"{'enabled' if normalized_enabled else 'disabled'} for this Deal Desk scope."
        ),
        applied_message=(
            f"Updated {str(trigger_key or '').strip()} to "
            f"{'enabled' if normalized_enabled else 'disabled'} for this Deal Desk scope."
        ),
        build_plan_summary=lambda index: (
            f"Set post_close.triggers[{index}].enabled to {str(normalized_enabled).lower()} "
            f"for trigger {str(trigger_key or '').strip()}."
        ),
        build_risk_summary=lambda _before, _after, pending_runs: (
            "This is an org-scoped Deal Desk config change. "
            + (
                "Future close-certification runs can execute this trigger again according to its preview policy."
                if normalized_enabled
                else "Future close-certification runs will skip this trigger until it is re-enabled."
            )
            + (
                f" {len(pending_runs)} staged preview plan(s) already exist on disk for this trigger."
                if pending_runs
                else ""
            )
        ),
    )


def set_post_close_trigger_on_failure(
    state_dir: str | Path | None,
    *,
    trigger_key: str,
    request: SetPostCloseTriggerOnFailureRequestV1,
    actor_id: str,
    actor_email: str | None = None,
) -> PostCloseTriggerConfigMutationResultV1:
    normalized_on_failure = str(request.on_failure or "").strip().lower()
    return _apply_trigger_config_mutation(
        state_dir,
        trigger_key=trigger_key,
        crm_org_id=str(request.crm_org_id or "").strip() or None,
        org_alias=str(request.org_alias or "").strip() or None,
        actor_id=actor_id,
        actor_email=actor_email,
        action="set_on_failure",
        target_signature=f"on_failure={normalized_on_failure}",
        matches_target=lambda entry: str(entry.on_failure or "").strip().lower() == normalized_on_failure,
        apply_change=lambda config, index: setattr(config.post_close.triggers[index], "on_failure", normalized_on_failure),
        noop_message=(
            f"Trigger {str(trigger_key or '').strip()} already uses on_failure={normalized_on_failure} "
            "for this Deal Desk scope."
        ),
        applied_message=(
            f"Updated {str(trigger_key or '').strip()} to on_failure={normalized_on_failure} "
            "for this Deal Desk scope."
        ),
        build_plan_summary=lambda index: (
            f"Set post_close.triggers[{index}].on_failure to {normalized_on_failure} "
            f"for trigger {str(trigger_key or '').strip()}."
        ),
        build_risk_summary=lambda _before, _after, pending_runs: (
            "This is an org-scoped Deal Desk config change. Future live trigger failures will "
            + (
                "block close progression for this trigger."
                if normalized_on_failure == "block"
                else "escalate for operator follow-up without blocking close progression."
            )
            + (
                f" {len(pending_runs)} staged preview plan(s) already exist on disk for this trigger."
                if pending_runs
                else ""
            )
        ),
    )


def set_certification_step_mode(
    state_dir: str | Path | None,
    *,
    step_key: str,
    request: SetCertificationStepModeRequestV1,
    actor_id: str,
    actor_email: str | None = None,
) -> CertificationStepPolicyMutationResultV1:
    store = DealDeskStore(state_dir)
    normalized_step_key = str(step_key or "").strip()
    normalized_crm_org_id = str(request.crm_org_id or "").strip() or None
    normalized_org_alias = str(request.org_alias or "").strip() or None
    normalized_mode = str(request.mode or "").strip().lower() or "auto"
    normalized_actor_id = str(actor_id or "").strip() or "unknown"
    normalized_actor_email = str(actor_email or "").strip() or None

    if not normalized_step_key:
        raise RuntimeError("step_key is required for Deal Desk certification-step mutation.")
    if normalized_crm_org_id is None and normalized_org_alias is None:
        raise RuntimeError("crm_org_id or org_alias is required for Deal Desk certification-step mutation.")

    step_meta = _certification_step_meta(normalized_step_key)
    before_config, config_source_before, config_path_before = store.load_effective_config_info(
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
    )
    before_payload = before_config.model_dump(exclude_none=False)
    current_mode = _get_certification_step_mode(before_config, normalized_step_key)

    if current_mode == normalized_mode:
        existing_receipt = _find_latest_step_receipt_for_state(
            store=store,
            crm_org_id=normalized_crm_org_id,
            org_alias=normalized_org_alias,
            action="set_step_mode",
            step_key=normalized_step_key,
            target_mode=normalized_mode,
        )
        message = (
            f"Certification step {step_meta['label']} is already set to {normalized_mode} "
            "for this Deal Desk scope."
        )
        if existing_receipt is not None:
            message = f"{message} Returning the latest mutation receipt."
        return CertificationStepPolicyMutationResultV1(
            status="noop",
            message=message,
            receipt=existing_receipt,
        )

    after_config = before_config.model_copy(deep=True)
    _set_certification_step_mode(after_config, normalized_step_key, normalized_mode)
    after_payload = after_config.model_dump(exclude_none=False)
    changed_paths = _config_diff(before_payload, after_payload)
    after_config_path = store.save_generated_config(
        after_config,
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
    )

    created_at = datetime.now(timezone.utc).isoformat()
    receipt_id = f"ddcfg_{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%S%fZ')}_{secrets.token_hex(4)}"
    receipt_path = store.config_mutation_receipt_path(
        receipt_id=receipt_id,
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
    )
    receipt = DealDeskConfigMutationReceiptV1(
        receipt_id=receipt_id,
        receipt_path=str(receipt_path),
        created_at=created_at,
        actor_id=normalized_actor_id,
        actor_email=normalized_actor_email,
        action="set_step_mode",
        mutation_key=f"set_step_mode:{normalized_step_key}:mode={normalized_mode}:{_stable_payload_hash(before_payload)}",
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
        target_kind="certification_step",
        step_key=normalized_step_key,
        step_label=step_meta["label"],
        config_source_before=config_source_before,
        config_path_before=config_path_before,
        config_path_after=str(after_config_path),
        plan_summary=(
            f"Set {step_meta['path']} to {normalized_mode} for certification step {step_meta['label']}."
        ),
        risk_summary=_step_mode_risk_summary(step_key=normalized_step_key, mode=normalized_mode),
        apply_summary=f"Saved the generated Deal Desk config to {after_config_path} and persisted a config-mutation receipt.",
        before_config=before_config,
        after_config=after_config,
        changes=changed_paths,
        notes=_build_certification_step_mutation_notes(
            step_key=normalized_step_key,
            before_mode=current_mode,
            after_mode=normalized_mode,
        ),
    )
    store.save_config_mutation_receipt(
        receipt,
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
    )
    return CertificationStepPolicyMutationResultV1(
        status="applied",
        message=(
            f"Updated certification step {step_meta['label']} to {normalized_mode} "
            "for this Deal Desk scope."
        ),
        receipt=receipt,
    )


def _find_trigger_entry(config: DealDeskConfig, trigger_key: str) -> tuple[Any, int]:
    normalized_trigger_key = str(trigger_key or "").strip()
    for index, item in enumerate(config.post_close.triggers):
        if str(item.trigger_key or "").strip() == normalized_trigger_key:
            return item, index
    raise KeyError(f"Post-close trigger {normalized_trigger_key} was not found in Deal Desk config.")


_CERTIFICATION_STEP_META: dict[str, dict[str, str]] = {
    "close_progression": {
        "label": "Close progression",
        "path": "certification_steps.close_progression.mode",
    },
    "post_close_execution": {
        "label": "Post-close execution",
        "path": "certification_steps.post_close_execution.mode",
    },
}


def _certification_step_meta(step_key: str) -> dict[str, str]:
    normalized_step_key = str(step_key or "").strip()
    meta = _CERTIFICATION_STEP_META.get(normalized_step_key)
    if meta is None:
        raise KeyError(f"Certification step {normalized_step_key} was not found in Deal Desk config.")
    return meta


def _get_certification_step_mode(config: DealDeskConfig, step_key: str) -> str:
    normalized_step_key = str(step_key or "").strip()
    if normalized_step_key == "close_progression":
        return str(config.certification_steps.close_progression.mode or "auto").strip().lower() or "auto"
    if normalized_step_key == "post_close_execution":
        return str(config.certification_steps.post_close_execution.mode or "auto").strip().lower() or "auto"
    raise KeyError(f"Certification step {normalized_step_key} was not found in Deal Desk config.")


def _set_certification_step_mode(config: DealDeskConfig, step_key: str, mode: str) -> None:
    normalized_step_key = str(step_key or "").strip()
    normalized_mode = str(mode or "").strip().lower() or "auto"
    if normalized_step_key == "close_progression":
        config.certification_steps.close_progression.mode = normalized_mode
        return
    if normalized_step_key == "post_close_execution":
        config.certification_steps.post_close_execution.mode = normalized_mode
        return
    raise KeyError(f"Certification step {normalized_step_key} was not found in Deal Desk config.")


def _step_mode_risk_summary(*, step_key: str, mode: str) -> str:
    normalized_step_key = str(step_key or "").strip()
    normalized_mode = str(mode or "").strip().lower() or "auto"
    if normalized_step_key == "close_progression":
        if normalized_mode == "approve":
            return (
                "This is an org-scoped Deal Desk config change. Final close progression will stage a pending "
                "plan and require operator approval before the opportunity moves to Closed Won."
            )
        if normalized_mode == "skip":
            return (
                "This is an org-scoped Deal Desk config change. Close certification can still complete, but "
                "final close progression will not run automatically in this Deal Desk scope."
            )
        return (
            "This is an org-scoped Deal Desk config change. Final close progression can run automatically "
            "after certification, subject to the active autonomy policy."
        )
    if normalized_step_key == "post_close_execution":
        if normalized_mode == "approve":
            return (
                "This is an org-scoped Deal Desk config change. Post-close execution will stage live work "
                "for operator approval instead of executing automatically after certification."
            )
        if normalized_mode == "skip":
            return (
                "This is an org-scoped Deal Desk config change. Post-close execution will not run in this "
                "Deal Desk scope until the step mode is changed."
            )
        return (
            "This is an org-scoped Deal Desk config change. Post-close execution can run automatically "
            "after certification, while still honoring trigger preview and failure policies."
        )
    raise KeyError(f"Certification step {normalized_step_key} was not found in Deal Desk config.")


def _build_certification_step_mutation_notes(
    *,
    step_key: str,
    before_mode: str,
    after_mode: str,
) -> list[str]:
    step_meta = _certification_step_meta(step_key)
    notes = [
        f"{step_meta['label']} mode changed from {before_mode} to {after_mode}.",
    ]
    if str(step_key or "").strip() == "close_progression":
        if after_mode == "approve":
            notes.append("Close progression now stages a pending plan instead of executing automatically.")
        elif after_mode == "skip":
            notes.append("Close progression now stops after certification without attempting the final stage move.")
        else:
            notes.append("Close progression can resume automatic execution after certification.")
    elif str(step_key or "").strip() == "post_close_execution":
        if after_mode == "approve":
            notes.append("Live post-close work now requires operator approval before execution.")
        elif after_mode == "skip":
            notes.append("Post-close execution now stays disabled after certification in this Deal Desk scope.")
        else:
            notes.append("Post-close execution can resume automatic processing after certification.")
    return notes


def _collect_pending_preview_runs(
    *,
    store: DealDeskStore,
    trigger_key: str,
    crm_org_id: str | None,
    org_alias: str | None,
    requested_opportunity_id: str | None,
    requested_trigger_run_id: str | None,
) -> list[PostCloseTriggerRunV1]:
    pending_runs = [
        item
        for item in store.list_post_close_trigger_runs(crm_org_id=crm_org_id, org_alias=org_alias)
        if item.mode == "preview"
        and item.status == "preview_generated"
        and item.trigger_key == trigger_key
        and (str(item.pending_run_id or "").strip() or str(item.pending_plan_path or "").strip())
    ]
    if requested_opportunity_id:
        pending_runs = [item for item in pending_runs if item.opportunity_id == requested_opportunity_id]
    pending_runs = sorted(pending_runs, key=lambda item: str(item.observed_at or ""), reverse=True)
    if requested_trigger_run_id:
        explicit = next((item for item in pending_runs if item.run_id == requested_trigger_run_id), None)
        if explicit is None:
            explicit = store.load_post_close_trigger_run(
                run_id=requested_trigger_run_id,
                opportunity_id=requested_opportunity_id,
                crm_org_id=crm_org_id,
                org_alias=org_alias,
            )
        if explicit is None:
            raise KeyError(f"Preview trigger run {requested_trigger_run_id} was not found.")
        if explicit.mode != "preview" or explicit.trigger_key != trigger_key:
            raise RuntimeError(f"Trigger run {requested_trigger_run_id} does not match trigger {trigger_key}.")
        if explicit.status != "preview_generated":
            raise RuntimeError(f"Trigger run {requested_trigger_run_id} is not awaiting preview approval.")
        if not any(item.run_id == explicit.run_id for item in pending_runs):
            pending_runs.insert(0, explicit)
        else:
            # Put the explicitly requested preview first so receipts and notes reflect the operator-selected run.
            pending_runs = [item for item in pending_runs if item.run_id != explicit.run_id]
            pending_runs.insert(0, explicit)
    return pending_runs


def _annotate_pending_preview_runs(
    *,
    store: DealDeskStore,
    pending_preview_runs: list[PostCloseTriggerRunV1],
    receipt: DealDeskConfigMutationReceiptV1,
    actor_label: str,
    crm_org_id: str | None,
    org_alias: str | None,
) -> None:
    for item in pending_preview_runs:
        notes = list(item.notes or [])
        note = f"Preview approved via Deal Desk config receipt {receipt.receipt_id}."
        if note not in notes:
            notes.append(note)
        store.save_post_close_trigger_run(
            item.model_copy(
                update={
                    "approval_receipt_id": receipt.receipt_id,
                    "approval_receipt_path": receipt.receipt_path,
                    "approved_at": receipt.created_at,
                    "approved_by": actor_label,
                    "notes": notes,
                    "crm_org_id": crm_org_id if crm_org_id is not None else item.crm_org_id,
                    "org_alias": org_alias if org_alias is not None else item.org_alias,
                }
            )
        )


def _apply_trigger_config_mutation(
    state_dir: str | Path | None,
    *,
    trigger_key: str,
    crm_org_id: str | None,
    org_alias: str | None,
    actor_id: str,
    actor_email: str | None,
    action: str,
    target_signature: str,
    matches_target: Callable[[Any], bool],
    apply_change: Callable[[DealDeskConfig, int], None],
    noop_message: str,
    applied_message: str,
    build_plan_summary: Callable[[int], str],
    build_risk_summary: Callable[[DealDeskConfig, DealDeskConfig, list[PostCloseTriggerRunV1]], str],
) -> PostCloseTriggerConfigMutationResultV1:
    store = DealDeskStore(state_dir)
    normalized_trigger_key = str(trigger_key or "").strip()
    normalized_crm_org_id = str(crm_org_id or "").strip() or None
    normalized_org_alias = str(org_alias or "").strip() or None
    normalized_actor_id = str(actor_id or "").strip() or "unknown"
    normalized_actor_email = str(actor_email or "").strip() or None

    if not normalized_trigger_key:
        raise RuntimeError("trigger_key is required for Deal Desk trigger mutation.")
    if normalized_crm_org_id is None and normalized_org_alias is None:
        raise RuntimeError("crm_org_id or org_alias is required for Deal Desk trigger mutation.")

    before_config, config_source_before, config_path_before = store.load_effective_config_info(
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
    )
    before_payload = before_config.model_dump(exclude_none=False)
    trigger_entry, trigger_index = _find_trigger_entry(before_config, normalized_trigger_key)

    if matches_target(trigger_entry):
        existing_receipt = _find_latest_trigger_receipt_for_state(
            store=store,
            crm_org_id=normalized_crm_org_id,
            org_alias=normalized_org_alias,
            action=action,
            trigger_key=normalized_trigger_key,
            matches_target=matches_target,
        )
        message = noop_message
        if existing_receipt is not None:
            message = f"{message} Returning the latest mutation receipt."
        return PostCloseTriggerConfigMutationResultV1(
            status="noop",
            message=message,
            receipt=existing_receipt,
        )

    pending_preview_runs = _collect_pending_preview_runs(
        store=store,
        trigger_key=normalized_trigger_key,
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
        requested_opportunity_id=None,
        requested_trigger_run_id=None,
    )

    after_config = before_config.model_copy(deep=True)
    apply_change(after_config, trigger_index)
    after_payload = after_config.model_dump(exclude_none=False)
    changed_paths = _config_diff(before_payload, after_payload)
    after_config_path = store.save_generated_config(
        after_config,
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
    )

    created_at = datetime.now(timezone.utc).isoformat()
    receipt_id = f"ddcfg_{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%S%fZ')}_{secrets.token_hex(4)}"
    receipt_path = store.config_mutation_receipt_path(
        receipt_id=receipt_id,
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
    )
    after_trigger_entry, _ = _find_trigger_entry(after_config, normalized_trigger_key)
    receipt = DealDeskConfigMutationReceiptV1(
        receipt_id=receipt_id,
        receipt_path=str(receipt_path),
        created_at=created_at,
        actor_id=normalized_actor_id,
        actor_email=normalized_actor_email,
        action=action,
        mutation_key=f"{action}:{normalized_trigger_key}:{target_signature}:{_stable_payload_hash(before_payload)}",
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
        target_kind="post_close_trigger",
        trigger_key=normalized_trigger_key,
        trigger_label=normalized_trigger_key.replace("_", " ").title(),
        config_source_before=config_source_before,
        config_path_before=config_path_before,
        config_path_after=str(after_config_path),
        plan_summary=build_plan_summary(trigger_index),
        risk_summary=build_risk_summary(before_config, after_config, pending_preview_runs),
        apply_summary=f"Saved the generated Deal Desk config to {after_config_path} and persisted a config-mutation receipt.",
        before_config=before_config,
        after_config=after_config,
        changes=changed_paths,
        notes=_build_trigger_mutation_notes(
            trigger_key=normalized_trigger_key,
            before_trigger=trigger_entry,
            after_trigger=after_trigger_entry,
            pending_preview_runs=pending_preview_runs,
        ),
    )
    store.save_config_mutation_receipt(
        receipt,
        crm_org_id=normalized_crm_org_id,
        org_alias=normalized_org_alias,
    )
    return PostCloseTriggerConfigMutationResultV1(
        status="applied",
        message=applied_message,
        receipt=receipt,
    )


def _build_trigger_mutation_notes(
    *,
    trigger_key: str,
    before_trigger: Any,
    after_trigger: Any,
    pending_preview_runs: list[PostCloseTriggerRunV1],
) -> list[str]:
    notes: list[str] = []
    if bool(before_trigger.enabled) != bool(after_trigger.enabled):
        notes.append(
            f"Trigger enabled state changed from {str(bool(before_trigger.enabled)).lower()} "
            f"to {str(bool(after_trigger.enabled)).lower()}."
        )
    before_failure = str(before_trigger.on_failure or "").strip().lower()
    after_failure = str(after_trigger.on_failure or "").strip().lower()
    if before_failure != after_failure:
        notes.append(f"Trigger failure mode changed from {before_failure} to {after_failure}.")
    if pending_preview_runs:
        latest = pending_preview_runs[0]
        notes.append(
            f"{len(pending_preview_runs)} staged preview plan(s) already exist for {trigger_key} in this Deal Desk scope."
        )
        if latest.pending_plan_path:
            notes.append(
                f"Latest staged preview plan remains at {latest.pending_plan_path} for run {latest.run_id} "
                f"(opportunity {latest.opportunity_id})."
            )
        elif latest.pending_run_id:
            notes.append(
                f"Latest staged preview run is {latest.run_id} with pending run {latest.pending_run_id} "
                f"(opportunity {latest.opportunity_id})."
            )
    return notes


def _find_latest_trigger_receipt_for_state(
    *,
    store: DealDeskStore,
    crm_org_id: str | None,
    org_alias: str | None,
    action: str,
    trigger_key: str,
    matches_target: Callable[[Any], bool],
) -> DealDeskConfigMutationReceiptV1 | None:
    receipts = sorted(
        store.list_config_mutation_receipts(crm_org_id=crm_org_id, org_alias=org_alias),
        key=lambda item: str(getattr(item, "created_at", "") or ""),
        reverse=True,
    )
    normalized_action = str(action or "").strip().lower()
    normalized_trigger_key = str(trigger_key or "").strip()
    for item in receipts:
        if str(getattr(item, "action", "") or "").strip().lower() != normalized_action:
            continue
        if str(getattr(item, "trigger_key", "") or "").strip() != normalized_trigger_key:
            continue
        after_config = getattr(item, "after_config", None)
        if after_config is None:
            continue
        try:
            trigger_entry, _ = _find_trigger_entry(after_config, normalized_trigger_key)
        except KeyError:
            continue
        if matches_target(trigger_entry):
            return item
    return None


def _find_latest_step_receipt_for_state(
    *,
    store: DealDeskStore,
    crm_org_id: str | None,
    org_alias: str | None,
    action: str,
    step_key: str,
    target_mode: str,
) -> DealDeskConfigMutationReceiptV1 | None:
    receipts = sorted(
        store.list_config_mutation_receipts(crm_org_id=crm_org_id, org_alias=org_alias),
        key=lambda item: str(getattr(item, "created_at", "") or ""),
        reverse=True,
    )
    normalized_action = str(action or "").strip().lower()
    normalized_step_key = str(step_key or "").strip()
    normalized_target_mode = str(target_mode or "").strip().lower() or "auto"
    for item in receipts:
        if str(getattr(item, "action", "") or "").strip().lower() != normalized_action:
            continue
        if str(getattr(item, "step_key", "") or "").strip() != normalized_step_key:
            continue
        after_config = getattr(item, "after_config", None)
        if after_config is None:
            continue
        try:
            if _get_certification_step_mode(after_config, normalized_step_key) == normalized_target_mode:
                return item
        except KeyError:
            continue
    return None


def _config_diff(before: Any, after: Any, *, path: str = "") -> list[DealDeskConfigMutationChangeV1]:
    if isinstance(before, dict) and isinstance(after, dict):
        changes: list[DealDeskConfigMutationChangeV1] = []
        for key in sorted(set(before.keys()) | set(after.keys())):
            child_path = f"{path}.{key}" if path else str(key)
            changes.extend(_config_diff(before.get(key), after.get(key), path=child_path))
        return changes
    if isinstance(before, list) and isinstance(after, list):
        changes: list[DealDeskConfigMutationChangeV1] = []
        for index in range(max(len(before), len(after))):
            child_path = f"{path}[{index}]"
            before_value = before[index] if index < len(before) else None
            after_value = after[index] if index < len(after) else None
            changes.extend(_config_diff(before_value, after_value, path=child_path))
        return changes
    if before == after:
        return []
    return [DealDeskConfigMutationChangeV1(path=path or "$", before=before, after=after)]


def _stable_payload_hash(payload: dict[str, Any]) -> str:
    canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
    return hashlib.sha256(canonical.encode("utf-8")).hexdigest()


__all__ = [
    "ApprovePostCloseTriggerPreviewRequestV1",
    "ApprovePostCloseTriggerPreviewResultV1",
    "CertificationStepPolicyMutationResultV1",
    "DealDeskConfigMutationChangeV1",
    "DealDeskConfigMutationReceiptV1",
    "DealDeskPendingPreviewContextV1",
    "PostCloseTriggerConfigMutationResultV1",
    "SetCertificationStepModeRequestV1",
    "SetPostCloseTriggerEnabledRequestV1",
    "SetPostCloseTriggerOnFailureRequestV1",
    "approve_post_close_trigger_preview",
    "set_certification_step_mode",
    "set_post_close_trigger_enabled",
    "set_post_close_trigger_on_failure",
]
