"""Pydantic schemas for Communication APIs.""" from __future__ import annotations from datetime import datetime from decimal import Decimal from typing import Any from uuid import UUID from pydantic import BaseModel, ConfigDict, Field class ORMModel(BaseModel): model_config = ConfigDict(from_attributes=True) class ProviderCreate(BaseModel): name: str provider_kind: str channel: str priority: int = 100 credentials: dict[str, Any] | None = None settings: dict[str, Any] | None = None balance: Decimal | None = None rate_limit_per_minute: int | None = None max_retries: int = 3 is_default: bool = False class ProviderUpdate(BaseModel): name: str | None = None priority: int | None = None credentials: dict[str, Any] | None = None settings: dict[str, Any] | None = None balance: Decimal | None = None rate_limit_per_minute: int | None = None max_retries: int | None = None status: str | None = None is_default: bool | None = None class ProviderOut(ORMModel): id: UUID tenant_id: UUID name: str provider_kind: str channel: str status: str priority: int balance: Decimal | None = None rate_limit_per_minute: int | None = None max_retries: int circuit_state: str consecutive_failures: int last_health_at: datetime | None = None last_error: str | None = None is_default: bool created_at: datetime updated_at: datetime class SenderCreate(BaseModel): channel: str value: str label: str | None = None provider_config_id: UUID | None = None is_default: bool = False is_active: bool = True class SenderOut(ORMModel): id: UUID tenant_id: UUID channel: str value: str label: str | None = None provider_config_id: UUID | None = None is_default: bool is_active: bool created_at: datetime class TemplateCreate(BaseModel): template_key: str name: str channel: str locale: str = "fa" body: str subject: str | None = None variables: list[str] | None = None class TemplateOut(ORMModel): id: UUID tenant_id: UUID template_key: str name: str channel: str locale: str version: int status: str body: str subject: str | None = None variables: list[str] | None = None approved_by: str | None = None approved_at: datetime | None = None rejection_reason: str | None = None created_at: datetime class TemplatePreviewRequest(BaseModel): variables: dict[str, Any] = Field(default_factory=dict) class TemplatePreviewResponse(BaseModel): rendered: str subject: str | None = None class ContactCreate(BaseModel): display_name: str | None = None phone: str | None = None email: str | None = None push_token: str | None = None metadata_json: dict[str, Any] | None = None tags: list[str] | None = None class ContactOut(ORMModel): id: UUID tenant_id: UUID display_name: str | None = None phone: str | None = None email: str | None = None push_token: str | None = None metadata_json: dict[str, Any] | None = None tags: list[str] | None = None created_at: datetime class ContactSourceCreate(BaseModel): name: str source_type: str base_url: str | None = None endpoint_path: str | None = None auth_config: dict[str, Any] | None = None query_mapping: dict[str, Any] | None = None field_mapping: dict[str, Any] | None = None is_active: bool = True class ContactSourceOut(ORMModel): id: UUID tenant_id: UUID name: str source_type: str base_url: str | None = None endpoint_path: str | None = None field_mapping: dict[str, Any] | None = None is_active: bool created_at: datetime class MessageSendRequest(BaseModel): channel: str = "sms" to_address: str | None = None from_address: str | None = None body: str | None = None subject: str | None = None template_key: str | None = None template_id: UUID | None = None template_variables: dict[str, Any] | None = None priority: str = "normal" scheduled_at: datetime | None = None correlation_id: str | None = None source_module: str | None = None metadata_json: dict[str, Any] | None = None contact_source_id: UUID | None = None contact_query: dict[str, Any] | None = None batch: bool = False process_immediately: bool = True class MessageOut(ORMModel): id: UUID tenant_id: UUID channel: str status: str to_address: str from_address: str | None = None subject: str | None = None body: str template_key: str | None = None provider_config_id: UUID | None = None provider_message_id: str | None = None priority: str scheduled_at: datetime | None = None sent_at: datetime | None = None delivered_at: datetime | None = None failed_at: datetime | None = None error_code: str | None = None error_message: str | None = None retry_count: int correlation_id: str | None = None source_module: str | None = None created_at: datetime class DeliveryEventOut(ORMModel): id: UUID message_id: UUID status: str provider_config_id: UUID | None = None detail: str | None = None payload: dict[str, Any] | None = None created_at: datetime class QueueItemOut(ORMModel): id: UUID message_id: UUID status: str priority: str attempt: int max_attempts: int available_at: datetime is_dead_letter: bool last_error: str | None = None batch_id: str | None = None created_at: datetime class OTPRequest(BaseModel): destination: str channel: str = "sms" purpose: str | None = None ttl_seconds: int | None = None code_length: int = 6 class OTPRequestOut(BaseModel): challenge_id: UUID destination: str channel: str expires_at: datetime message_id: UUID | None = None # Returned only in non-production for tests/dev debug_code: str | None = None class OTPVerifyRequest(BaseModel): destination: str code: str challenge_id: UUID | None = None class OTPVerifyOut(BaseModel): verified: bool challenge_id: UUID | None = None status: str class WebhookIn(BaseModel): provider_kind: str event_type: str external_id: str | None = None message_id: UUID | None = None payload: dict[str, Any] | None = None signature: str | None = None class WebhookOut(ORMModel): id: UUID provider_kind: str event_type: str external_id: str | None = None message_id: UUID | None = None signature_valid: bool processed: bool created_at: datetime class CapabilitiesOut(BaseModel): service: str version: str channels: list[dict[str, Any]] features: list[str] independent: bool = True requires_other_modules: bool = False class ProviderStatusOut(BaseModel): id: UUID name: str provider_kind: str channel: str status: str circuit_state: str consecutive_failures: int balance: Decimal | None = None rate_limit_per_minute: int | None = None last_health_at: datetime | None = None last_error: str | None = None healthy: bool class MonitoringStatsOut(BaseModel): messages_by_status: dict[str, int] queue_by_status: dict[str, int] providers: list[ProviderStatusOut] rate_limit_deferrals: int = 0 checked_at: str | None = None