from typing import Optional, List, Dict, Tuple

from common.database import DBConfig, Db
from common.logging import get_logger, span
from common.models.stream import StreamCreate, Stream, StreamUpdate

logger = get_logger(__name__)

SELECT_STREAM_QUERY = """
                      SELECT id,
                             user_id,
                             stream_id,
                             stream_start_time,
                             stream_end_time,
                             platform,
                             game_name
                      FROM streams \
                      """

SELECT_STREAM_BY_ID_QUERY = (
    SELECT_STREAM_QUERY
    + """
WHERE id = %s
"""
)

SELECT_STREAM_BY_EXTERNAL_ID_QUERY = (
    SELECT_STREAM_QUERY
    + """
WHERE stream_id = %s
"""
)

SELECT_ACTIVE_STREAMS_QUERY = (
    SELECT_STREAM_QUERY
    + """
WHERE stream_end_time IS NULL
"""
)

SELECT_ACTIVE_STREAMS_BY_USER_QUERY = (
    SELECT_STREAM_QUERY
    + """
WHERE stream_end_time IS NULL
AND user_id = %s
"""
)

SELECT_ACTIVE_STREAMS_BY_TWITCH_BROADCASTER_ID_QUERY = (
    SELECT_STREAM_QUERY
    + """
WHERE stream_end_time IS NULL
AND user_id = (SELECT user_id FROM twitch_account WHERE twitch_account.twitch_id = %s)
"""
)

SELECT_ACTIVE_STREAMS_WITH_DETAILS_QUERY = """
    SELECT s.id,
           s.user_id,
           s.stream_id,
           s.stream_start_time,
           s.stream_end_time,
           s.platform,
           s.game_name,
           CASE 
               WHEN s.platform = 'twitch' THEN ta.twitch_username
               WHEN s.platform = 'kick' THEN ka.kick_username
               ELSE NULL
           END as channel_name,
           CASE
               WHEN s.platform = 'twitch' THEN ta.twitch_id
               WHEN s.platform = 'kick' THEN ka.kick_id
               ELSE NULL
           END as platform_user_id
    FROM streams s
    LEFT JOIN twitch_account ta ON s.user_id = ta.user_id AND s.platform = 'twitch'
    LEFT JOIN kick_account ka ON s.user_id = ka.user_id AND s.platform = 'kick'
    WHERE s.stream_end_time IS NULL
    ORDER BY s.stream_start_time DESC
"""


