"""Runtime queue and case-management helpers for the Deal Desk operator console."""

from __future__ import annotations

import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Literal
from urllib.parse import urlencode

from pydantic import BaseModel, ConfigDict, Field

from g_gremlin.autopilot.contracts import PendingActionV1
from g_gremlin.autopilot.pending_actions import load_pending_action

from .eventing import utc_now_iso
from .history import CloseCertificationRunV1
from .review import CloseReviewDetailV1, load_close_review_detail
from .service import (
    QuoteIntentV1,
    SellerQuoteRevisionApplyResultV1,
    SellerQuoteRevisionApprovalContextV1,
    SellerQuoteRevisionPreviewV1,
    load_seller_quote_revision_approval_context,
    preview_seller_quote_revision,
)
from .store import DealDeskStore
from .workflow import QuoteWorkflowRunV1, persist_quote_workflow_run

OperatorCaseType = Literal["seller_quote_revision", "close_review"]
OperatorCaseState = Literal["needs_human", "claimed", "waiting_on_seller", "completed", "closed"]
OperatorCaseResolution = Literal["need_info", "completed", "cannot_do"]

_SLA_WARNING_MINUTES = 120
_SLA_BREACH_MINUTES = 480
_STATE_PRIORITY: dict[OperatorCaseState, int] = {
    "needs_human": 0,
    "claimed": 1,
    "waiting_on_seller": 2,
    "completed": 3,
    "closed": 4,
}


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

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


class DealDeskOperatorNoteV1(_OperatorConsoleModel):
    version: int = 1
    kind: str = "comment"
    author: str
    author_email: str | None = None
    message: str = ""
    created_at: str


class DealDeskOperatorSlackContextRecordV1(_OperatorConsoleModel):
    version: int = 1
    seller_actor_id: str | None = None
    seller_actor_email: str | None = None
    source_surface: str | None = None
    source_channel: str | None = None
    revops_channel: str | None = None
    seller_response_callback_url: str | None = None
    response_callback_present: bool = False
    result_returned_to_seller: bool = False
    result_last_posted_at: str | None = None
    result_last_message: str | None = None


class DealDeskOperatorSlackContextViewV1(_OperatorConsoleModel):
    version: int = 1
    seller_actor_id: str | None = None
    seller_actor_email: str | None = None
    source_surface: str | None = None
    source_channel: str | None = None
    revops_channel: str | None = None
    response_callback_present: bool = False
    result_returned_to_seller: bool = False
    result_last_posted_at: str | None = None
    result_last_message: str | None = None


class DealDeskOperatorCaseRecordV1(_OperatorConsoleModel):
    version: int = 1
    case_id: str
    case_type: OperatorCaseType
    source_run_id: str
    opportunity_id: str
    crm_org_id: str
    org_alias: str | None = None
    created_at: str
    updated_at: str
    state: OperatorCaseState = "needs_human"
    resolution: OperatorCaseResolution | None = None
    claimed_by: str | None = None
    claimed_at: str | None = None
    resolved_by: str | None = None
    resolved_at: str | None = None
    notes: list[DealDeskOperatorNoteV1] = Field(default_factory=list)
    slack_context: DealDeskOperatorSlackContextRecordV1 = Field(default_factory=DealDeskOperatorSlackContextRecordV1)


class DealDeskOperatorLifecycleV1(_OperatorConsoleModel):
    version: int = 1
    state: OperatorCaseState
    resolution: OperatorCaseResolution | None = None
    claimed_by: str | None = None
    claimed_at: str | None = None
    resolved_by: str | None = None
    resolved_at: str | None = None
    updated_at: str


class DealDeskOperatorQueueCountsV1(_OperatorConsoleModel):
    needs_human: int = 0
    claimed: int = 0
    waiting_on_seller: int = 0
    completed: int = 0
    closed: int = 0


class DealDeskOperatorQueueItemV1(_OperatorConsoleModel):
    version: int = 1
    case_id: str
    case_type: OperatorCaseType
    source_run_id: str
    opportunity_id: str
    crm_org_id: str
    org_alias: str | None = None
    state: OperatorCaseState
    resolution: OperatorCaseResolution | None = None
    title: str
    subtitle: str | None = None
    requested_change: str = ""
    seller_label: str | None = None
    seller_actor_id: str | None = None
    engine: str | None = None
    queue_reason: str = ""
    blocked_reason: str = ""
    blocked_reason_code: str = "unknown"
    workflow_state: str | None = None
    overall_disposition: str | None = None
    source_surface: str | None = None
    source_channel: str | None = None
    result_returned_to_seller: bool = False
    lifecycle_summary: str = ""
    age_minutes: int = 0
    age_label: str = "0m"
    sla_status: Literal["on_track", "warning", "breached"] = "on_track"
    last_activity_at: str
    created_at: str
    updated_at: str


class DealDeskOperatorQueueResponseV1(_OperatorConsoleModel):
    version: int = 1
    status: str
    count: int = 0
    counts: DealDeskOperatorQueueCountsV1 = Field(default_factory=DealDeskOperatorQueueCountsV1)
    items: list[DealDeskOperatorQueueItemV1] = Field(default_factory=list)


class DealDeskOperatorSurfaceLinksV1(_OperatorConsoleModel):
    console_path: str = "/dealdesk/console"
    reviews_path: str = "/dealdesk/reviews"
    capabilities_path: str = "/dealdesk/capabilities"
    quote_intelligence_path: str = "/dealdesk/quote-intelligence"
    triggers_path: str = "/dealdesk/triggers"


class DealDeskOperatorQuoteDetailV1(_OperatorConsoleModel):
    version: int = 1
    run: QuoteWorkflowRunV1
    preview: SellerQuoteRevisionPreviewV1 | None = None
    apply_result: SellerQuoteRevisionApplyResultV1 | None = None
    approval_context: SellerQuoteRevisionApprovalContextV1 | None = None
    pending_action: PendingActionV1 | None = None
    pending_post: dict[str, Any] | None = None
    diff_lines: list[str] = Field(default_factory=list)


