"""The WhatsApp commands a customer can trigger from their own dashboard.

Each is a thin translation: take the command's payload, ask the provider, hand
back something the CMS can render. The judgement lives in the provider (which
knows what it can do) and in the CMS (which knows about retries and history) —
deliberately not here.

ON RETURNING A QR CODE TO THE CMS

The customer's browser has to display it, and the browser talks to the CMS, so
the code has to travel. It is a credential in transit: short-lived by
construction, sent over the same signed channel as everything else, and the CMS
is responsible for dropping it once the link completes or the ninety seconds
pass. It must never reach a log on either side.
"""

from __future__ import annotations

import logging
from typing import Any

from app.whatsapp.session import (
    LinkRequest,
    SessionState,
    SessionStatus,
    status_of,
    supports_linking,
)
from app.workflow.commands import CommandResult

logger = logging.getLogger(__name__)


def _status_payload(status: SessionStatus) -> dict[str, Any]:
    """What the dashboard needs to draw a connection panel."""
    return {
        "state": status.state.value,
        "detail": status.detail,
        "number": status.number,
        "needs_attention": status.needs_attention,
    }


def connect(provider) -> CommandResult:
    """Begin linking an account, and return a code to scan.

    Never issues a code for an account that is already connected — doing so
    starts a fresh link and drops the working one, so a customer pressing
    Connect twice would disconnect themselves. The provider enforces that; this
    reports what it decided.
    """
    if not supports_linking(provider):
        # Meta, in practice. Permanent: no number of retries will grow a QR flow
        # onto an API that does not have one, and the customer needs telling
        # once rather than after three attempts.
        return CommandResult.failed(
            f"{getattr(provider, 'name', 'this provider')} cannot be linked by QR code. "
            "A WhatsApp Business connection is configured, not scanned.",
            retryable=False,
        )

    outcome = provider.start_link()

    if isinstance(outcome, SessionStatus):
        if outcome.state is SessionState.CONNECTED:
            # Already linked. A success, not a failure — the customer asked to
            # be connected and they are.
            return CommandResult.succeeded(already_connected=True, **_status_payload(outcome))

        if outcome.state is SessionState.LINK_EXPIRED:
            # A normal outcome, not a failure. The provider stopped offering
            # codes because nobody scanned one — retrying would produce the
            # same answer, and the screen needs to say "start again", not
            # "something went wrong".
            return CommandResult.succeeded(**_status_payload(outcome))

        return CommandResult.failed(outcome.detail or "the provider would not start a session")

    if not isinstance(outcome, LinkRequest):  # pragma: no cover - defensive
        return CommandResult.failed("the provider returned something unrecognised")

    # The rotation number, never the payload. A log line saying "code 4 of 30"
    # is useful; one containing the code is a credential on disk.
    logger.info(
        "Issued a WhatsApp link code (%s, expires %s).",
        f"{outcome.rotation} of {outcome.rotation_limit}" if outcome.rotation else "rotation unknown",
        outcome.expires_at.isoformat(),
    )

    return CommandResult.succeeded(
        # The only place this value is allowed to appear. Never logged.
        qr=outcome.payload,
        expires_at=outcome.expires_at.isoformat(),
        state=SessionState.AWAITING_SCAN.value,
        # So the screen can show the countdown the customer is otherwise
        # running down invisibly.
        rotation=outcome.rotation,
        rotation_limit=outcome.rotation_limit,
        rotations_left=outcome.rotations_left,
    )


def status(provider) -> CommandResult:
    """What the connection is doing right now.

    Always a success, even when the answer is "unavailable": the command was to
    find out, and finding out that the provider is unreachable is an answer. A
    failure here would be retried three times to learn the same thing.
    """
    return CommandResult.succeeded(**_status_payload(status_of(provider)))


def disconnect(provider) -> CommandResult:
    """Unlink, so the phone stops listing this deployment as a device."""
    if not supports_linking(provider):
        return CommandResult.failed(
            f"{getattr(provider, 'name', 'this provider')} has no device link to remove.",
            retryable=False,
        )

    outcome = provider.unlink()

    if outcome.state is SessionState.UNAVAILABLE:
        return CommandResult.failed(outcome.detail or "the provider could not be reached")

    return CommandResult.succeeded(**_status_payload(outcome))


def register(processor, provider) -> None:
    """Wire the handlers onto a processor.

    The provider is bound here rather than looked up per command: one deployment
    serves one installation and therefore one WhatsApp account (ADR-0001). A
    handler that selected a provider from a payload would be the first step
    toward multi-tenancy.
    """
    processor.register("whatsapp.connect", lambda _payload: connect(provider))
    processor.register("whatsapp.status", lambda _payload: status(provider))
    processor.register("whatsapp.disconnect", lambda _payload: disconnect(provider))