class StreamDAO:
    """Data Access Object for streams in PostgreSQL."""

    def __init__(self, db: Optional[Db] = None):
        """Initialize with an existing DB connection or create a new one."""
        self.db = db if db else Db(DBConfig(pg_dsn=None))
        logger.debug("StreamDAO initialized", extra={"component": "StreamDAO"})

    async def create_stream(self, stream: StreamCreate) -> Stream | None:
        """Create a new stream record."""
        with span(
            logger,
            "create_stream",
            {
                "user_id": stream.user_id,
                "stream_id": stream.stream_id,
                "platform": stream.platform,
            },
        ):
            columns = [
                "user_id",
                "stream_id",
                "stream_start_time",
                "platform",
                "game_name",
                "stream_end_time",
            ]
            values = [
                stream.user_id,
                stream.stream_id,
                stream.stream_start_time,
                stream.platform,
                stream.game_name,
                stream.stream_end_time,
            ]

            try:
                result = await self.db.insert("streams", columns, tuple(values))

                if result:
                    logger.info(
                        f"Created stream record for {stream.platform} stream {stream.stream_id}",
                        extra={
                            "user_id": stream.user_id,
                            "stream_id": stream.stream_id,
                            "platform": stream.platform,
                            "game_name": stream.game_name,
                        },
                    )
                    return Stream(
                        id=result,
                        user_id=stream.user_id,
                        game_name=stream.game_name,
                        stream_id=stream.stream_id,
                        stream_start_time=stream.stream_start_time,
                        stream_end_time=stream.stream_end_time,
                        platform=stream.platform,
                    )
                else:
                    logger.error(
                        f"Failed to create stream record for {stream.platform} stream {stream.stream_id}",
                        extra={
                            "user_id": stream.user_id,
                            "stream_id": stream.stream_id,
                            "platform": stream.platform,
                        },
                    )
                    return None
            except Exception as e:
                logger.error(
                    f"Error creating stream record: {e}",
                    extra={
                        "user_id": stream.user_id,
                        "stream_id": stream.stream_id,
                        "platform": stream.platform,
                        "error": str(e),
                    },
                )
                return None

    async def get_stream_by_external_id(
        self, external_stream_id: str
    ) -> Optional[Stream]:
        """
        Get a stream using the external stream identifier.
        The streams table stores this in the stream_id column as a string.
        """
        with span(
            logger,
            "get_stream_by_external_id",
            {"external_stream_id": external_stream_id},
            log_entry=False,
            log_exit=False,
        ):
            try:
                row = await self.db.fetch_one(
                    SELECT_STREAM_BY_EXTERNAL_ID_QUERY, (external_stream_id,)
                )
                if not row:
                    logger.debug(
                        f"No stream found with external ID {external_stream_id}",
                        extra={"external_stream_id": external_stream_id},
                    )
                    return None

                stream = self._row_to_stream(row)
                logger.debug(
                    f"Found stream with external ID {external_stream_id}",
                    extra={
                        "external_stream_id": external_stream_id,
                        "user_id": stream.user_id,
                        "platform": stream.platform,
                    },
                )
                return stream
            except Exception as e:
                logger.error(
                    f"Error retrieving stream with external ID {external_stream_id}: {e}",
                    extra={"external_stream_id": external_stream_id, "error": str(e)},
                )
                return None

    async def get_stream_by_id(self, stream_id: int) -> Optional[Stream]:
        """Get a stream by its ID."""
        with span(logger, "get_stream_by_id", {"stream_id": stream_id}):
            try:
                row = await self.db.fetch_one(SELECT_STREAM_BY_ID_QUERY, (stream_id,))
                if not row:
                    logger.debug(
                        f"No stream found with ID {stream_id}",
                        extra={"stream_id": stream_id},
                    )
                    return None

                stream = self._row_to_stream(row)
                logger.debug(
                    f"Found stream with ID {stream_id}",
                    extra={
                        "stream_id": stream_id,
                        "user_id": stream.user_id,
                        "platform": stream.platform,
                        "external_stream_id": stream.stream_id,
                    },
                )
                return stream
            except Exception as e:
                logger.error(
                    f"Error retrieving stream with ID {stream_id}: {e}",
                    extra={"stream_id": stream_id, "error": str(e)},
                )
                return None

    async def get_active_streams(self) -> List[Stream]:
        """Get all active streams (where stream_end_time is NULL)."""
        with span(logger, "get_active_streams"):
            try:
                rows = await self.db.fetch_all(SELECT_ACTIVE_STREAMS_QUERY)

                if not rows:
                    logger.debug("No active streams found")
                    return []

                streams = [self._row_to_stream(row) for row in rows]
                logger.debug(
                    f"Found {len(streams)} active streams",
                    extra={"active_stream_count": len(streams)},
                )
                return streams
            except Exception as e:
                logger.error(
                    f"Error retrieving active streams: {e}", extra={"error": str(e)}
                )
                return []

    async def get_active_streams_by_user(self, user_id: int) -> List[Stream]:
        """Get all active streams for a specific user (where stream_end_time is NULL)."""
        with span(
            logger,
            "get_active_streams_by_user",
            {"user_id": user_id},
            log_entry=False,
            log_exit=False,
        ):
            try:
                rows = await self.db.fetch_all(
                    SELECT_ACTIVE_STREAMS_BY_USER_QUERY, (user_id,)
                )

                if not rows:
                    logger.debug(
                        "No active streams found for user",
                        extra={"user_id": user_id},
                    )
                    return []

                streams = [self._row_to_stream(row) for row in rows]
                logger.debug(
                    "Found active streams for user",
                    extra={
                        "user_id": user_id,
                        "active_stream_count": len(streams),
                    },
                )
                return streams
            except Exception as e:
                logger.error(
                    "Error retrieving active streams for user",
                    extra={"user_id": user_id, "error": str(e)},
                )
                return []

    async def get_active_stream_by_broadcaster_id(self, user_id: str) -> Stream:
        """Get all active streams for a specific broadcaster (where stream_end_time is NULL)."""
        with span(
            logger, "get_active_stream_by_broadcaster_id", {"broadcaster_id": user_id}
        ):
            try:
                row = await self.db.fetch_one(
                    SELECT_ACTIVE_STREAMS_BY_TWITCH_BROADCASTER_ID_QUERY, (user_id,)
                )
                if not row:
                    logger.warning(
                        f"No active stream found for broadcaster ID {user_id}",
                        extra={"broadcaster_id": user_id},
                    )
                    raise ValueError(f"No active stream found for user {user_id}")

                stream = self._row_to_stream(row)
                logger.debug(
                    f"Found active stream for broadcaster ID {user_id}",
                    extra={
                        "broadcaster_id": user_id,
                        "stream_id": stream.id,
                        "external_stream_id": stream.stream_id,
                        "platform": stream.platform,
                    },
                )
                return stream
            except Exception as e:
                if not isinstance(e, ValueError):
                    logger.error(
                        f"Error retrieving active stream for broadcaster ID {user_id}: {e}",
                        extra={"broadcaster_id": user_id, "error": str(e)},
                    )
                raise

    async def get_streams_by_user(self, user_id: int) -> List[Stream]:
        """Get all streams for a specific user."""
        with span(logger, "get_streams_by_user", {"user_id": user_id}):
            try:
                rows = await self.db.fetch_all(
                    SELECT_STREAM_QUERY
                    + " WHERE user_id = %s AND stream_end_time IS NOT NULL ORDER BY stream_end_time DESC",
                    (user_id,),
                )

                if not rows:
                    logger.debug(
                        f"No completed streams found for user ID {user_id}",
                        extra={"user_id": user_id},
                    )
                    return []

                streams = [self._row_to_stream(row) for row in rows]
                logger.debug(
                    f"Found {len(streams)} completed streams for user ID {user_id}",
                    extra={"user_id": user_id, "stream_count": len(streams)},
                )
                return streams
            except Exception as e:
                logger.error(
                    f"Error retrieving streams for user ID {user_id}: {e}",
                    extra={"user_id": user_id, "error": str(e)},
                )
                return []

    async def get_streams_by_user_paginated(
        self, user_id: int, cursor: Optional[str] = None, limit: int = 20
    ) -> Tuple[List[Stream], Optional[str], bool]:
        """
        Get streams for a user with cursor-based pagination.

        Args:
            user_id: User ID to fetch streams for
            cursor: Base64-encoded cursor from previous page (format: "id_timestamp")
            limit: Maximum number of streams to return (default 20)

        Returns:
            Tuple of (streams, next_cursor, has_more)
        """
        with span(
            logger,
            "get_streams_by_user_paginated",
            {"user_id": user_id, "has_cursor": cursor is not None, "limit": limit},
            log_entry=False,
            log_exit=False,
        ):
            try:
                # Decode cursor if present
                if cursor:
                    from base64 import b64decode

                    try:
                        decoded = b64decode(cursor).decode("utf-8")
                        cursor_id, cursor_timestamp = decoded.split("_")
                        cursor_id = int(cursor_id)
                        cursor_timestamp = int(cursor_timestamp)

                        query = (
                            SELECT_STREAM_QUERY
                            + """
                            WHERE user_id = %s
                              AND stream_end_time IS NOT NULL
                              AND (EXTRACT(EPOCH FROM stream_end_time), id) < (%s, %s)
                            ORDER BY stream_end_time DESC, id DESC
                            LIMIT %s
                        """
                        )
                        rows = await self.db.fetch_all(
                            query, (user_id, cursor_timestamp, cursor_id, limit + 1)
                        )
                    except Exception as e:
                        logger.error(
                            f"Invalid cursor format: {e}",
                            extra={"user_id": user_id, "cursor": cursor},
                        )
                        # Fall back to first page
                        cursor = None

                if not cursor:
                    # First page
                    query = (
                        SELECT_STREAM_QUERY
                        + """
                        WHERE user_id = %s
                          AND stream_end_time IS NOT NULL
                        ORDER BY stream_end_time DESC, id DESC
                        LIMIT %s
                    """
                    )
                    rows = await self.db.fetch_all(query, (user_id, limit + 1))

                if not rows:
                    logger.debug(
                        f"No completed streams found for user ID {user_id}",
                        extra={"user_id": user_id},
                    )
                    return [], None, False

                # Check if there are more results
                has_more = len(rows) > limit
                items = rows[:limit]

                # Generate next cursor from last item
                next_cursor = None
                if has_more and items:
                    from base64 import b64encode

                    last_item = items[-1]
                    last_id = last_item[0]  # id is first column
                    last_stream_end_time = last_item[4]  # stream_end_time is 5th column
                    if last_stream_end_time:
                        cursor_str = (
                            f"{last_id}_{int(last_stream_end_time.timestamp())}"
                        )
                        next_cursor = b64encode(cursor_str.encode("utf-8")).decode(
                            "utf-8"
                        )

                streams = [self._row_to_stream(row) for row in items]
                logger.debug(
                    f"Found {len(streams)} completed streams for user ID {user_id}",
                    extra={
                        "user_id": user_id,
                        "stream_count": len(streams),
                        "has_more": has_more,
                    },
                )
                return streams, next_cursor, has_more

            except Exception as e:
                logger.error(
                    f"Error retrieving paginated streams for user ID {user_id}: {e}",
                    extra={"user_id": user_id, "error": str(e)},
                )
                return [], None, False

    async def get_last_stream_by_user(self, user_id: int) -> Optional[Stream]:
        """Get the last stream for a specific user (where stream_end_time is not NULL)."""
        with span(logger, "get_last_stream_by_user", {"user_id": user_id}):
            try:
                row = await self.db.fetch_one(
                    SELECT_STREAM_QUERY
                    + " WHERE user_id = %s AND stream_end_time IS NOT NULL ORDER BY stream_end_time DESC LIMIT 1",
                    (user_id,),
                )

                if not row:
                    logger.debug(
                        f"No last completed stream found for user ID {user_id}",
                        extra={"user_id": user_id},
                    )
                    return None

                stream = self._row_to_stream(row)
                logger.debug(
                    f"Found last completed stream for user ID {user_id}",
                    extra={
                        "user_id": user_id,
                        "stream_id": stream.id,
                        "external_stream_id": stream.stream_id,
                        "platform": stream.platform,
                    },
                )
                return stream
            except Exception as e:
                logger.error(
                    f"Error retrieving last stream for user ID {user_id}: {e}",
                    extra={"user_id": user_id, "error": str(e)},
                )
                return None

    async def get_active_stream_by_user(self, user_id: int) -> Stream:
        """Get the first active stream for a specific user."""
        streams = await self.get_active_streams_by_user(user_id)
        if not streams:
            logger.warning(
                f"No active stream found for user ID {user_id}",
                extra={"user_id": user_id},
            )
            raise ValueError(f"No active stream found for user {user_id}")

        stream = streams[0]
        logger.debug(
            "Found active stream for user",
            extra={
                "user_id": user_id,
                "stream_id": stream.id,
                "external_stream_id": stream.stream_id,
                "platform": stream.platform,
            },
        )
        return stream

    async def update_stream(self, stream_id: str, update: StreamUpdate) -> None:
        """Update stream details."""
        with span(logger, "update_stream", {"stream_id": stream_id}):
            update_dict = update.dict(exclude_none=True)
            if not update_dict:
                logger.debug(
                    f"No updates provided for stream ID {stream_id}, skipping",
                    extra={"stream_id": stream_id},
                )
                return

            set_clauses = []
            values = []

            for key, value in update_dict.items():
                set_clauses.append(key)
                values.append(value)

            try:
                await self.db.update(
                    "streams",
                    set_clauses,
                    values,
                    where_clause="stream_id = %s",
                    where_params=[stream_id],
                )
                logger.info(
                    f"Updated stream {stream_id} with fields: {list(update_dict.keys())}",
                    extra={"stream_id": stream_id, "fields": list(update_dict.keys())},
                )
            except Exception as e:
                logger.error(
                    f"Error updating stream {stream_id}: {e}",
                    extra={"stream_id": stream_id, "error": str(e)},
                )
                raise

    async def end_stream(self, stream_id: int) -> None:
        """End an active stream by setting stream_end_time to current time.

        Args:
            stream_id: The database ID of the stream to end (NOT external stream_id)
        """
        with span(logger, "end_stream", {"stream_id": stream_id}):
            try:
                from datetime import datetime, timezone

                end_time = datetime.now(timezone.utc)
                await self.db.execute_commit(
                    """
                    UPDATE streams
                    SET stream_end_time = %s
                    WHERE id = %s AND stream_end_time IS NULL
                    """,
                    (end_time, stream_id),
                )
                logger.info(
                    "Stream ended successfully",
                    extra={
                        "stream_id": stream_id,
                        "end_time": end_time.isoformat(),
                    },
                )
            except Exception as e:
                logger.error(
                    f"Error ending stream {stream_id}: {e}",
                    extra={"stream_id": stream_id, "error": str(e)},
                )
                raise

    async def delete_stream(self, stream_id: str) -> None:
        """Delete a stream."""
        with span(logger, "delete_stream", {"stream_id": stream_id}):
            try:
                await self.db.execute_commit(
                    """
                                             DELETE
                                             FROM streams
                                             WHERE stream_id = %s
                                             """,
                    (stream_id,),
                )
                logger.info(
                    f"Deleted stream {stream_id}", extra={"stream_id": stream_id}
                )
            except Exception as e:
                logger.error(
                    f"Error deleting stream {stream_id}: {e}",
                    extra={"stream_id": stream_id, "error": str(e)},
                )
                raise

    async def get_active_streams_with_details(self) -> List[Dict]:
        """Get all active streams with channel names and platform user IDs."""
        with span(logger, "get_active_streams_with_details"):
            try:
                rows = await self.db.fetch_all(SELECT_ACTIVE_STREAMS_WITH_DETAILS_QUERY)

                if not rows:
                    logger.debug("No active streams found")
                    return []

                streams_with_details = []
                for row in rows:
                    stream_detail = {
                        "id": row[0],
                        "user_id": row[1],
                        "stream_id": row[2],
                        "stream_start_time": row[3],
                        "stream_end_time": row[4],
                        "platform": row[5],
                        "game_name": row[6],
                        "channel_name": row[7],
                        "platform_user_id": row[8],
                    }
                    streams_with_details.append(stream_detail)

                logger.debug(
                    f"Found {len(streams_with_details)} active streams with details",
                    extra={"active_stream_count": len(streams_with_details)},
                )
                return streams_with_details
            except Exception as e:
                logger.error(
                    f"Error retrieving active streams with details: {e}",
                    extra={"error": str(e)},
                )
                return []

    def _row_to_stream(self, row) -> Stream:
        """Convert a database row to a Stream model."""
        return Stream(
            id=row[0],
            user_id=row[1],
            stream_id=row[2],
            stream_start_time=row[3],
            stream_end_time=row[4],
            platform=row[5],
            game_name=row[6],
        )