class DealDeskOperatorCloseDetailV1(_OperatorConsoleModel):
    version: int = 1
    review: CloseReviewDetailV1


class DealDeskOperatorCaseDetailV1(_OperatorConsoleModel):
    version: int = 1
    case: DealDeskOperatorQueueItemV1
    lifecycle: DealDeskOperatorLifecycleV1
    slack_context: DealDeskOperatorSlackContextViewV1
    notes: list[DealDeskOperatorNoteV1] = Field(default_factory=list)
    recommended_next_action: str = ""
    next_steps: list[str] = Field(default_factory=list)
    surface_links: DealDeskOperatorSurfaceLinksV1 = Field(default_factory=DealDeskOperatorSurfaceLinksV1)
    quote_detail: DealDeskOperatorQuoteDetailV1 | None = None
    close_detail: DealDeskOperatorCloseDetailV1 | None = None


def build_operator_case_id(*, case_type: OperatorCaseType, run_id: str) -> str:
    prefix = "quote" if case_type == "seller_quote_revision" else "close"
    return f"{prefix}--{str(run_id or '').strip()}"


def parse_operator_case_id(case_id: str) -> tuple[OperatorCaseType, str]:
    normalized = str(case_id or "").strip()
    if normalized.startswith("quote--"):
        return "seller_quote_revision", normalized[len("quote--") :]
    if normalized.startswith("close--"):
        return "close_review", normalized[len("close--") :]
    raise KeyError(f"Unknown Deal Desk operator case id {case_id!r}.")


def build_operator_console_queue(
    state_dir: str | Path | None,
    *,
    limit: int = 200,
    state: str | None = None,
    org: str | None = None,
    engine: str | None = None,
    seller: str | None = None,
    age_bucket: str | None = None,
    blocked_reason: str | None = None,
    query: str | None = None,
) -> DealDeskOperatorQueueResponseV1:
    store = DealDeskStore(state_dir)
    items = _all_queue_items(store)
    filtered = [
        item
        for item in items
        if _matches_queue_filters(
            item,
            org=org,
            engine=engine,
            seller=seller,
            age_bucket=age_bucket,
            blocked_reason=blocked_reason,
            query=query,
        )
    ]
    counts = _counts_for_items(filtered)
    normalized_state = _normalize_token(state)
    if normalized_state:
        filtered = [item for item in filtered if _normalize_token(item.state) == normalized_state]
    filtered.sort(
        key=lambda item: (
            _STATE_PRIORITY.get(item.state, 99),
            -_epoch_seconds(item.last_activity_at),
            item.case_id,
        )
    )
    trimmed = filtered[: max(1, min(int(limit or 200), 200))]
    return DealDeskOperatorQueueResponseV1(
        status="ok" if trimmed else "empty",
        count=len(trimmed),
        counts=counts,
        items=trimmed,
    )


def load_operator_console_detail(
    state_dir: str | Path | None,
    *,
    case_id: str,
) -> DealDeskOperatorCaseDetailV1:
    store = DealDeskStore(state_dir)
    case_type, run_id = parse_operator_case_id(case_id)
    if case_type == "seller_quote_revision":
        run = _load_quote_case_run(store, run_id=run_id)
        record = _quote_case_record(store, run=run)
        preview = _parse_quote_preview(run.preview)
        apply_result = _parse_quote_apply_result(run.apply_result)
        approval_context = load_seller_quote_revision_approval_context(
            run_id=str(run.pending_run_id or run.run_id),
            state_dir=str(store.state_dir),
        )
        pending_action = load_pending_action(store.state_dir, str(run.pending_run_id or run.run_id))
        queue_item = _queue_item_from_quote(store, run=run, record=record)
        if queue_item is None:
            raise KeyError(f"Quote operator case {case_id} was not found.")
        return DealDeskOperatorCaseDetailV1(
            case=queue_item,
            lifecycle=_lifecycle_view(record),
            slack_context=_slack_context_view(record),
            notes=list(record.notes),
            recommended_next_action=_recommended_quote_action(run=run, preview=preview, record=record),
            next_steps=_quote_next_steps(run=run, preview=preview),
            surface_links=_surface_links(
                crm_org_id=run.crm_org_id,
                org_alias=run.org_alias,
            ),
            quote_detail=DealDeskOperatorQuoteDetailV1(
                run=run,
                preview=preview,
                apply_result=apply_result,
                approval_context=approval_context,
                pending_action=pending_action,
                pending_post=_load_pending_post_marker(store.state_dir, str(run.pending_run_id or run.run_id)),
                diff_lines=_quote_diff_lines(preview),
            ),
        )

    review = load_close_review_detail(store.state_dir, run_id=run_id)
    record = _close_case_record(store, run=review.run)
    queue_item = _queue_item_from_close(store, run=review.run, record=record)
    if queue_item is None:
        raise KeyError(f"Close-review operator case {case_id} was not found.")
    return DealDeskOperatorCaseDetailV1(
        case=queue_item,
        lifecycle=_lifecycle_view(record),
        slack_context=_slack_context_view(record),
        notes=list(record.notes),
        recommended_next_action=_recommended_close_action(review.run, record),
        next_steps=_close_next_steps(review),
        surface_links=_surface_links(
            crm_org_id=review.run.crm_org_id,
            org_alias=review.run.org_alias,
        ),
        close_detail=DealDeskOperatorCloseDetailV1(review=review),
    )


