"""Communication platform domain models — Phase 8."""
from __future__ import annotations

import uuid
from datetime import datetime
from decimal import Decimal

from sqlalchemy import (
    Boolean,
    DateTime,
    Index,
    Integer,
    Numeric,
    String,
    Text,
    UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.types import JSON

from app.core.database import Base
from app.models.base import (
    ActorAuditMixin,
    SoftDeleteMixin,
    TenantMixin,
    TimestampMixin,
    UUIDPrimaryKeyMixin,
)
from app.models.types import GUID


class ProviderConfig(
    Base,
    UUIDPrimaryKeyMixin,
    TenantMixin,
    TimestampMixin,
    SoftDeleteMixin,
    ActorAuditMixin,
):
    """Tenant-specific communication provider credentials and routing metadata."""

    __tablename__ = "provider_configs"
    __table_args__ = (
        Index("ix_provider_configs_tenant_channel", "tenant_id", "channel"),
        Index("ix_provider_configs_tenant_priority", "tenant_id", "priority"),
    )

    name: Mapped[str] = mapped_column(String(120), nullable=False)
    provider_kind: Mapped[str] = mapped_column(String(40), nullable=False)
    channel: Mapped[str] = mapped_column(String(40), nullable=False)
    status: Mapped[str] = mapped_column(String(40), nullable=False, default="active")
    priority: Mapped[int] = mapped_column(Integer, nullable=False, default=100)
    credentials: Mapped[dict | None] = mapped_column(JSON, nullable=True)
    settings: Mapped[dict | None] = mapped_column(JSON, nullable=True)
    balance: Mapped[Decimal | None] = mapped_column(Numeric(18, 4), nullable=True)
    rate_limit_per_minute: Mapped[int | None] = mapped_column(Integer, nullable=True)
    max_retries: Mapped[int] = mapped_column(Integer, nullable=False, default=3)
    circuit_state: Mapped[str] = mapped_column(
        String(20), nullable=False, default="closed"
    )
    circuit_opened_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    consecutive_failures: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
    last_health_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    last_error: Mapped[str | None] = mapped_column(Text, nullable=True)
    is_default: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)


class SenderNumber(
    Base,
    UUIDPrimaryKeyMixin,
    TenantMixin,
    TimestampMixin,
    SoftDeleteMixin,
    ActorAuditMixin,
):
    __tablename__ = "sender_numbers"
    __table_args__ = (
        Index("ix_sender_numbers_tenant_channel", "tenant_id", "channel"),
        # App layer enforces uniqueness among non-deleted rows; DB unique is tenant+channel+value.
        UniqueConstraint(
            "tenant_id", "channel", "value", name="uq_sender_numbers_tenant_channel_value"
        ),
    )

    channel: Mapped[str] = mapped_column(String(40), nullable=False)
    value: Mapped[str] = mapped_column(String(64), nullable=False)
    label: Mapped[str | None] = mapped_column(String(120), nullable=True)
    provider_config_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    is_default: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
    is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)


class MessageTemplate(
    Base,
    UUIDPrimaryKeyMixin,
    TenantMixin,
    TimestampMixin,
    SoftDeleteMixin,
    ActorAuditMixin,
):
    __tablename__ = "message_templates"
    __table_args__ = (
        Index("ix_message_templates_tenant_key", "tenant_id", "template_key"),
        UniqueConstraint(
            "tenant_id",
            "template_key",
            "locale",
            "version",
            name="uq_message_templates_key_locale_version",
        ),
    )

    template_key: Mapped[str] = mapped_column(String(120), nullable=False)
    name: Mapped[str] = mapped_column(String(200), nullable=False)
    channel: Mapped[str] = mapped_column(String(40), nullable=False)
    locale: Mapped[str] = mapped_column(String(16), nullable=False, default="fa")
    version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
    status: Mapped[str] = mapped_column(String(40), nullable=False, default="draft")
    body: Mapped[str] = mapped_column(Text, nullable=False)
    subject: Mapped[str | None] = mapped_column(String(300), nullable=True)
    variables: Mapped[list | None] = mapped_column(JSON, nullable=True)
    approved_by: Mapped[str | None] = mapped_column(String(100), nullable=True)
    approved_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    rejection_reason: Mapped[str | None] = mapped_column(Text, nullable=True)


