"""Communication repositories."""
from __future__ import annotations

from datetime import datetime, timezone
from typing import Sequence
from uuid import UUID

from sqlalchemy import asc, desc, func, or_, select

from app.models.foundation import (
    CommunicationAuditLog,
    ContactSource,
    DeliveryEvent,
    ManualContact,
    Message,
    MessageTemplate,
    OTPChallenge,
    ProviderConfig,
    ProviderLog,
    QueueItem,
    SenderNumber,
    WebhookReceipt,
)
from app.repositories.base import TenantBaseRepository


class ProviderConfigRepository(TenantBaseRepository[ProviderConfig]):
    model = ProviderConfig

    async def list_by_channel(
        self, tenant_id: UUID, channel: str, *, active_only: bool = True
    ) -> Sequence[ProviderConfig]:
        clauses = [
            self.model.tenant_id == tenant_id,
            self.model.channel == channel,
            self.model.is_deleted.is_(False),
        ]
        if active_only:
            clauses.append(self.model.status.in_(["active", "degraded"]))
        stmt = (
            select(self.model)
            .where(*clauses)
            .order_by(asc(self.model.priority), asc(self.model.created_at))
        )
        result = await self.session.execute(stmt)
        return result.scalars().all()

    async def list_all_status(self, tenant_id: UUID) -> Sequence[ProviderConfig]:
        stmt = (
            select(self.model)
            .where(
                self.model.tenant_id == tenant_id,
                self.model.is_deleted.is_(False),
            )
            .order_by(asc(self.model.channel), asc(self.model.priority))
        )
        result = await self.session.execute(stmt)
        return result.scalars().all()