def upsert_quote_operator_case(
    state_dir: str | Path | None,
    *,
    run_id: str,
    seller_actor_id: str | None = None,
    seller_actor_email: str | None = None,
    source_surface: str | None = None,
    source_channel: str | None = None,
    revops_channel: str | None = None,
    seller_response_callback_url: str | None = None,
) -> DealDeskOperatorCaseRecordV1:
    store = DealDeskStore(state_dir)
    run = _load_quote_case_run(store, run_id=run_id)
    record = _quote_case_record(store, run=run)
    _merge_slack_context(
        record,
        seller_actor_id=seller_actor_id,
        seller_actor_email=seller_actor_email,
        source_surface=source_surface,
        source_channel=source_channel,
        revops_channel=revops_channel,
        seller_response_callback_url=seller_response_callback_url,
    )
    if record.state in {"completed", "closed"}:
        record.state = "needs_human"
        record.resolution = None
        record.claimed_by = None
        record.claimed_at = None
        record.resolved_by = None
        record.resolved_at = None
    record.updated_at = utc_now_iso()
    store.save_operator_case_record(record)
    return record


def upsert_close_review_operator_case(
    state_dir: str | Path | None,
    *,
    run_id: str,
    seller_actor_id: str | None = None,
    seller_actor_email: str | None = None,
    source_surface: str | None = None,
    source_channel: str | None = None,
    revops_channel: str | None = None,
    seller_response_callback_url: str | None = None,
) -> DealDeskOperatorCaseRecordV1:
    store = DealDeskStore(state_dir)
    run = store.load_close_certification_run(run_id=run_id)
    if run is None:
        raise KeyError(f"Close certification run {run_id} was not found.")
    record = _close_case_record(store, run=run)
    _merge_slack_context(
        record,
        seller_actor_id=seller_actor_id,
        seller_actor_email=seller_actor_email,
        source_surface=source_surface,
        source_channel=source_channel,
        revops_channel=revops_channel,
        seller_response_callback_url=seller_response_callback_url,
    )
    if record.state in {"completed", "closed"}:
        record.state = "needs_human"
        record.resolution = None
        record.claimed_by = None
        record.claimed_at = None
        record.resolved_by = None
        record.resolved_at = None
    record.updated_at = utc_now_iso()
    store.save_operator_case_record(record)
    return record


def ensure_operator_case_record(
    state_dir: str | Path | None,
    *,
    case_id: str,
) -> DealDeskOperatorCaseRecordV1:
    store = DealDeskStore(state_dir)
    case_type, run_id = parse_operator_case_id(case_id)
    if case_type == "seller_quote_revision":
        run = _load_quote_case_run(store, run_id=run_id)
        return _quote_case_record(store, run=run)
    run = store.load_close_certification_run(run_id=run_id)
    if run is None:
        raise KeyError(f"Close certification run {run_id} was not found.")
    return _close_case_record(store, run=run)


def update_operator_case_lifecycle(
    state_dir: str | Path | None,
    *,
    case_id: str,
    state: OperatorCaseState | None = None,
    resolution: OperatorCaseResolution | None = None,
    actor_id: str,
    actor_email: str | None = None,
    note: str | None = None,
    note_kind: str = "comment",
) -> DealDeskOperatorCaseRecordV1:
    store = DealDeskStore(state_dir)
    record = ensure_operator_case_record(store.state_dir, case_id=case_id)
    now = utc_now_iso()
    normalized_note = str(note or "").strip()
    if state is not None:
        record.state = state
        if state in {"needs_human", "claimed"}:
            record.resolution = None
            record.resolved_by = None
            record.resolved_at = None
        if state == "needs_human":
            record.claimed_by = None
            record.claimed_at = None
        if state == "waiting_on_seller":
            record.resolved_by = None
            record.resolved_at = None
        if state == "claimed":
            record.claimed_by = actor_id
            record.claimed_at = now
        if state in {"completed", "closed"}:
            record.resolved_by = actor_id
            record.resolved_at = now
    if resolution is not None:
        record.resolution = resolution
    if normalized_note:
        record.notes.append(
            DealDeskOperatorNoteV1(
                kind=note_kind,
                author=actor_id,
                author_email=actor_email,
                message=normalized_note,
                created_at=now,
            )
        )
    record.updated_at = now
    store.save_operator_case_record(record)
    return record


def mark_operator_case_seller_response(
    state_dir: str | Path | None,
    *,
    case_id: str,
    sent: bool,
    message: str | None = None,
    posted_at: str | None = None,
) -> DealDeskOperatorCaseRecordV1:
    store = DealDeskStore(state_dir)
    record = ensure_operator_case_record(store.state_dir, case_id=case_id)
    if sent:
        record.slack_context.result_returned_to_seller = True
        record.slack_context.result_last_posted_at = str(posted_at or utc_now_iso())
    if message not in (None, ""):
        record.slack_context.result_last_message = str(message)
    record.updated_at = utc_now_iso()
    store.save_operator_case_record(record)
    return record


def refresh_quote_operator_case(
    state_dir: str | Path | None,
    *,
    case_id: str,
) -> DealDeskOperatorCaseDetailV1:
    store = DealDeskStore(state_dir)
    case_type, run_id = parse_operator_case_id(case_id)
    if case_type != "seller_quote_revision":
        raise KeyError(f"Refresh is not supported for operator case {case_id!r}.")
    run = _load_quote_case_run(store, run_id=run_id)
    intent = QuoteIntentV1.model_validate(run.intent)
    preview = preview_seller_quote_revision(
        intent=intent,
        state_dir=str(store.state_dir),
        force_refresh=True,
    )
    run.context_id = preview.context_id or run.context_id
    run.detected_engine = preview.detected_engine
    run.preview = preview.to_dict()
    if preview.selected_quote is not None:
        run.current_quote_id = str(preview.selected_quote.quote_id or "").strip() or None
    persist_quote_workflow_run(
        store,
        run,
        engine=preview.detected_engine,
        quote_status=(preview.selected_quote.status if preview.selected_quote is not None else None),
    )
    return load_operator_console_detail(store.state_dir, case_id=case_id)


