"""Task API request/response schemas."""

from __future__ import annotations

from datetime import datetime
from typing import Any

from pydantic import BaseModel, Field, field_serializer

from awaithumans.server.schemas._datetime import utc_iso
from awaithumans.types import TaskStatus
from awaithumans.utils.constants import MAX_TIMEOUT_SECONDS, MIN_TIMEOUT_SECONDS


class CreateTaskRequest(BaseModel):
    task: str
    payload: dict[str, Any]
    payload_schema: dict[str, Any]
    response_schema: dict[str, Any]
    form_definition: dict[str, Any] | None = None
    timeout_seconds: int = Field(ge=MIN_TIMEOUT_SECONDS, le=MAX_TIMEOUT_SECONDS)
    idempotency_key: str
    assign_to: dict[str, Any] | None = None
    notify: list[str] | None = None
    verifier_config: dict[str, Any] | None = None
    redact_payload: bool = False
    callback_url: str | None = None
    # Optional caller-supplied context shown to reviewers verbatim
    # in Slack + on the dashboard. Free-form key-value strings —
    # nested structures are rejected here so the rendering surfaces
    # have one shape to format.
    task_metadata: dict[str, str] | None = Field(
        default=None,
        description=(
            "Free-form key-value context the reviewer should see "
            "when deciding how to complete the task. Examples: "
            "customer name, business vertical, escalation tier."
        ),
    )
    # AwaitVerify Flow A / Flow B: a pre-computed extraction of the
    # document that the reviewer should verify rather than fill from
    # scratch. The dashboard mounts the form with these values
    # pre-populated. Shape matches `response_schema`; managed
    # validates that match before forwarding here, so the OSS server
    # only asserts JSON-serializability and persists.
    initial_response: dict[str, Any] | None = Field(
        default=None,
        description=(
            "Optional pre-computed extraction (Flow A / Flow B). The "
            "reviewer dashboard renders the form with these values "
            "pre-filled so the human verifies/corrects rather than "
            "re-types. Null for pure-human review."
        ),
    )
    # AwaitVerify: clear the response from the server DB on successful
    # callback delivery. The customer's callback URL is the canonical
    # destination; the dashboard then shows a "delivered" placeholder
    # instead of the response content. Symmetric to (but independent
    # of) the request-side ``redact_payload`` flag.
    redact_response_after_submit: bool = Field(
        default=False,
        description=(
            "When true, the server clears ``response`` after the "
            "callback URL returns 2xx and stamps "
            "``response_redacted_at``. The dashboard read-back shows a "
            "placeholder; the audit log retains submission metadata."
        ),
    )


class CompleteTaskRequest(BaseModel):
    response: dict[str, Any]
    completed_by_email: str | None = None
    completed_via_channel: str | None = None


class TaskResponse(BaseModel):
    id: str
    idempotency_key: str
    task: str
    payload: dict[str, Any] | None = None
    payload_schema: dict[str, Any]
    response_schema: dict[str, Any]
    form_definition: dict[str, Any] | None = None
    task_metadata: dict[str, str] | None = None
    status: TaskStatus
    assign_to: dict[str, Any] | None = None
    assigned_to_email: str | None = None
    # Populated for tasks assigned to a directory user. The dashboard
    # uses these to render the assignee in lists and the detail panel:
    # `display_name → email → @<slack_user_id> → "—"`. Without these
    # fields a Slack-only assignee (no email) shows as blank in the
    # UI even when routing knows who they are.
    assigned_to_user_id: str | None = None
    assigned_to_display_name: str | None = None
    assigned_to_slack_user_id: str | None = None
    initial_response: dict[str, Any] | None = None
    response: dict[str, Any] | None = None
    # When non-null, ``response`` will be None (cleared on successful
    # callback delivery). The dashboard reads this field to choose the
    # "Response delivered" placeholder vs the structured read-back.
    response_redacted_at: datetime | None = None
    redact_response_after_submit: bool = False
    verifier_result: dict[str, Any] | None = None
    verification_attempt: int = 0
    timeout_seconds: int
    redact_payload: bool
    created_at: datetime
    updated_at: datetime
    completed_at: datetime | None = None
    timed_out_at: datetime | None = None
    completed_by_email: str | None = None
    # Mirror the assignee fallback chain so a Slack-only completer
    # (no email column) doesn't render as "—" in the audit log.
    # Populated via a join on User at response time.
    completed_by_user_id: str | None = None
    completed_by_display_name: str | None = None
    completed_by_slack_user_id: str | None = None
    completed_via_channel: str | None = None

    model_config = {"from_attributes": True}

    @field_serializer(
        "created_at",
        "updated_at",
        "completed_at",
        "timed_out_at",
        "response_redacted_at",
    )
    def _ser_dt(self, dt: datetime | None) -> str | None:
        return utc_iso(dt)


class PollResponse(BaseModel):
    status: str
    response: dict[str, Any] | None = None
    completed_at: datetime | None = None
    timed_out_at: datetime | None = None

    @field_serializer("completed_at", "timed_out_at")
    def _ser_dt(self, dt: datetime | None) -> str | None:
        return utc_iso(dt)
