"""Invoice / document status lifecycle for the firm review workflow.

Local OCR pushes Processing → Needs review / Auto-ready.
Until OCR finishes (or if it fails), the firm can still open the original file.
"""

from __future__ import annotations

from datetime import datetime, timezone

# Exact status strings used across the app (ledger JSON + UI).
STATUS_NEW = "New"  # legacy — treat as Processing in UI
STATUS_PROCESSING = "Processing"
STATUS_AI_PROCESSED = "AI processed"
STATUS_NEEDS_REVIEW = "Needs review"
STATUS_AUTO_READY = "Auto-ready"
STATUS_APPROVED = "Approved"
STATUS_REJECTED = "Rejected"
STATUS_SYNCED = "Synced"
STATUS_SYNC_FAILED = "Sync failed"

ALL_STATUSES = frozenset(
    {
        STATUS_NEW,
        STATUS_PROCESSING,
        STATUS_AI_PROCESSED,
        STATUS_NEEDS_REVIEW,
        STATUS_AUTO_READY,
        STATUS_APPROVED,
        STATUS_REJECTED,
        STATUS_SYNCED,
        STATUS_SYNC_FAILED,
    }
)

INBOX_QUEUE_STATUSES = frozenset(
    {STATUS_NEW, STATUS_PROCESSING, STATUS_AI_PROCESSED, STATUS_NEEDS_REVIEW, STATUS_AUTO_READY}
)

# Allowed transitions. Keys are current status; values are permitted next statuses.
ALLOWED_TRANSITIONS: dict[str, frozenset[str]] = {
    STATUS_NEW: frozenset(
        {STATUS_PROCESSING, STATUS_AI_PROCESSED, STATUS_NEEDS_REVIEW, STATUS_REJECTED}
    ),
    STATUS_PROCESSING: frozenset(
        {
            STATUS_AI_PROCESSED,
            STATUS_NEEDS_REVIEW,
            STATUS_AUTO_READY,
            STATUS_APPROVED,
            STATUS_REJECTED,
        }
    ),
    STATUS_AI_PROCESSED: frozenset(
        {STATUS_NEEDS_REVIEW, STATUS_AUTO_READY, STATUS_REJECTED}
    ),
    STATUS_NEEDS_REVIEW: frozenset({STATUS_APPROVED, STATUS_REJECTED, STATUS_AUTO_READY}),
    STATUS_AUTO_READY: frozenset(
        {STATUS_APPROVED, STATUS_REJECTED, STATUS_NEEDS_REVIEW, STATUS_SYNCED, STATUS_SYNC_FAILED}
    ),
    STATUS_APPROVED: frozenset({STATUS_SYNCED, STATUS_SYNC_FAILED, STATUS_REJECTED}),
    STATUS_REJECTED: frozenset({STATUS_NEEDS_REVIEW, STATUS_PROCESSING}),
    STATUS_SYNCED: frozenset(),
    STATUS_SYNC_FAILED: frozenset({STATUS_SYNCED, STATUS_APPROVED}),
}

REVIEWABLE_STATUSES = frozenset(
    {STATUS_PROCESSING, STATUS_NEEDS_REVIEW, STATUS_AUTO_READY, STATUS_NEW}
)
APPROVABLE_STATUSES = frozenset(
    {STATUS_PROCESSING, STATUS_NEEDS_REVIEW, STATUS_AUTO_READY, STATUS_NEW}
)
REJECTABLE_STATUSES = frozenset(
    {
        STATUS_NEW,
        STATUS_PROCESSING,
        STATUS_AI_PROCESSED,
        STATUS_NEEDS_REVIEW,
        STATUS_AUTO_READY,
        STATUS_APPROVED,
    }
)
SYNCABLE_STATUSES = frozenset({STATUS_APPROVED, STATUS_AUTO_READY, STATUS_SYNC_FAILED})
EDITABLE_STATUSES = frozenset(
    {
        STATUS_NEW,
        STATUS_PROCESSING,
        STATUS_AI_PROCESSED,
        STATUS_NEEDS_REVIEW,
        STATUS_AUTO_READY,
        STATUS_REJECTED,
        STATUS_APPROVED,
        STATUS_SYNC_FAILED,
    }
)


class WorkflowError(ValueError):
    """Invalid status transition or review action."""


def utc_now_iso() -> str:
    return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")


def today_iso() -> str:
    return datetime.now(timezone.utc).date().isoformat()


def assert_transition(current: str, next_status: str) -> None:
    if next_status not in ALL_STATUSES:
        raise WorkflowError(f"Unknown status: {next_status}")
    allowed = ALLOWED_TRANSITIONS.get(current, frozenset())
    if next_status not in allowed:
        raise WorkflowError(
            f"Cannot change status from “{current}” to “{next_status}”."
        )


def display_label(invoice_file: str, supplier: str | None) -> str:
    supplier_n = (supplier or "").strip()
    if supplier_n and supplier_n not in {"Pending extraction", "—", "-"}:
        return supplier_n
    return invoice_file or "Document"