def find_latest_seller_quote_revision_run_for_intent(
    state_dir: str | Path | None,
    *,
    intent: QuoteIntentV1,
) -> QuoteWorkflowRunV1 | None:
    store = DealDeskStore(state_dir)
    candidates = [
        item
        for item in store.list_quote_workflow_runs(
            opportunity_id=intent.opportunity_id,
            crm_org_id=intent.crm_org_id,
            org_alias=intent.org_alias,
        )
        if item.workflow_kind == "seller_quote_revision"
    ]
    candidates.sort(
        key=lambda item: (
            _epoch_seconds(item.last_transition_at or item.triggered_at),
            _epoch_seconds(item.triggered_at),
        ),
        reverse=True,
    )
    for candidate in candidates:
        candidate_intent = _parse_quote_intent(candidate.intent)
        if candidate_intent is None:
            continue
        if (
            candidate_intent.operation == intent.operation
            and candidate_intent.semantic_key == intent.semantic_key
            and candidate_intent.requested_value_raw == intent.requested_value_raw
            and candidate_intent.raw_request == intent.raw_request
        ):
            return candidate
    return candidates[0] if candidates else None


def load_operator_case_callback_url(
    state_dir: str | Path | None,
    *,
    case_id: str,
) -> str | None:
    record = DealDeskStore(state_dir).load_operator_case_record(case_id=case_id)
    if record is None:
        return None
    return str(record.slack_context.seller_response_callback_url or "").strip() or None


def _all_queue_items(store: DealDeskStore) -> list[DealDeskOperatorQueueItemV1]:
    records = {record.case_id: record for record in store.list_operator_case_records()}
    items: list[DealDeskOperatorQueueItemV1] = []

    quote_runs = sorted(
        [
            item
            for item in store.list_quote_workflow_runs()
            if item.workflow_kind == "seller_quote_revision"
        ],
        key=lambda item: (
            _epoch_seconds(item.last_transition_at or item.triggered_at),
            _epoch_seconds(item.triggered_at),
            item.run_id,
        ),
        reverse=True,
    )
    for run in quote_runs:
        case_id = build_operator_case_id(case_type="seller_quote_revision", run_id=run.run_id)
        item = _queue_item_from_quote(store, run=run, record=records.pop(case_id, None))
        if item is not None:
            items.append(item)

    close_runs = sorted(
        store.list_close_certification_runs(),
        key=lambda item: (
            _epoch_seconds(item.triggered_at),
            item.run_id,
        ),
        reverse=True,
    )
    for run in close_runs:
        case_id = build_operator_case_id(case_type="close_review", run_id=run.run_id)
        item = _queue_item_from_close(store, run=run, record=records.pop(case_id, None))
        if item is not None:
            items.append(item)
    return items


def _queue_item_from_quote(
    store: DealDeskStore,
    *,
    run: QuoteWorkflowRunV1,
    record: DealDeskOperatorCaseRecordV1 | None,
) -> DealDeskOperatorQueueItemV1 | None:
    default_state = _inferred_quote_case_state(run)
    existing_record = record
    if default_state is None and existing_record is None:
        return None
    record = _coerce_quote_record(run=run, record=existing_record)
    preview = _parse_quote_preview(run.preview)
    intent = _parse_quote_intent(run.intent)
    pending_action = load_pending_action(store.state_dir, str(run.pending_run_id or run.run_id))
    seller_actor_id, seller_actor_email = _seller_actor(record, pending_action=pending_action)
    seller_label = seller_actor_email or seller_actor_id
    requested_change = _quote_requested_change(intent=intent, preview=preview)
    queue_reason, blocked_reason_code, blocked_reason = _quote_queue_reason(run=run, preview=preview)
    title = requested_change or f"Quote change on {run.opportunity_id}"
    subtitle = _quote_subtitle(run=run, preview=preview)
    last_activity_at = str(record.updated_at or run.last_transition_at or run.triggered_at or utc_now_iso())
    age_minutes = _minutes_since(last_activity_at)
    return DealDeskOperatorQueueItemV1(
        case_id=record.case_id,
        case_type="seller_quote_revision",
        source_run_id=run.run_id,
        opportunity_id=run.opportunity_id,
        crm_org_id=run.crm_org_id,
        org_alias=run.org_alias,
        state=record.state,
        resolution=record.resolution,
        title=title,
        subtitle=subtitle,
        requested_change=requested_change,
        seller_label=seller_label,
        seller_actor_id=seller_actor_id,
        engine=run.detected_engine or (preview.detected_engine if preview is not None else None),
        queue_reason=queue_reason,
        blocked_reason=blocked_reason,
        blocked_reason_code=blocked_reason_code,
        workflow_state=run.current_state,
        source_surface=record.slack_context.source_surface,
        source_channel=record.slack_context.source_channel,
        result_returned_to_seller=record.slack_context.result_returned_to_seller,
        lifecycle_summary=_lifecycle_summary(record),
        age_minutes=age_minutes,
        age_label=_age_label(age_minutes),
        sla_status=_sla_status(age_minutes),
        last_activity_at=last_activity_at,
        created_at=record.created_at,
        updated_at=record.updated_at,
    )


