import datetime
import json
from typing import List, Optional

from common.dao.chat_message import ChatMessageDao
from common.database.db import Db, DBConfig
from common.logging import get_logger, span
from common.platform.twitch.model import (
    TwitchChatMessage,
    MessageFragment,
    Badge,
    MessageContent,
)

logger = get_logger(__name__)


class TwitchChatMessageDAO(ChatMessageDao):
    """Data Access Object for Twitch chat messages in PostgreSQL."""

    def __init__(self, db: Db = None):
        """Initialize with an existing DB connection or create a new one."""
        self.db = db if db else Db(DBConfig())

    async def get_messages_by_time_range(
        self, broadcaster_user_id: str, start_timestamp: float, end_timestamp: float
    ) -> List[TwitchChatMessage]:
        """
        Get all chat messages for a broadcaster within a specific time range.

        Args:
            broadcaster_user_id: The broadcaster's user ID
            start_timestamp: Start timestamp in Unix format
            end_timestamp: End timestamp in Unix format

        Returns:
            List of TwitchChatMessage objects within the specified time range
        """
        with span(
            logger,
            "get_twitch_messages_by_time_range",
            {
                "broadcaster_id": broadcaster_user_id,
                "start_timestamp": start_timestamp,
                "end_timestamp": end_timestamp,
            },
        ):
            try:
                start_time = datetime.datetime.fromtimestamp(start_timestamp)
                end_time = datetime.datetime.fromtimestamp(end_timestamp)
                logger.debug(
                    f"Fetching Twitch chat messages for broadcaster {broadcaster_user_id} between {start_time} and {end_time}",
                    extra={
                        "broadcaster_id": broadcaster_user_id,
                        "start_timestamp": start_timestamp,
                        "end_timestamp": end_timestamp,
                    },
                )

                message_rows = await self.db.fetch_all(
                    """
                    SELECT *
                    FROM twitch_chat_messages
                    WHERE broadcaster_user_id = %s
                      AND created_at BETWEEN TIMESTAMP 'epoch' + (%s::double precision * INTERVAL '1 second')
                        AND TIMESTAMP 'epoch' + (%s::double precision * INTERVAL '1 second')
                    ORDER BY created_at
                    """,
                    (broadcaster_user_id, start_timestamp, end_timestamp),
                )

                messages = await self._build_messages_from_rows(message_rows)
                logger.info(
                    f"Retrieved {len(messages)} Twitch chat messages for broadcaster {broadcaster_user_id} in time range",
                    extra={
                        "broadcaster_id": broadcaster_user_id,
                        "start_timestamp": start_timestamp,
                        "end_timestamp": end_timestamp,
                        "message_count": len(messages),
                    },
                )
                return messages
            except Exception as e:
                logger.error(
                    f"Error retrieving Twitch chat messages for broadcaster {broadcaster_user_id}: {e}",
                    extra={
                        "broadcaster_id": broadcaster_user_id,
                        "start_timestamp": start_timestamp,
                        "end_timestamp": end_timestamp,
                        "error": str(e),
                    },
                )
                raise

    def _to_jsonb(self, data):
        if data is None:
            return None
        return json.dumps(data)

    async def save_message(self, message: TwitchChatMessage) -> str:
        """Save a complete chat message with its related entities."""
        with span(
            logger,
            "save_twitch_message",
            {
                "message_id": message.message_id,
                "broadcaster_id": message.broadcaster_user_id,
                "chatter_id": message.chatter_user_id,
            },
        ):
            try:
                # 1. Insert main message; we continue using the UUID for related inserts.
                await self.db.execute_commit(
                    """
                                             INSERT INTO twitch_chat_messages (message_id, broadcaster_user_id,
                                                                               broadcaster_user_login,
                                                                               broadcaster_user_name,
                                                                               chatter_user_id, chatter_user_login,
                                                                               chatter_user_name, message_text,
                                                                               message_type, color, created_at,
                                                                               cheer, reply,
                                                                               channel_points_custom_reward_id,
                                                                               source_broadcaster_user_id,
                                                                               source_broadcaster_user_login,
                                                                               source_broadcaster_user_name,
                                                                               source_message_id)
                                             VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
                                                     %s, %s)
                                             """,
                    (
                        message.message_id,
                        message.broadcaster_user_id,
                        message.broadcaster_user_login,
                        message.broadcaster_user_name,
                        message.chatter_user_id,
                        message.chatter_user_login,
                        message.chatter_user_name,
                        message.message.text,
                        message.message_type,
                        self._to_jsonb(message.color),  # Corrected line
                        datetime.datetime.fromtimestamp(message.created_at),
                        self._to_jsonb(message.cheer),
                        self._to_jsonb(message.reply),
                        message.channel_points_custom_reward_id,
                        message.source_broadcaster_user_id,
                        message.source_broadcaster_user_login,
                        message.source_broadcaster_user_name,
                        message.source_message_id,
                    ),
                )

                # 2. Insert message fragments using the UUID message_id (matches table definition)
                fragment_count = 0
                if message.message.fragments:
                    logger.debug(
                        f"Saving {len(message.message.fragments)} fragments for message {message.message_id}",
                        extra={
                            "message_id": message.message_id,
                            "fragment_count": len(message.message.fragments),
                        },
                    )
                    for i, fragment in enumerate(message.message.fragments):
                        await self.db.execute_commit(
                            """
                                                     INSERT INTO message_fragments (message_id, type, text,
                                                                                    cheermote, emote,
                                                                                    mention, fragment_order)
                                                     VALUES (%s, %s, %s, %s, %s, %s, %s)
                                                     """,
                            (
                                message.message_id,
                                fragment.type,
                                fragment.text,
                                self._to_jsonb(fragment.cheermote),
                                self._to_jsonb(fragment.emote),
                                self._to_jsonb(fragment.mention),
                                i,
                            ),
                        )
                        fragment_count += 1

                # 3. Insert badges using the UUID message_id
                badge_count = 0
                if message.badges:
                    logger.debug(
                        f"Saving {len(message.badges)} badges for message {message.message_id}",
                        extra={
                            "message_id": message.message_id,
                            "badge_count": len(message.badges),
                        },
                    )
                    for i, badge in enumerate(message.badges):
                        await self.db.execute_commit(
                            """
                                                     INSERT INTO message_badges (message_id, set_id, badge_id, info,
                                                                                 badge_order)
                                                     VALUES (%s, %s, %s, %s, %s)
                                                     """,
                            (message.message_id, badge.set_id, badge.id, badge.info, i),
                        )
                        badge_count += 1

                # 4. Insert source badges if present using the UUID message_id
                source_badge_count = 0
                if message.source_badges:
                    logger.debug(
                        f"Saving {len(message.source_badges)} source badges for message {message.message_id}",
                        extra={
                            "message_id": message.message_id,
                            "source_badge_count": len(message.source_badges),
                        },
                    )
                    for i, badge in enumerate(message.source_badges):
                        await self.db.execute_commit(
                            """
                                                     INSERT INTO source_badges (message_id, set_id, badge_id, info, badge_order)
                                                     VALUES (%s, %s, %s, %s, %s)
                                                     """,
                            (message.message_id, badge.set_id, badge.id, badge.info, i),
                        )
                        source_badge_count += 1

                logger.info(
                    f"Twitch message {message.message_id} persisted with {fragment_count} fragments, {badge_count} badges, and {source_badge_count} source badges",
                    extra={
                        "message_id": message.message_id,
                        "fragment_count": fragment_count,
                        "badge_count": badge_count,
                        "source_badge_count": source_badge_count,
                    },
                )
                return message.message_id
            except Exception as e:
                logger.error(
                    f"Could not save Twitch chat message: {e}",
                    extra={"message_id": message.message_id, "error": str(e)},
                )
                raise

    async def get_message_by_id(self, message_id: str) -> Optional[TwitchChatMessage]:
        """Retrieve a complete chat message by its ID."""
        with span(logger, "get_twitch_message_by_id", {"message_id": message_id}):
            try:
                # Get main message data
                logger.debug(
                    f"Fetching Twitch chat message with ID {message_id}",
                    extra={"message_id": message_id},
                )

                message_data = await self.db.fetch_one(
                    """
                                                       SELECT id,
                                                              message_id,
                                                              broadcaster_user_id,
                                                              broadcaster_user_login,
                                                              broadcaster_user_name,
                                                              chatter_user_id,
                                                              chatter_user_login,
                                                              chatter_user_name,
                                                              text,
                                                              message_type,
                                                              color,
                                                              created_at,
                                                              cheer,
                                                              reply,
                                                              channel_points_custom_reward_id,
                                                              source_broadcaster_user_id,
                                                              source_broadcaster_user_login,
                                                              source_broadcaster_user_name,
                                                              source_message_id
                                                       FROM twitch_chat_messages
                                                       WHERE message_id = %s
                                                       """,
                    (message_id,),
                )

                if not message_data:
                    logger.debug(
                        f"No Twitch chat message found with ID {message_id}",
                        extra={"message_id": message_id},
                    )
                    return None

                message = await self._build_message_from_data(message_data)
                logger.debug(
                    f"Successfully retrieved Twitch chat message with ID {message_id}",
                    extra={"message_id": message_id},
                )
                return message
            except Exception as e:
                logger.error(
                    f"Error retrieving Twitch chat message with ID {message_id}: {e}",
                    extra={"message_id": message_id, "error": str(e)},
                )
                raise

    async def get_messages_by_broadcaster(
        self, broadcaster_id: str, limit: int = 100, offset: int = 0
    ) -> List[TwitchChatMessage]:
        """Get messages by broadcaster ID with pagination."""
        message_rows = await self.db.fetch_all(
            """
                                               SELECT *
                                               FROM twitch_chat_messages
                                               WHERE broadcaster_user_id = %s
                                               ORDER BY created_at DESC
                                                   LIMIT %s
                                               OFFSET %s
                                               """,
            (broadcaster_id, limit, offset),
        )

        return await self._build_messages_from_rows(message_rows)

    async def get_messages_by_chatter(
        self, chatter_id: str, limit: int = 100, offset: int = 0
    ) -> List[TwitchChatMessage]:
        """Get messages by chatter ID with pagination."""
        message_rows = await self.db.fetch_all(
            """
                                               SELECT *
                                               FROM twitch_chat_messages
                                               WHERE chatter_user_id = %s
                                               ORDER BY created_at DESC
                                                   LIMIT %s
                                               OFFSET %s
                                               """,
            (chatter_id, limit, offset),
        )

        return await self._build_messages_from_rows(message_rows)

    async def get_messages_by_stream(
        self, stream_id: str, limit: int = 100, offset: int = 0
    ) -> List[TwitchChatMessage]:
        """Get messages by stream ID with pagination."""
        message_rows = await self.db.fetch_all(
            """
                                               SELECT *
                                               FROM twitch_chat_messages
                                               WHERE source_message_id = %s
                                               ORDER BY created_at DESC
                                                   LIMIT %s
                                               OFFSET %s
                                               """,
            (stream_id, limit, offset),
        )

        return await self._build_messages_from_rows(message_rows)

    async def count_messages_by_stream(self, stream_id: int) -> int:
        """Count messages by stream ID.

        Returns 0 if the stream has no associated Twitch account (e.g., Kick streams).
        """
        stream = await self.db.fetch_one(
            """
            SELECT s.stream_id, s.stream_start_time, s.stream_end_time, ta.twitch_id
            FROM streams s
            LEFT JOIN twitch_account ta ON s.user_id = ta.user_id
            WHERE s.id = %s
            """,
            (stream_id,),
        )
        if not stream:
            # Stream doesn't exist at all
            return 0
        broadcaster_user_id = stream[3]
        if not broadcaster_user_id:
            # Stream exists but user has no Twitch account (e.g., Kick stream)
            return 0
        start_time = stream[1]
        end_time = stream[2]
        # Convert to epoch seconds if the values are datetime objects.
        if isinstance(start_time, datetime.datetime):
            start_time = start_time.timestamp()
        if isinstance(end_time, datetime.datetime):
            end_time = end_time.timestamp()
        broadcaster_user_id = stream[3]
        chats = await self.get_messages_by_time_range(
            broadcaster_user_id, start_time, end_time
        )
        return len(chats) if chats else 0

    async def delete_message(self, message_id: str) -> bool:
        """Delete a message and all its related data."""
        with span(logger, "delete_twitch_message", {"message_id": message_id}):
            try:
                logger.debug(
                    f"Deleting Twitch chat message with ID {message_id}",
                    extra={"message_id": message_id},
                )

                # The foreign key constraints with CASCADE will handle deleting related data
                await self.db.execute_commit(
                    """
                                             DELETE
                                             FROM twitch_chat_messages
                                             WHERE message_id = %s
                                             """,
                    (message_id,),
                )

                logger.info(
                    f"Successfully deleted Twitch chat message with ID {message_id}",
                    extra={"message_id": message_id},
                )
                return True
            except Exception as e:
                logger.error(
                    f"Error deleting Twitch chat message with ID {message_id}: {e}",
                    extra={"message_id": message_id, "error": str(e)},
                )
                raise

    async def _build_messages_from_rows(self, rows) -> List[TwitchChatMessage]:
        """Build multiple message objects from database rows."""
        messages = []
        logger.debug(
            f"Building {len(rows)} Twitch chat messages from database rows",
            extra={"row_count": len(rows)},
        )
        for row in rows:
            message = await self._build_message_from_data(row)
            if message:
                messages.append(message)
        logger.debug(
            f"Successfully built {len(messages)} Twitch chat messages",
            extra={"message_count": len(messages)},
        )
        return messages

    async def _build_message_from_data(self, row) -> Optional[TwitchChatMessage]:
        try:
            # Use the internal id from the first column instead of the UUID string for related queries.
            row[0]  # Assuming row[0] contains the internal integer id
            uuid_message_id = row[1]  # The original UUID message id

            logger.debug(
                f"Building Twitch chat message from data for message ID {uuid_message_id}",
                extra={"message_id": uuid_message_id},
            )

            # Get fragments using the internal message id
            logger.debug(
                f"Fetching fragments for message ID {uuid_message_id}",
                extra={"message_id": uuid_message_id},
            )
            fragment_rows = (
                await self.db.fetch_all(
                    """
                SELECT type, text, cheermote, emote, mention
                FROM message_fragments
                WHERE message_id = %s
                ORDER BY fragment_order
                """,
                    (uuid_message_id,),
                )
                or []
            )

            fragments = []
            for f_row in fragment_rows:
                fragments.append(
                    MessageFragment(
                        type=f_row[0],
                        text=f_row[1],
                        cheermote=f_row[2],
                        emote=f_row[3],
                        mention=f_row[4],
                    )
                )
            logger.debug(
                f"Built {len(fragments)} fragments for message ID {uuid_message_id}",
                extra={"message_id": uuid_message_id, "fragment_count": len(fragments)},
            )

            # Get badges
            logger.debug(
                f"Fetching badges for message ID {uuid_message_id}",
                extra={"message_id": uuid_message_id},
            )
            badge_rows = (
                await self.db.fetch_all(
                    """
                SELECT set_id, badge_id, info
                FROM message_badges
                WHERE message_id = %s
                ORDER BY badge_order
                """,
                    (uuid_message_id,),
                )
                or []
            )

            badges = []
            for b_row in badge_rows:
                badges.append(Badge(set_id=b_row[0], id=b_row[1], info=b_row[2]))
            logger.debug(
                f"Built {len(badges)} badges for message ID {uuid_message_id}",
                extra={"message_id": uuid_message_id, "badge_count": len(badges)},
            )

            # Get source badges if any
            logger.debug(
                f"Fetching source badges for message ID {uuid_message_id}",
                extra={"message_id": uuid_message_id},
            )
            source_badge_rows = (
                await self.db.fetch_all(
                    """
                SELECT set_id, badge_id, info
                FROM source_badges
                WHERE message_id = %s
                ORDER BY badge_order
                """,
                    (uuid_message_id,),
                )
                or []
            )

            source_badges = []
            for sb_row in source_badge_rows:
                source_badges.append(
                    Badge(set_id=sb_row[0], id=sb_row[1], info=sb_row[2])
                )
            if not source_badges:
                source_badges = None
            else:
                logger.debug(
                    f"Built {len(source_badges)} source badges for message ID {uuid_message_id}",
                    extra={
                        "message_id": uuid_message_id,
                        "source_badge_count": len(source_badges),
                    },
                )

            # Create the message content
            message_content = MessageContent(text=row[8], fragments=fragments)

            message = TwitchChatMessage(
                broadcaster_user_id=row[2],
                broadcaster_user_login=row[3],
                broadcaster_user_name=row[4],
                chatter_user_id=row[5],
                chatter_user_login=row[6],
                chatter_user_name=row[7],
                message_id=uuid_message_id,
                message=message_content,
                message_type=row[9],
                color=row[10],
                created_at=row[11].timestamp() if row[11] else 0,
                cheer=row[12],
                reply=row[13],
                badges=badges,
                channel_points_custom_reward_id=row[14],
                source_broadcaster_user_id=row[15],
                source_broadcaster_user_login=row[16],
                source_broadcaster_user_name=row[17],
                source_message_id=row[18],
                source_badges=source_badges,
            )

            logger.debug(
                f"Successfully built Twitch chat message with ID {uuid_message_id}",
                extra={"message_id": uuid_message_id},
            )
            return message
        except Exception as e:
            logger.error(
                f"Error building Twitch chat message from data: {e}",
                extra={"error": str(e)},
            )
            raise
