"""Post and destination persistence."""

from __future__ import annotations

from typing import Any

from sqlalchemy import delete, func, select, update
from sqlalchemy.orm import selectinload

from app.database.models import (
    DestinationStatus,
    MediaAsset,
    Post,
    PostDestination,
    PostStatus,
    PublishChannel,
)
from app.database.repositories.base import BaseRepository


class PostRepository(BaseRepository):
    async def create(
        self,
        *,
        media_asset_id: int,
        created_by: int,
        title: str | None = None,
    ) -> Post:
        async with self.db.session() as session:
            post = Post(
                media_asset_id=media_asset_id,
                created_by=created_by,
                title=title,
                status=PostStatus.DRAFT,
            )
            session.add(post)
            await session.flush()
            return post

    async def get(self, post_id: int, *, full: bool = True) -> Post | None:
        async with self.db.session() as session:
            statement = select(Post).where(Post.id == post_id)
            if full:
                statement = statement.options(
                    selectinload(Post.media_asset).selectinload(MediaAsset.source_chat),
                    selectinload(Post.destinations).selectinload(PostDestination.channel),
                    selectinload(Post.required_channels),
                    selectinload(Post.access_tokens),
                )
            return await session.scalar(statement)

    async def list(
        self,
        *,
        statuses: list[PostStatus] | None = None,
        limit: int = 10,
        offset: int = 0,
    ) -> list[Post]:
        async with self.db.session() as session:
            statement = (
                select(Post)
                .options(selectinload(Post.media_asset))
                .order_by(Post.created_at.desc())
                .limit(limit)
                .offset(offset)
            )
            if statuses:
                statement = statement.where(Post.status.in_(statuses))
            return list(await session.scalars(statement))

    async def update(self, post_id: int, **fields: Any) -> bool:
        allowed = {
            "title",
            "description",
            "cover_file_id",
            "caption_override",
            "caption_template_override",
            "caption_prefix_override",
            "caption_suffix_override",
            "signature_override",
            "video_link_template_override",
            "buttons_override",
            "video_link",
            "status",
            "published_at",
            "scheduled_at",
            "auto_delete_enabled",
            "user_delete_after_seconds",
            "channel_delete_after_seconds",
            "membership_mode",
            "protect_content",
            "publish_count",
            "last_error",
        }
        values = {key: value for key, value in fields.items() if key in allowed}
        if not values:
            return False
        async with self.db.session() as session:
            post = await session.get(Post, post_id)
            if post is None:
                return False
            for key, value in values.items():
                setattr(post, key, value)
            return True

    async def set_status(
        self,
        post_id: int,
        status: PostStatus,
        *,
        last_error: str | None = None,
        published: bool = False,
    ) -> bool:
        async with self.db.session() as session:
            post = await session.get(Post, post_id)
            if post is None:
                return False
            post.status = status
            post.last_error = last_error
            if published:
                from app.database.base import utcnow

                post.published_at = utcnow()
            return True

    async def replace_destinations(self, post_id: int, channel_ids: list[int]) -> None:
        async with self.db.session() as session:
            await session.execute(
                delete(PostDestination).where(
                    PostDestination.post_id == post_id,
                    PostDestination.status == DestinationStatus.PENDING,
                )
            )
            existing = set(
                await session.scalars(
                    select(PostDestination.channel_id).where(PostDestination.post_id == post_id)
                )
            )
            for channel_id in dict.fromkeys(channel_ids):
                if channel_id in existing:
                    continue
                session.add(
                    PostDestination(
                        post_id=post_id,
                        channel_id=channel_id,
                        status=DestinationStatus.PENDING,
                    )
                )

    async def destinations(self, post_id: int) -> list[PostDestination]:
        async with self.db.session() as session:
            rows = await session.scalars(
                select(PostDestination)
                .options(selectinload(PostDestination.channel))
                .where(PostDestination.post_id == post_id)
                .order_by(PostDestination.id)
            )
            return list(rows)

    async def claim_destination(self, destination_id: int) -> bool:
        """Atomically claim a destination for sending.

        FAILED may be retried explicitly by an administrator. UNKNOWN is never
        auto-retried because Telegram may already have accepted the message.
        """

        async with self.db.session() as session:
            from app.database.base import utcnow

            result = await session.execute(
                update(PostDestination)
                .where(
                    PostDestination.id == destination_id,
                    PostDestination.status.in_(
                        [DestinationStatus.PENDING, DestinationStatus.FAILED]
                    ),
                )
                .values(status=DestinationStatus.SENDING, attempted_at=utcnow())
            )
            return bool(result.rowcount)

    async def recover_sending(self) -> int:
        async with self.db.session() as session:
            result = await session.execute(
                update(PostDestination)
                .where(PostDestination.status == DestinationStatus.SENDING)
                .values(
                    status=DestinationStatus.UNKNOWN,
                    error_message="عملیات ارسال به‌دلیل توقف غیرمنتظره نامعلوم شد.",
                )
            )
            return int(result.rowcount or 0)

    async def reset_failed_destinations(self, post_id: int) -> int:
        async with self.db.session() as session:
            result = await session.execute(
                update(PostDestination)
                .where(
                    PostDestination.post_id == post_id,
                    PostDestination.status == DestinationStatus.FAILED,
                )
                .values(status=DestinationStatus.PENDING, error_message=None)
            )
            return int(result.rowcount or 0)

    async def reset_destinations_for_republish(self, post_id: int) -> int:
        """Prepare every destination for an explicit, user-confirmed republish."""

        async with self.db.session() as session:
            result = await session.execute(
                update(PostDestination)
                .where(PostDestination.post_id == post_id)
                .values(
                    status=DestinationStatus.PENDING,
                    telegram_message_id=None,
                    error_message=None,
                    attempted_at=None,
                    published_at=None,
                    delete_at=None,
                    deleted_at=None,
                    delete_error=None,
                )
            )
            return int(result.rowcount or 0)

    async def get_destination(self, destination_id: int) -> PostDestination | None:
        async with self.db.session() as session:
            return await session.scalar(
                select(PostDestination)
                .options(selectinload(PostDestination.channel), selectinload(PostDestination.post))
                .where(PostDestination.id == destination_id)
            )

    async def update_destination(
        self, destination_id: int, **fields: Any
    ) -> bool:
        allowed = {
            "status",
            "telegram_message_id",
            "error_message",
            "attempted_at",
            "published_at",
            "delete_at",
            "deleted_at",
            "delete_error",
        }
        values = {key: value for key, value in fields.items() if key in allowed}
        if not values:
            return False
        async with self.db.session() as session:
            destination = await session.get(PostDestination, destination_id)
            if destination is None:
                return False
            for key, value in values.items():
                setattr(destination, key, value)
            return True

    async def count(self, statuses: list[PostStatus] | None = None) -> int:
        async with self.db.session() as session:
            statement = select(func.count(Post.id))
            if statuses:
                statement = statement.where(Post.status.in_(statuses))
            return int(await session.scalar(statement) or 0)

    async def delete(self, post_id: int) -> bool:
        async with self.db.session() as session:
            post = await session.get(Post, post_id)
            if post is None:
                return False
            await session.delete(post)
            return True

    async def destination_counts(self) -> dict[str, int]:
        async with self.db.session() as session:
            rows = await session.execute(
                select(PostDestination.status, func.count(PostDestination.id)).group_by(
                    PostDestination.status
                )
            )
            return {str(status.value): int(count) for status, count in rows}

    async def channel_post(self, destination_id: int) -> PostDestination | None:
        async with self.db.session() as session:
            return await session.scalar(
                select(PostDestination)
                .options(selectinload(PostDestination.channel))
                .where(PostDestination.id == destination_id)
            )

    async def channels_for_publish(self, post_id: int) -> list[PublishChannel]:
        async with self.db.session() as session:
            rows = await session.scalars(
                select(PublishChannel)
                .join(PostDestination, PostDestination.channel_id == PublishChannel.id)
                .where(PostDestination.post_id == post_id)
                .order_by(PostDestination.id)
            )
            return list(rows)