def _queue_item_from_close(
    store: DealDeskStore,
    *,
    run: CloseCertificationRunV1,
    record: DealDeskOperatorCaseRecordV1 | None,
) -> DealDeskOperatorQueueItemV1 | None:
    default_state = _inferred_close_case_state(run)
    existing_record = record
    if default_state is None and existing_record is None:
        return None
    record = _coerce_close_record(run=run, record=existing_record)
    queue_reason, blocked_reason_code, blocked_reason = _close_queue_reason(run)
    seller_actor_id = record.slack_context.seller_actor_id
    seller_label = record.slack_context.seller_actor_email or seller_actor_id
    last_activity_at = str(record.updated_at or run.triggered_at or utc_now_iso())
    age_minutes = _minutes_since(last_activity_at)
    return DealDeskOperatorQueueItemV1(
        case_id=record.case_id,
        case_type="close_review",
        source_run_id=run.run_id,
        opportunity_id=run.opportunity_id,
        crm_org_id=run.crm_org_id,
        org_alias=run.org_alias,
        state=record.state,
        resolution=record.resolution,
        title=f"Close review on {run.opportunity_id}",
        subtitle=f"{_humanize_token(run.overall_disposition)} disposition",
        requested_change="Close certification escalation",
        seller_label=seller_label,
        seller_actor_id=seller_actor_id,
        engine=None,
        queue_reason=queue_reason,
        blocked_reason=blocked_reason,
        blocked_reason_code=blocked_reason_code,
        workflow_state=run.close_workflow_state,
        overall_disposition=run.overall_disposition,
        source_surface=record.slack_context.source_surface,
        source_channel=record.slack_context.source_channel,
        result_returned_to_seller=record.slack_context.result_returned_to_seller,
        lifecycle_summary=_lifecycle_summary(record),
        age_minutes=age_minutes,
        age_label=_age_label(age_minutes),
        sla_status=_sla_status(age_minutes),
        last_activity_at=last_activity_at,
        created_at=record.created_at,
        updated_at=record.updated_at,
    )


def _load_quote_case_run(store: DealDeskStore, *, run_id: str) -> QuoteWorkflowRunV1:
    run = store.load_quote_workflow_run(run_id=run_id)
    if run is None or run.workflow_kind != "seller_quote_revision":
        raise KeyError(f"Quote workflow run {run_id} was not found.")
    return run


def _quote_case_record(store: DealDeskStore, *, run: QuoteWorkflowRunV1) -> DealDeskOperatorCaseRecordV1:
    return _coerce_quote_record(
        run=run,
        record=store.load_operator_case_record(
            case_id=build_operator_case_id(case_type="seller_quote_revision", run_id=run.run_id)
        ),
    )


def _close_case_record(store: DealDeskStore, *, run: CloseCertificationRunV1) -> DealDeskOperatorCaseRecordV1:
    return _coerce_close_record(
        run=run,
        record=store.load_operator_case_record(
            case_id=build_operator_case_id(case_type="close_review", run_id=run.run_id)
        ),
    )


def _coerce_quote_record(
    *,
    run: QuoteWorkflowRunV1,
    record: DealDeskOperatorCaseRecordV1 | None,
) -> DealDeskOperatorCaseRecordV1:
    created_at = str(run.triggered_at or utc_now_iso())
    updated_at = str(run.last_transition_at or run.triggered_at or utc_now_iso())
    default_state = _inferred_quote_case_state(run) or "needs_human"
    case_id = build_operator_case_id(case_type="seller_quote_revision", run_id=run.run_id)
    if record is None:
        return DealDeskOperatorCaseRecordV1(
            case_id=case_id,
            case_type="seller_quote_revision",
            source_run_id=run.run_id,
            opportunity_id=run.opportunity_id,
            crm_org_id=run.crm_org_id,
            org_alias=run.org_alias,
            created_at=created_at,
            updated_at=updated_at,
            state=default_state,
        )
    record.case_id = case_id
    record.case_type = "seller_quote_revision"
    record.source_run_id = run.run_id
    record.opportunity_id = run.opportunity_id
    record.crm_org_id = run.crm_org_id
    record.org_alias = run.org_alias
    record.created_at = record.created_at or created_at
    record.updated_at = record.updated_at or updated_at
    record.state = record.state or default_state
    return record


def _coerce_close_record(
    *,
    run: CloseCertificationRunV1,
    record: DealDeskOperatorCaseRecordV1 | None,
) -> DealDeskOperatorCaseRecordV1:
    created_at = str(run.triggered_at or utc_now_iso())
    updated_at = str(run.triggered_at or utc_now_iso())
    default_state = _inferred_close_case_state(run) or "needs_human"
    case_id = build_operator_case_id(case_type="close_review", run_id=run.run_id)
    if record is None:
        return DealDeskOperatorCaseRecordV1(
            case_id=case_id,
            case_type="close_review",
            source_run_id=run.run_id,
            opportunity_id=run.opportunity_id,
            crm_org_id=run.crm_org_id,
            org_alias=run.org_alias,
            created_at=created_at,
            updated_at=updated_at,
            state=default_state,
        )
    record.case_id = case_id
    record.case_type = "close_review"
    record.source_run_id = run.run_id
    record.opportunity_id = run.opportunity_id
    record.crm_org_id = run.crm_org_id
    record.org_alias = run.org_alias
    record.created_at = record.created_at or created_at
    record.updated_at = record.updated_at or updated_at
    record.state = record.state or default_state
    return record


def _inferred_quote_case_state(run: QuoteWorkflowRunV1) -> OperatorCaseState | None:
    current_state = _normalize_token(run.current_state)
    receipt_outcome = _normalize_token(run.receipt_outcome)
    approval_note = _normalize_token(run.approval_note)
    if current_state == "approval_pending" or run.approval_required or run.pending_run_id:
        return "needs_human"
    if current_state == "delivered" or receipt_outcome == "success":
        return "completed"
    if current_state == "failed":
        if receipt_outcome == "rejected" or "rejected" in approval_note:
            return "closed"
        return "needs_human"
    return None


def _inferred_close_case_state(run: CloseCertificationRunV1) -> OperatorCaseState | None:
    disposition = _normalize_token(run.overall_disposition)
    if disposition in {"approval_required", "block_close"}:
        return "needs_human"
    return None