class ManualContact(
    Base,
    UUIDPrimaryKeyMixin,
    TenantMixin,
    TimestampMixin,
    SoftDeleteMixin,
    ActorAuditMixin,
):
    """Contacts owned by Communication only — no business-module data duplication."""

    __tablename__ = "manual_contacts"
    __table_args__ = (
        Index("ix_manual_contacts_tenant_phone", "tenant_id", "phone"),
        Index("ix_manual_contacts_tenant_email", "tenant_id", "email"),
    )

    display_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
    phone: Mapped[str | None] = mapped_column(String(32), nullable=True)
    email: Mapped[str | None] = mapped_column(String(255), nullable=True)
    push_token: Mapped[str | None] = mapped_column(String(512), nullable=True)
    metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
    tags: Mapped[list | None] = mapped_column(JSON, nullable=True)


class ContactSource(
    Base,
    UUIDPrimaryKeyMixin,
    TenantMixin,
    TimestampMixin,
    SoftDeleteMixin,
    ActorAuditMixin,
):
    """Dynamic contact source configuration — resolves contacts via API, never copies data."""

    __tablename__ = "contact_sources"
    __table_args__ = (
        Index("ix_contact_sources_tenant_type", "tenant_id", "source_type"),
    )

    name: Mapped[str] = mapped_column(String(120), nullable=False)
    source_type: Mapped[str] = mapped_column(String(40), nullable=False)
    base_url: Mapped[str | None] = mapped_column(String(500), nullable=True)
    endpoint_path: Mapped[str | None] = mapped_column(String(300), nullable=True)
    auth_config: Mapped[dict | None] = mapped_column(JSON, nullable=True)
    query_mapping: Mapped[dict | None] = mapped_column(JSON, nullable=True)
    field_mapping: Mapped[dict | None] = mapped_column(JSON, nullable=True)
    is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)


class Message(
    Base,
    UUIDPrimaryKeyMixin,
    TenantMixin,
    TimestampMixin,
    SoftDeleteMixin,
    ActorAuditMixin,
):
    __tablename__ = "messages"
    __table_args__ = (
        Index("ix_messages_tenant_status", "tenant_id", "status"),
        Index("ix_messages_tenant_channel", "tenant_id", "channel"),
        Index("ix_messages_tenant_created", "tenant_id", "created_at"),
        Index("ix_messages_tenant_correlation", "tenant_id", "correlation_id"),
        Index("ix_messages_tenant_provider_msg", "tenant_id", "provider_message_id"),
        UniqueConstraint(
            "tenant_id",
            "correlation_id",
            "to_address",
            name="uq_messages_tenant_correlation_to",
        ),
    )

    channel: Mapped[str] = mapped_column(String(40), nullable=False)
    status: Mapped[str] = mapped_column(String(40), nullable=False, default="queued")
    to_address: Mapped[str] = mapped_column(String(255), nullable=False)
    from_address: Mapped[str | None] = mapped_column(String(255), nullable=True)
    subject: Mapped[str | None] = mapped_column(String(300), nullable=True)
    body: Mapped[str] = mapped_column(Text, nullable=False)
    template_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    template_key: Mapped[str | None] = mapped_column(String(120), nullable=True)
    template_variables: Mapped[dict | None] = mapped_column(JSON, nullable=True)
    provider_config_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    provider_message_id: Mapped[str | None] = mapped_column(String(200), nullable=True)
    priority: Mapped[str] = mapped_column(String(20), nullable=False, default="normal")
    scheduled_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    sent_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    delivered_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    failed_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    expires_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    error_code: Mapped[str | None] = mapped_column(String(80), nullable=True)
    error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
    retry_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
    metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
    correlation_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
    source_module: Mapped[str | None] = mapped_column(String(80), nullable=True)


class QueueItem(
    Base,
    UUIDPrimaryKeyMixin,
    TenantMixin,
    TimestampMixin,
):
    """Append-only queue row — soft delete intentionally omitted."""

    __tablename__ = "queue_items"
    __table_args__ = (
        Index("ix_queue_items_tenant_status", "tenant_id", "status"),
        Index("ix_queue_items_priority_available", "priority", "available_at"),
    )

    message_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False, index=True)
    status: Mapped[str] = mapped_column(String(40), nullable=False, default="pending")
    priority: Mapped[str] = mapped_column(String(20), nullable=False, default="normal")
    attempt: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
    max_attempts: Mapped[int] = mapped_column(Integer, nullable=False, default=3)
    available_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False
    )
    locked_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    completed_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    last_error: Mapped[str | None] = mapped_column(Text, nullable=True)
    batch_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
    is_dead_letter: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)


