"""DMS workflow — service layer.

trigger_workflow_transition() is the single entry point for all document
workflow transitions.  It handles:
  - State machine validation (delegates to DocumentStateMachine.validate)
  - Persistence (updates Document.workflow_status, and Document.status for
    publish / archive transitions)
  - Audit trail (writes a DocumentAuditLog record)
  - Event dispatch (fires the corresponding domain event)
"""

from __future__ import annotations

from simorgh.apps.dms.workflow.transitions import TRANSITIONS, WorkflowState, WorkflowError


def trigger_workflow_transition(
    doc,
    action: str,
    *,
    actor_id: int,
    comment: str = "",
) -> object:
    """Execute a workflow transition on *doc*.

    Parameters
    ----------
    doc:
        A ``Document`` model instance.
    action:
        One of the keys defined in ``TRANSITIONS`` (e.g. "publish").
    actor_id:
        PK of the user who triggered the action.
    comment:
        Optional text comment (stored in audit log).

    Returns
    -------
    The refreshed ``Document`` instance.

    Raises
    ------
    WorkflowError
        If the transition is not valid from the current state or validators fail.
    """
    from simorgh.apps.dms.documents.models import Document, DocumentStatus

    spec = TRANSITIONS.get(action)
    if spec is None:
        raise WorkflowError(f"Unknown workflow action: {action!r}")

    current = doc.workflow_status or ""
    if current not in spec.from_states:
        valid_from = ", ".join(sorted(spec.from_states) or ["<initial>"])
        raise WorkflowError(
            f"Cannot perform '{action}' from state '{current}'. "
            f"Valid from: {valid_from}."
        )

    for validator in spec.validators:
        validator(doc)

    # ---------- persist ----------
    doc.workflow_status = spec.to_state

    # Sync Document.status for terminal states
    if spec.to_state == WorkflowState.PUBLISHED:
        doc.status = DocumentStatus.PUBLISHED
    elif spec.to_state == WorkflowState.ARCHIVED:
        doc.status = DocumentStatus.ARCHIVED

    update_fields = ["workflow_status", "status"]
    doc.save(update_fields=update_fields)

    # ---------- audit log ----------
    _write_audit_log(doc=doc, action=action, actor_id=actor_id, comment=comment, spec=spec)

    # ---------- events ----------
    _dispatch_transition_event(doc=doc, action=action, actor_id=actor_id, comment=comment)

    return doc


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _write_audit_log(*, doc, action: str, actor_id: int, comment: str, spec) -> None:
    """Write a record to the DMS audit log (best-effort — never raises)."""
    try:
        from simorgh.apps.dms.audit.models import DocumentAction
        from simorgh.apps.dms.audit.services import log_document_action

        # Map workflow action to closest DocumentAction choice.
        _ACTION_MAP = {
            "publish": DocumentAction.PUBLISHED,
            "archive": DocumentAction.ARCHIVED,
        }
        audit_action = _ACTION_MAP.get(action, DocumentAction.METADATA_UPDATED)

        log_document_action(
            tenant_id=doc.tenant_id,
            document_id=doc.pk,
            action=audit_action,
            actor=None,  # actor_id not directly stored; metadata carries it
            metadata={
                "workflow_action": action,
                "label": spec.label,
                "to_state": doc.workflow_status,
                "actor_id": actor_id,
                "comment": comment,
            },
        )
    except Exception:
        pass  # audit is non-critical


def _dispatch_transition_event(*, doc, action: str, actor_id: int, comment: str) -> None:
    """Dispatch the domain event corresponding to the action."""
    # Import lazily to avoid circular imports at module load time.
    from simorgh.apps.events.bus import dispatch

    _ACTION_TO_EVENT: dict[str, str] = {
        "submit_for_review": "dms.document.submitted_for_review",
        "approve": "dms.document.approved",
        "reject": "dms.document.rejected",
        "publish": "dms.document.published",
        "archive": "dms.document.archived",
        "resubmit": "dms.document.submitted_for_review",
        "retract": "dms.document.retracted",
    }
    event_name = _ACTION_TO_EVENT.get(action)
    if event_name is None:
        return

    payload = {
        "document_id": str(doc.public_id),
        "tenant_id": doc.tenant_id,
        "actor_id": actor_id,
        "workflow_status": doc.workflow_status,
        "comment": comment,
    }
    try:
        dispatch(event_name, payload, audit=True)
    except Exception:
        pass  # event dispatch is non-critical