def _quote_queue_reason(
    *,
    run: QuoteWorkflowRunV1,
    preview: SellerQuoteRevisionPreviewV1 | None,
) -> tuple[str, str, str]:
    if _normalize_token(run.current_state) == "approval_pending":
        next_step = str((preview.next_step if preview is not None else "") or "").strip()
        return (
            "Awaiting operator approval",
            "approval_pending",
            next_step or "Quote change requires RevOps approval before execution.",
        )
    if preview is not None and preview.preview_policy_state in {"review_required", "unsupported"}:
        reason = _first_nonempty(list(preview.preview_policy_reasons or []))
        return (
            "Guided apply blocked by policy",
            "policy_review",
            reason or preview.support_reason or preview.apply_support_reason or run.workflow_error or "Policy requires human review.",
        )
    if preview is not None and preview.capability_gaps:
        return (
            "Capability gap requires manual follow-up",
            "capability_gap",
            _first_nonempty(list(preview.capability_gaps or [])) or preview.support_reason or run.workflow_error or "Required quote capability is unavailable.",
        )
    if _normalize_token(run.receipt_outcome) == "rejected":
        return (
            "Operator closed the request",
            "rejected",
            run.approval_note or run.workflow_error or "Quote change was rejected during review.",
        )
    return (
        "Quote change needs human follow-up",
        "execution_failed",
        run.workflow_error or (preview.next_step if preview is not None else None) or "Quote change did not complete automatically.",
    )


def _close_queue_reason(run: CloseCertificationRunV1) -> tuple[str, str, str]:
    disposition = _normalize_token(run.overall_disposition)
    if disposition == "block_close":
        return (
            "Close progression is blocked",
            "close_blocked",
            run.quote_integrity_summary or run.document_evidence_summary or "Close blockers require operator intervention.",
        )
    if disposition == "approval_required":
        return (
            "Close progression needs approval",
            "close_approval_required",
            run.quote_integrity_summary or run.document_evidence_summary or "Close review requires operator approval.",
        )
    return (
        "Close review needs follow-up",
        "close_review",
        run.quote_integrity_summary or run.document_evidence_summary or "Close review requires operator follow-up.",
    )


def _quote_requested_change(
    *,
    intent: QuoteIntentV1 | None,
    preview: SellerQuoteRevisionPreviewV1 | None,
) -> str:
    target_label = str(
        (
            preview.intent.target_label
            if preview is not None
            else (intent.target_label if intent is not None else "")
        )
        or ""
    ).strip()
    requested_value = str(
        (
            preview.intent.requested_value_display
            if preview is not None
            else (intent.requested_value_display if intent is not None else "")
        )
        or ""
    ).strip()
    if target_label and requested_value:
        return f"{target_label} -> {requested_value}"
    if target_label:
        return target_label
    return str(intent.raw_request if intent is not None else "").strip()


def _quote_subtitle(*, run: QuoteWorkflowRunV1, preview: SellerQuoteRevisionPreviewV1 | None) -> str:
    selected_quote = preview.selected_quote if preview is not None else None
    if selected_quote is not None:
        return f"{selected_quote.label} - {_humanize_token(selected_quote.status)}"
    return f"Opportunity {run.opportunity_id}"


def _recommended_quote_action(
    *,
    run: QuoteWorkflowRunV1,
    preview: SellerQuoteRevisionPreviewV1 | None,
    record: DealDeskOperatorCaseRecordV1,
) -> str:
    if _normalize_token(run.current_state) == "approval_pending":
        return "Review the pending plan, then complete to execute it or cannot do to reject it."
    if record.state == "waiting_on_seller":
        return "Use the note history to confirm what the seller still owes, then refresh once they respond."
    if preview is not None and preview.preview_policy_state in {"review_required", "unsupported"}:
        return "Inspect the policy reasons and evidence, then decide whether to request more detail from the seller or resolve the request manually."
    if preview is not None and preview.capability_gaps:
        return "Check the capability gaps and diff preview, then resolve manually or close the request as cannot do."
    return "Review the workflow timeline and failure reason, then either complete the handoff or request more information."


def _recommended_close_action(run: CloseCertificationRunV1, record: DealDeskOperatorCaseRecordV1) -> str:
    if record.state == "waiting_on_seller":
        return "Confirm the seller supplied the missing evidence, then refresh the review and complete the handoff."
    if _normalize_token(run.overall_disposition) == "block_close":
        return "Open the review detail, inspect the blockers, and decide whether close can proceed or must stay blocked."
    return "Review the packet and approval context, then complete the handoff or request more detail from the seller."


def _quote_next_steps(
    *,
    run: QuoteWorkflowRunV1,
    preview: SellerQuoteRevisionPreviewV1 | None,
) -> list[str]:
    steps: list[str] = []
    if preview is not None:
        if preview.next_step:
            steps.append(str(preview.next_step))
        steps.extend(str(item) for item in list(preview.preview_policy_reasons or [])[:3] if str(item).strip())
        steps.extend(str(item) for item in list(preview.capability_gaps or [])[:2] if str(item).strip())
    if run.pending_plan_path:
        steps.append(f"Pending plan: {run.pending_plan_path}")
    if run.workflow_error:
        steps.append(run.workflow_error)
    return _dedupe_nonempty(steps)


def _close_next_steps(review: CloseReviewDetailV1) -> list[str]:
    run = review.run
    steps: list[str] = []
    if review.review_summary:
        steps.append(review.review_summary)
    if run.quote_integrity_summary:
        steps.append(run.quote_integrity_summary)
    if run.document_evidence_summary:
        steps.append(run.document_evidence_summary)
    if run.post_close_pending_plan_path:
        steps.append(f"Pending post-close preview: {run.post_close_pending_plan_path}")
    return _dedupe_nonempty(steps[:4])