class DeliveryEvent(
    Base,
    UUIDPrimaryKeyMixin,
    TenantMixin,
    TimestampMixin,
):
    """Immutable delivery timeline events for a message."""

    __tablename__ = "delivery_events"
    __table_args__ = (
        Index("ix_delivery_events_message", "tenant_id", "message_id", "created_at"),
    )

    message_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
    status: Mapped[str] = mapped_column(String(40), nullable=False)
    provider_config_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    detail: Mapped[str | None] = mapped_column(Text, nullable=True)
    payload: Mapped[dict | None] = mapped_column(JSON, nullable=True)


class ProviderLog(
    Base,
    UUIDPrimaryKeyMixin,
    TenantMixin,
    TimestampMixin,
):
    """Append-only provider I/O log — soft delete intentionally omitted."""

    __tablename__ = "provider_logs"
    __table_args__ = (
        Index("ix_provider_logs_tenant_provider", "tenant_id", "provider_config_id"),
        Index("ix_provider_logs_tenant_created", "tenant_id", "created_at"),
    )

    provider_config_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
    message_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    request_payload: Mapped[dict | None] = mapped_column(JSON, nullable=True)
    response_payload: Mapped[dict | None] = mapped_column(JSON, nullable=True)
    success: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
    latency_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
    error_message: Mapped[str | None] = mapped_column(Text, nullable=True)


class OTPChallenge(
    Base,
    UUIDPrimaryKeyMixin,
    TenantMixin,
    TimestampMixin,
):
    """Business/platform OTP challenges — independent from Core auth OTP."""

    __tablename__ = "otp_challenges"
    __table_args__ = (
        Index("ix_otp_challenges_tenant_destination", "tenant_id", "destination"),
        Index("ix_otp_challenges_tenant_status", "tenant_id", "status"),
    )

    channel: Mapped[str] = mapped_column(String(40), nullable=False, default="sms")
    destination: Mapped[str] = mapped_column(String(255), nullable=False)
    code_hash: Mapped[str] = mapped_column(String(128), nullable=False)
    status: Mapped[str] = mapped_column(String(40), nullable=False, default="pending")
    attempts: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
    max_attempts: Mapped[int] = mapped_column(Integer, nullable=False, default=5)
    expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
    verified_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    message_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    purpose: Mapped[str | None] = mapped_column(String(80), nullable=True)
    metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)


class WebhookReceipt(
    Base,
    UUIDPrimaryKeyMixin,
    TenantMixin,
    TimestampMixin,
):
    """Append-only inbound webhook receipt — soft delete intentionally omitted."""

    __tablename__ = "webhook_receipts"
    __table_args__ = (
        Index("ix_webhook_receipts_tenant_provider", "tenant_id", "provider_kind"),
        Index(
            "ix_webhook_receipts_tenant_external",
            "tenant_id",
            "provider_kind",
            "external_id",
        ),
        UniqueConstraint(
            "tenant_id",
            "provider_kind",
            "external_id",
            name="uq_webhook_receipts_tenant_provider_external",
        ),
    )

    provider_kind: Mapped[str] = mapped_column(String(40), nullable=False)
    provider_config_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    event_type: Mapped[str] = mapped_column(String(80), nullable=False)
    external_id: Mapped[str | None] = mapped_column(String(200), nullable=True)
    message_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    payload: Mapped[dict | None] = mapped_column(JSON, nullable=True)
    signature_valid: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
    processed: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
    processing_error: Mapped[str | None] = mapped_column(Text, nullable=True)


class CommunicationAuditLog(
    Base,
    UUIDPrimaryKeyMixin,
    TenantMixin,
    TimestampMixin,
):
    """Append-only audit trail — soft delete intentionally omitted."""

    __tablename__ = "communication_audit_logs"
    __table_args__ = (
        Index("ix_comm_audit_tenant_entity", "tenant_id", "entity_type", "entity_id"),
    )

    entity_type: Mapped[str] = mapped_column(String(80), nullable=False)
    entity_id: Mapped[str] = mapped_column(String(64), nullable=False)
    action: Mapped[str] = mapped_column(String(40), nullable=False)
    actor_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
    changes: Mapped[dict | None] = mapped_column(JSON, nullable=True)
    detail: Mapped[str | None] = mapped_column(Text, nullable=True)