class SenderNumberRepository(TenantBaseRepository[SenderNumber]):
    model = SenderNumber

    async def get_default(self, tenant_id: UUID, channel: str) -> SenderNumber | None:
        stmt = select(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.channel == channel,
            self.model.is_default.is_(True),
            self.model.is_active.is_(True),
            self.model.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()


class MessageTemplateRepository(TenantBaseRepository[MessageTemplate]):
    model = MessageTemplate

    async def get_by_key(
        self,
        tenant_id: UUID,
        template_key: str,
        *,
        locale: str = "fa",
        approved_only: bool = True,
    ) -> MessageTemplate | None:
        clauses = [
            self.model.tenant_id == tenant_id,
            self.model.template_key == template_key,
            self.model.locale == locale,
            self.model.is_deleted.is_(False),
        ]
        if approved_only:
            clauses.append(self.model.status == "approved")
        stmt = (
            select(self.model)
            .where(*clauses)
            .order_by(desc(self.model.version))
            .limit(1)
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()

    async def list_versions(
        self, tenant_id: UUID, template_key: str, locale: str = "fa"
    ) -> Sequence[MessageTemplate]:
        stmt = (
            select(self.model)
            .where(
                self.model.tenant_id == tenant_id,
                self.model.template_key == template_key,
                self.model.locale == locale,
                self.model.is_deleted.is_(False),
            )
            .order_by(desc(self.model.version))
        )
        result = await self.session.execute(stmt)
        return result.scalars().all()


class ManualContactRepository(TenantBaseRepository[ManualContact]):
    model = ManualContact


class ContactSourceRepository(TenantBaseRepository[ContactSource]):
    model = ContactSource

    async def list_active(self, tenant_id: UUID) -> Sequence[ContactSource]:
        stmt = select(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.is_active.is_(True),
            self.model.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return result.scalars().all()


class MessageRepository(TenantBaseRepository[Message]):
    model = Message

    async def get_by_correlation(
        self, tenant_id: UUID, correlation_id: str
    ) -> Sequence[Message]:
        stmt = (
            select(self.model)
            .where(
                self.model.tenant_id == tenant_id,
                self.model.correlation_id == correlation_id,
                self.model.is_deleted.is_(False),
            )
            .order_by(asc(self.model.created_at))
        )
        result = await self.session.execute(stmt)
        return result.scalars().all()

    async def list_by_status(
        self, tenant_id: UUID, status: str, *, offset: int = 0, limit: int = 20
    ) -> Sequence[Message]:
        stmt = (
            select(self.model)
            .where(
                self.model.tenant_id == tenant_id,
                self.model.status == status,
                self.model.is_deleted.is_(False),
            )
            .order_by(desc(self.model.created_at))
            .offset(offset)
            .limit(limit)
        )
        result = await self.session.execute(stmt)
        return result.scalars().all()

    async def count_by_status(self, tenant_id: UUID) -> dict[str, int]:
        stmt = (
            select(self.model.status, func.count())
            .where(
                self.model.tenant_id == tenant_id,
                self.model.is_deleted.is_(False),
            )
            .group_by(self.model.status)
        )
        result = await self.session.execute(stmt)
        return {row[0]: int(row[1]) for row in result.all()}


class QueueItemRepository(TenantBaseRepository[QueueItem]):
    model = QueueItem

    async def enqueue(self, item: QueueItem) -> QueueItem:
        return await self.add(item)

    async def claim_next(
        self,
        tenant_id: UUID | None = None,
        *,
        limit: int = 10,
        increment_attempt: bool = True,
    ) -> Sequence[QueueItem]:
        now = datetime.now(timezone.utc)
        clauses = [
            self.model.status.in_(["pending", "scheduled"]),
            self.model.is_dead_letter.is_(False),
        ]
        if tenant_id is not None:
            clauses.append(self.model.tenant_id == tenant_id)
        from sqlalchemy import case

        priority_case = case(
            (self.model.priority == "urgent", 1),
            (self.model.priority == "high", 2),
            (self.model.priority == "normal", 3),
            else_=4,
        )
        stmt = (
            select(self.model)
            .where(*clauses)
            .order_by(asc(priority_case), asc(self.model.available_at))
            .limit(limit * 3)
        )
        bind = self.session.get_bind()
        if bind is not None and bind.dialect.name == "postgresql":
            stmt = stmt.with_for_update(skip_locked=True)
        result = await self.session.execute(stmt)
        candidates = list(result.scalars().all())
        items: list[QueueItem] = []
        for item in candidates:
            available = item.available_at
            if available.tzinfo is None:
                available = available.replace(tzinfo=timezone.utc)
            if available > now:
                continue
            item.status = "processing"
            item.locked_at = now
            if increment_attempt:
                item.attempt += 1
            items.append(item)
            if len(items) >= limit:
                break
        await self.session.flush()
        return items

    async def count_by_status(self, tenant_id: UUID) -> dict[str, int]:
        stmt = (
            select(self.model.status, func.count())
            .where(self.model.tenant_id == tenant_id)
            .group_by(self.model.status)
        )
        result = await self.session.execute(stmt)
        return {row[0]: int(row[1]) for row in result.all()}

    async def list_dead_letters(
        self, tenant_id: UUID, *, offset: int = 0, limit: int = 20
    ) -> Sequence[QueueItem]:
        stmt = (
            select(self.model)
            .where(
                self.model.tenant_id == tenant_id,
                or_(
                    self.model.is_dead_letter.is_(True),
                    self.model.status == "dead_letter",
                ),
            )
            .offset(offset)
            .limit(limit)
        )
        result = await self.session.execute(stmt)
        return result.scalars().all()


class DeliveryEventRepository(TenantBaseRepository[DeliveryEvent]):
    model = DeliveryEvent

    async def list_for_message(
        self, tenant_id: UUID, message_id: UUID
    ) -> Sequence[DeliveryEvent]:
        stmt = (
            select(self.model)
            .where(
                self.model.tenant_id == tenant_id,
                self.model.message_id == message_id,
            )
            .order_by(asc(self.model.created_at))
        )
        result = await self.session.execute(stmt)
        return result.scalars().all()


class ProviderLogRepository(TenantBaseRepository[ProviderLog]):
    model = ProviderLog


class OTPChallengeRepository(TenantBaseRepository[OTPChallenge]):
    model = OTPChallenge

    async def get_latest_pending(
        self, tenant_id: UUID, destination: str
    ) -> OTPChallenge | None:
        stmt = (
            select(self.model)
            .where(
                self.model.tenant_id == tenant_id,
                self.model.destination == destination,
                self.model.status == "pending",
            )
            .order_by(desc(self.model.created_at))
            .limit(1)
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()

    async def count_recent(
        self, tenant_id: UUID, destination: str, since: datetime
    ) -> int:
        stmt = select(func.count()).select_from(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.destination == destination,
            self.model.created_at >= since,
        )
        result = await self.session.execute(stmt)
        return int(result.scalar_one())


class WebhookReceiptRepository(TenantBaseRepository[WebhookReceipt]):
    model = WebhookReceipt


class AuditLogRepository(TenantBaseRepository[CommunicationAuditLog]):
    model = CommunicationAuditLog