def _surface_links(*, crm_org_id: str | None, org_alias: str | None) -> DealDeskOperatorSurfaceLinksV1:
    params: dict[str, str] = {}
    if crm_org_id:
        params["crm_org_id"] = crm_org_id
    if org_alias:
        params["org_alias"] = org_alias
    query = f"?{urlencode(params)}" if params else ""
    return DealDeskOperatorSurfaceLinksV1(
        console_path="/dealdesk/console",
        reviews_path=f"/dealdesk/reviews{query}",
        capabilities_path=f"/dealdesk/capabilities{query}",
        quote_intelligence_path=f"/dealdesk/quote-intelligence{query}",
        triggers_path=f"/dealdesk/triggers{query}",
    )


def _lifecycle_view(record: DealDeskOperatorCaseRecordV1) -> DealDeskOperatorLifecycleV1:
    return DealDeskOperatorLifecycleV1(
        state=record.state,
        resolution=record.resolution,
        claimed_by=record.claimed_by,
        claimed_at=record.claimed_at,
        resolved_by=record.resolved_by,
        resolved_at=record.resolved_at,
        updated_at=record.updated_at,
    )


def _slack_context_view(record: DealDeskOperatorCaseRecordV1) -> DealDeskOperatorSlackContextViewV1:
    return DealDeskOperatorSlackContextViewV1(
        seller_actor_id=record.slack_context.seller_actor_id,
        seller_actor_email=record.slack_context.seller_actor_email,
        source_surface=record.slack_context.source_surface,
        source_channel=record.slack_context.source_channel,
        revops_channel=record.slack_context.revops_channel,
        response_callback_present=record.slack_context.response_callback_present,
        result_returned_to_seller=record.slack_context.result_returned_to_seller,
        result_last_posted_at=record.slack_context.result_last_posted_at,
        result_last_message=record.slack_context.result_last_message,
    )


def _seller_actor(
    record: DealDeskOperatorCaseRecordV1,
    *,
    pending_action: PendingActionV1 | None,
) -> tuple[str | None, str | None]:
    actor_id = str(record.slack_context.seller_actor_id or "").strip() or None
    actor_email = str(record.slack_context.seller_actor_email or "").strip() or None
    if actor_id or actor_email:
        return actor_id, actor_email
    if pending_action is None:
        return None, None
    return (
        str(pending_action.created_by.actor_id or "").strip() or None,
        str(pending_action.created_by.mapped_email or "").strip() or None,
    )


def _merge_slack_context(
    record: DealDeskOperatorCaseRecordV1,
    *,
    seller_actor_id: str | None = None,
    seller_actor_email: str | None = None,
    source_surface: str | None = None,
    source_channel: str | None = None,
    revops_channel: str | None = None,
    seller_response_callback_url: str | None = None,
) -> None:
    if seller_actor_id:
        record.slack_context.seller_actor_id = str(seller_actor_id).strip()
    if seller_actor_email:
        record.slack_context.seller_actor_email = str(seller_actor_email).strip()
    if source_surface:
        record.slack_context.source_surface = str(source_surface).strip()
    if source_channel:
        record.slack_context.source_channel = str(source_channel).strip()
    if revops_channel:
        record.slack_context.revops_channel = str(revops_channel).strip()
    if seller_response_callback_url:
        record.slack_context.seller_response_callback_url = str(seller_response_callback_url).strip()
        record.slack_context.response_callback_present = True
    elif record.slack_context.seller_response_callback_url:
        record.slack_context.response_callback_present = True


def _parse_quote_intent(payload: Any) -> QuoteIntentV1 | None:
    if not isinstance(payload, dict):
        return None
    try:
        return QuoteIntentV1.model_validate(payload)
    except Exception:
        return None


def _parse_quote_preview(payload: Any) -> SellerQuoteRevisionPreviewV1 | None:
    if not isinstance(payload, dict) or not payload:
        return None
    try:
        return SellerQuoteRevisionPreviewV1.model_validate(payload)
    except Exception:
        return None


def _parse_quote_apply_result(payload: Any) -> SellerQuoteRevisionApplyResultV1 | None:
    if not isinstance(payload, dict) or not payload:
        return None
    try:
        return SellerQuoteRevisionApplyResultV1.model_validate(payload)
    except Exception:
        return None


def _quote_diff_lines(preview: SellerQuoteRevisionPreviewV1 | None) -> list[str]:
    if preview is None:
        return []
    if preview.field_preview is not None:
        return _dedupe_nonempty(
            [
                f"Resolved field: {preview.field_preview.object_name}.{preview.field_preview.field_name}",
                f"Current value: {preview.field_preview.current_value_display}",
                f"Proposed value: {preview.field_preview.proposed_value_display}",
            ]
        )
    if preview.line_item_preview is None:
        return []
    current_lines = list(preview.line_item_preview.current_lines or [])
    current_line = current_lines[0] if current_lines else None
    lines = [
        f"Reference: {preview.line_item_preview.product_reference_value}",
        f"Resolution: {_humanize_token(preview.line_item_preview.resolution_status)}",
        str(preview.line_item_preview.resolution_reason or ""),
    ]
    if current_line is not None:
        lines.append(
            f"Current line: {current_line.product_name or current_line.product_code or current_line.line_item_id} x {current_line.quantity_display}"
        )
    if preview.line_item_preview.proposed_line is not None:
        proposed_line = preview.line_item_preview.proposed_line
        lines.append(
            f"Proposed line: {proposed_line.product_name or proposed_line.product_code or proposed_line.line_item_id} x {proposed_line.quantity_display}"
        )
    lines.extend(str(item) for item in list(preview.line_item_preview.diff_lines or [])[:4])
    return _dedupe_nonempty(lines)


def _load_pending_post_marker(state_dir: str | Path, run_id: str) -> dict[str, Any] | None:
    marker_path = Path(state_dir) / "pending" / str(run_id or "").strip() / "dealdesk_approval_request.json"
    if not marker_path.exists():
        return None
    try:
        payload = json.loads(marker_path.read_text(encoding="utf-8"))
    except Exception:
        return None
    return payload if isinstance(payload, dict) else None


