"""Sending a classified document to the workflow that handles it.

A lookup, deliberately. The routing decision is `registry.get(type).workflow` —
one field on the type, so registering a type and registering its route are the
same act. The brief asks that adding a document type require three steps; this
is what removes the third.

## Why a router at all, when it is one dict lookup

Because of what it replaces. Routing used to be an `if` in the inbox: *does the
caption carry an identifier? then bank statements, else the reading workflow*.
That worked for two paths and does not survive a third — each new type would be
another branch in a function that also downloads files and counts refusals, and
the coupling would be invisible until somebody added a type and nothing happened.

Here the decision is data, the fallback is explicit, and a type with no workflow
of its own still goes somewhere sensible.

## The fallback is not a failure

An unclassified document, or one whose type has no workflow, goes to the generic
review workflow: file it, attach what is known, let a human decide. That is the
correct outcome and it is worth saying plainly — a system that refuses to act on
what it does not recognise leaves the document nowhere, and the person who sent
it with nothing to look at.
"""

from __future__ import annotations

from dataclasses import dataclass

from app.documents import registry
from app.documents.classifier import UNKNOWN, Classification
from app.documents.registry import ProcessingStrategy


@dataclass(frozen=True, slots=True)
class Route:
    """Where a document goes, and what may be done to it on the way."""

    workflow: str
    reason: str

    reads_document: bool = True
    """Whether this document may be opened at all.

    Carried on the route rather than looked up again downstream, so a workflow
    cannot forget to ask. False for a bank statement — ADR-0002 — and the
    property belongs to the type, not to the workflow that happens to handle it.
    """

    strategy: ProcessingStrategy | None = None
    """How this document's client is worked out — or None if that is not yet
    decidable.

    None means the type is not known *yet*, and it is deliberately not a
    strategy of its own. An unclassified document has not earned a way of being
    handled; it is a temporary state, and the caller resolves it from what it
    does know (a PDF is held, a photograph is read) and re-decides on the real
    type as soon as there is one. Giving "unknown" its own strategy would freeze
    a guess into the routing table.
    """

    sensitive: bool = False


def route(classification: Classification) -> Route:
    """The workflow for a classified document."""
    if not classification.is_known:
        return Route(
            workflow=registry.GENERIC_WORKFLOW,
            reason=classification.reason
            or "Nothing recognised this document, so a person decides what it is.",
        )

    filing = registry.get(classification.filing_type)

    if filing is None:
        # A classification naming a type the registry does not have. Not
        # reachable today — the classifier only ever returns registry slugs —
        # and handled rather than asserted, because the alternative is a
        # KeyError inside a daemon loop over somebody's document.
        return Route(
            workflow=registry.GENERIC_WORKFLOW,
            reason=f"'{classification.filing_type}' is not a type this agent knows how to handle.",
        )

    return Route(
        workflow=filing.workflow,
        reason=f"{classification.label} is handled by {filing.workflow}.",
        reads_document=filing.reads_document,
        strategy=filing.strategy,
        sensitive=filing.sensitive,
    )


def workflows() -> dict[str, tuple[str, ...]]:
    """Which types each workflow serves.

    For the operator surfaces and for the test that asserts every workflow named
    in the registry actually exists. A type routed to a workflow nobody defined
    fails at the moment a real document arrives, which is the worst time to find
    out.
    """
    grouped: dict[str, list[str]] = {}

    for filing in registry.filing_types():
        grouped.setdefault(filing.workflow, []).append(filing.slug)

    return {name: tuple(slugs) for name, slugs in grouped.items()}