def _matches_queue_filters(
    item: DealDeskOperatorQueueItemV1,
    *,
    org: str | None,
    engine: str | None,
    seller: str | None,
    age_bucket: str | None,
    blocked_reason: str | None,
    query: str | None,
) -> bool:
    if org:
        org_token = _normalize_token(org)
        if org_token not in {
            _normalize_token(item.org_alias),
            _normalize_token(item.crm_org_id),
        }:
            return False
    if engine and _normalize_token(item.engine) != _normalize_token(engine):
        return False
    if seller:
        haystack = " ".join(
            [
                str(item.seller_label or ""),
                str(item.seller_actor_id or ""),
            ]
        )
        if _normalize_token(seller) not in _normalize_token(haystack):
            return False
    if blocked_reason and _normalize_token(blocked_reason) not in {
        _normalize_token(item.blocked_reason_code),
        _normalize_token(item.blocked_reason),
    }:
        return False
    if age_bucket and not _matches_age_bucket(item.age_minutes, age_bucket):
        return False
    if query:
        text = " ".join(
            [
                item.case_id,
                str(item.title or ""),
                str(item.subtitle or ""),
                str(item.requested_change or ""),
                str(item.blocked_reason or ""),
                str(item.queue_reason or ""),
                str(item.opportunity_id or ""),
                str(item.org_alias or ""),
                str(item.crm_org_id or ""),
                str(item.seller_label or ""),
                str(item.engine or ""),
            ]
        )
        if _normalize_token(query) not in _normalize_token(text):
            return False
    return True


def _matches_age_bucket(age_minutes: int, age_bucket: str) -> bool:
    normalized = _normalize_token(age_bucket)
    if normalized == "under_2h":
        return age_minutes < _SLA_WARNING_MINUTES
    if normalized == "over_2h":
        return age_minutes >= _SLA_WARNING_MINUTES
    if normalized == "over_8h":
        return age_minutes >= _SLA_BREACH_MINUTES
    return True


def _counts_for_items(items: list[DealDeskOperatorQueueItemV1]) -> DealDeskOperatorQueueCountsV1:
    counts = DealDeskOperatorQueueCountsV1()
    for item in items:
        if item.state == "needs_human":
            counts.needs_human += 1
        elif item.state == "claimed":
            counts.claimed += 1
        elif item.state == "waiting_on_seller":
            counts.waiting_on_seller += 1
        elif item.state == "completed":
            counts.completed += 1
        elif item.state == "closed":
            counts.closed += 1
    return counts


def _lifecycle_summary(record: DealDeskOperatorCaseRecordV1) -> str:
    if record.state == "claimed" and record.claimed_by:
        return f"Claimed by {record.claimed_by}"
    if record.state == "waiting_on_seller":
        return "Waiting on seller"
    if record.state == "completed":
        actor = record.resolved_by or record.claimed_by
        return f"Completed by {actor}" if actor else "Completed"
    if record.state == "closed":
        if record.resolution == "cannot_do":
            return "Closed as cannot do"
        return "Closed"
    return "Needs human review"


def _minutes_since(value: str | None) -> int:
    parsed = _parse_datetime(value)
    if parsed is None:
        return 0
    delta = datetime.now(timezone.utc) - parsed
    return max(0, int(delta.total_seconds() // 60))


def _age_label(age_minutes: int) -> str:
    if age_minutes < 60:
        return f"{age_minutes}m"
    if age_minutes < 24 * 60:
        return f"{round(age_minutes / 60)}h"
    return f"{round(age_minutes / (24 * 60))}d"


def _sla_status(age_minutes: int) -> Literal["on_track", "warning", "breached"]:
    if age_minutes >= _SLA_BREACH_MINUTES:
        return "breached"
    if age_minutes >= _SLA_WARNING_MINUTES:
        return "warning"
    return "on_track"


def _parse_datetime(value: str | None) -> datetime | None:
    raw = str(value or "").strip()
    if not raw:
        return None
    try:
        return datetime.fromisoformat(raw.replace("Z", "+00:00")).astimezone(timezone.utc)
    except Exception:
        return None


def _epoch_seconds(value: str | None) -> int:
    parsed = _parse_datetime(value)
    return int(parsed.timestamp()) if parsed is not None else 0


def _normalize_token(value: Any) -> str:
    return str(value or "").strip().lower()


def _humanize_token(value: Any) -> str:
    return " ".join(part.capitalize() for part in _normalize_token(value).replace("-", "_").split("_") if part) or "Unknown"


def _first_nonempty(values: list[str]) -> str | None:
    for value in values:
        normalized = str(value or "").strip()
        if normalized:
            return normalized
    return None


def _dedupe_nonempty(values: list[str]) -> list[str]:
    seen: set[str] = set()
    result: list[str] = []
    for value in values:
        normalized = str(value or "").strip()
        if not normalized:
            continue
        if normalized in seen:
            continue
        seen.add(normalized)
        result.append(normalized)
    return result


__all__ = [
    "DealDeskOperatorCaseDetailV1",
    "DealDeskOperatorCaseRecordV1",
    "DealDeskOperatorQueueItemV1",
    "DealDeskOperatorQueueResponseV1",
    "build_operator_case_id",
    "build_operator_console_queue",
    "ensure_operator_case_record",
    "find_latest_seller_quote_revision_run_for_intent",
    "load_operator_case_callback_url",
    "load_operator_console_detail",
    "mark_operator_case_seller_response",
    "parse_operator_case_id",
    "refresh_quote_operator_case",
    "update_operator_case_lifecycle",
    "upsert_close_review_operator_case",
    "upsert_quote_operator_case",
]
