from typing import Optional, List
import json
from common.models.stream_signal import TikTokSettings

from fastapi import HTTPException
from common.logging import get_logger, span
from common.database import DBConfig, Db
from common.models.clip import ClipBase
from common.models.common import Platform, UserAccessToken, UserAccessTokenUpdate
from common.models.content_scheduler import (
    PostStatus,
    ScheduledPostFetch,
    ScheduledPostCreate,
    ScheduledPostFilters,
    ScheduledPostUpdate,
    PlatformData,
    PlatformInfo,
    PostStatusElement,
)
from datetime import datetime, timezone
from .user import UserDAO


logger = get_logger(__name__)


class ScheduledPostDAO:
    """Data Access Object for scheduled posts 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(
            "ScheduledPostDAO initialized", extra={"component": "ScheduledPostDAO"}
        )

    def _coerce_json(self, raw, default):
        if raw is None:
            return default
        if isinstance(raw, (dict, list)):
            return raw
        if isinstance(raw, str):
            try:
                return json.loads(raw)
            except Exception:
                return default
        return default

    async def _insert_tiktok_settings(self, s: TikTokSettings) -> int:
        inserted_id = await self.db.insert(
            "tiktok_settings",
            [
                "comment",
                "duet",
                "stitch",
                "promotional_content",
                "promoting_yourself",
                "paid_partnership",
                "privacy_settings",
            ],
            (
                s.comment,
                s.duet,
                s.stitch,
                s.promotional_content,
                s.promoting_yourself,
                s.paid_partnership,
                s.privacy_settings,
            ),
        )
        if not inserted_id:
            raise HTTPException(
                status_code=500, detail="Failed to create tiktok_settings"
            )
        return int(inserted_id)

    async def _update_tiktok_settings(
        self, tiktok_settings_id: int, s: TikTokSettings
    ) -> None:
        await self.db.update(
            table="tiktok_settings",
            set_columns=[
                "comment",
                "duet",
                "stitch",
                "promotional_content",
                "promoting_yourself",
                "paid_partnership",
                "privacy_settings",
            ],
            set_values=[
                s.comment,
                s.duet,
                s.stitch,
                s.promotional_content,
                s.promoting_yourself,
                s.paid_partnership,
                s.privacy_settings,
            ],
            where_clause="id = %s",
            where_params=(tiktok_settings_id,),
        )

    async def _resolve_tiktok_settings_id_for_create(
        self, tiktok: Optional[TikTokSettings]
    ) -> Optional[int]:
        # Not provided => FK NULL
        if tiktok is None:
            return None

        # id None => create
        if tiktok.id is None:
            return await self._insert_tiktok_settings(tiktok)

        # id provided => update then link (same approach you used)
        await self._update_tiktok_settings(int(tiktok.id), tiktok)
        return int(tiktok.id)

    async def _resolve_tiktok_settings_id_for_update(
        self,
        current_tiktok_settings_id: Optional[int],
        tiktok: Optional[TikTokSettings],
    ) -> Optional[int]:
        # Explicit null => FK NULL
        if tiktok is None:
            return None

        # First time setup => create new row and link
        if tiktok.id is None:
            return await self._insert_tiktok_settings(tiktok)

        # id provided => must match current linked id (safety)
        if current_tiktok_settings_id != int(tiktok.id):
            raise HTTPException(
                status_code=422,
                detail="tiktok_settings.id must match the currently linked settings id",
            )

        await self._update_tiktok_settings(int(tiktok.id), tiktok)
        return int(tiktok.id)

    async def _get_post_platform_names(self, post_id: int) -> set[str]:
        rows = await self.db.fetch_all(
            """
            SELECT p.name
            FROM scheduled_post_platforms spp
            JOIN platforms p ON p.id = spp.platform_id
            WHERE spp.scheduled_post_id = %s
            """,
            (post_id,),
        )
        return {r[0] for r in (rows or [])}

    def _require_tiktok_in_platforms_if_settings(
        self, platforms: set[str], tiktok_settings: Optional[TikTokSettings]
    ) -> None:
        if tiktok_settings is not None and "tiktok" not in platforms:
            raise HTTPException(
                status_code=422,
                detail="tiktok_settings requires Platform.TIKTOK in platforms",
            )

    async def get_all_platforms(self) -> List[PlatformInfo]:
        """Fetch all platforms"""
        with span(logger, "get_all_platforms"):
            try:
                rows = await self.db.fetch_all(
                    "SELECT id,name, is_enabled FROM platforms"
                )
                if not rows:
                    logger.debug("No scheduled posts found for filters")
                    return []
                results = []
                for row in rows:
                    results.append(
                        PlatformInfo(
                            name=row[1],
                            is_enabled=row[2],
                        )
                    )
                return results
            except Exception as e:
                raise HTTPException(
                    status_code=500, detail=f"Error fetching platforms: {e}"
                )

    async def update_scheduled_post(
        self, post_id: int, user_id: int, update_data: ScheduledPostUpdate
    ) -> ScheduledPostFetch | None:
        """Update a scheduled post if it is still in 'scheduled' status and only update the provided fields."""
        with span(logger, "update_scheduled_post", {"post_id": post_id}):
            row = await self.db.fetch_one(
                """
                SELECT sp.id, sp.tiktok_settings_id
                FROM scheduled_post sp
                JOIN post_statuses ps ON sp.status_id = ps.id
                WHERE sp.id = %s AND ps.status = 'scheduled' AND sp.user_id = %s
                """,
                (post_id, user_id),
            )

            if row is None:
                logger.warning(f"Post {post_id} not found or not in scheduled status.")
                raise HTTPException(
                    status_code=422,
                    detail=f"Post {post_id} not found or not in scheduled status.",
                )

            current_tiktok_settings_id = row[1]

            columns = []
            values = []

            if update_data.scheduled_datetime is not None:
                if update_data.scheduled_datetime <= datetime.now(timezone.utc):
                    raise HTTPException(
                        status_code=422,
                        detail="Scheduled datetime must be in the future.",
                    )
                columns.append("scheduled_datetime")
                values.append(update_data.scheduled_datetime)

            if update_data.title is not None:
                columns.append("title")
                values.append(update_data.title)

            if update_data.caption is not None:
                columns.append("caption")
                values.append(update_data.caption)

            if update_data.status is not None:
                status_row = await self.db.fetch_one(
                    "SELECT id FROM post_statuses WHERE status = %s",
                    (update_data.status.value,),
                )
                if not status_row:
                    raise HTTPException(
                        status_code=422, detail=f"Invalid status: {update_data.status}"
                    )
                columns.append("status_id")
                values.append(status_row[0])

            if update_data.platforms is not None:
                active_platforms = await self.db.fetch_all(
                    "SELECT id,name FROM platforms WHERE is_enabled  = true"
                )
                active_ids = {row[1] for row in active_platforms}
                requested_ids = {platform.value for platform in update_data.platforms}
                invalid_ids = requested_ids - active_ids
                if invalid_ids:
                    raise HTTPException(
                        status_code=422,
                        detail=f"Invalid or inactive platform IDs: {invalid_ids}",
                    )
            if update_data.platforms is not None:
                requested_platforms = {p.value for p in update_data.platforms}
            else:
                requested_platforms = await self._get_post_platform_names(post_id)

            if "tiktok_settings" in update_data.model_fields_set:
                self._require_tiktok_in_platforms_if_settings(
                    requested_platforms, update_data.tiktok_settings
                )

                tiktok_settings_id = await self._resolve_tiktok_settings_id_for_update(
                    current_tiktok_settings_id=current_tiktok_settings_id,
                    tiktok=update_data.tiktok_settings,
                )
                columns.append("tiktok_settings_id")
                values.append(tiktok_settings_id)

            if not columns and update_data.platforms is None:
                logger.info("No fields provided to update.")
                raise HTTPException(
                    status_code=422,
                    detail=f"No fields provided to update post id {post_id}.",
                )

            try:
                if columns:
                    await self.db.update(
                        table="scheduled_post",
                        set_columns=columns,
                        set_values=values,
                        where_clause="id = %s",
                        where_params=(post_id,),
                    )

                if update_data.platforms is not None:
                    current_platform_rows = await self.db.fetch_all(
                        "SELECT platform_id, status_id FROM scheduled_post_platforms WHERE scheduled_post_id = %s",
                        (post_id,),
                    )
                    current_platform_ids = {row[0] for row in current_platform_rows}

                    statuses = await self.db.fetch_all(
                        "SELECT id, status FROM post_statuses"
                    )
                    status_map = {row[1]: row[0] for row in statuses}
                    status_scheduled = status_map["scheduled"]
                    status_cancelled = status_map["cancelled"]

                    requested_names = {p.value for p in update_data.platforms}

                    requested_ids = {
                        id for (id, name) in active_platforms if name in requested_names
                    }

                    to_insert = requested_ids - current_platform_ids
                    to_cancel = current_platform_ids - requested_ids
                    to_reschedule = current_platform_ids & requested_ids

                    for platform_id in to_insert:
                        await self.db.execute(
                            """
                            INSERT INTO scheduled_post_platforms (scheduled_post_id, platform_id, status_id)
                            VALUES (%s, %s, %s)
                            """,
                            (post_id, platform_id, status_scheduled),
                        )

                    for platform_id in to_cancel:
                        await self.db.execute(
                            """
                            UPDATE scheduled_post_platforms
                            SET status_id = %s
                            WHERE scheduled_post_id = %s AND platform_id = %s
                            """,
                            (status_cancelled, post_id, platform_id),
                        )

                    for platform_id in to_reschedule:
                        await self.db.execute(
                            """
                            UPDATE scheduled_post_platforms
                            SET status_id = %s
                            WHERE scheduled_post_id = %s AND platform_id = %s
                            """,
                            (status_scheduled, post_id, platform_id),
                        )

                logger.info(f"Scheduled post {post_id} updated successfully.")
                return await self.get_one_scheduled_post(post_id)

            except Exception as e:
                logger.error(f"Error updating scheduled post {post_id}: {e}")
                raise HTTPException(
                    status_code=500,
                    detail=f"Error updating scheduled post {post_id}: {e}",
                )

    async def create_scheduled_post(
        self, post: ScheduledPostCreate
    ) -> ScheduledPostFetch:
        with span(
            logger,
            "create_scheduled_post",
            {
                "user_id": post.user_id,
                "clip_id": post.clip_id,
                "platforms": post.platforms,
            },
        ):
            now = datetime.now(timezone.utc)

            if post.scheduled_datetime <= now:
                raise HTTPException(
                    status_code=500, detail="Scheduled time must be in the future."
                )

            status_row = await self.db.fetch_one(
                "SELECT id FROM post_statuses WHERE status = %s",
                [PostStatus.SCHEDULED.value],
            )
            if not status_row:
                raise HTTPException(
                    status_code=500,
                    detail=f"Status '{PostStatus.SCHEDULED.value}' not found on database.",
                )
            scheduled_status_id = status_row[0]

            db_platforms = await self.db.fetch_all(
                "SELECT id, name FROM platforms WHERE is_enabled = TRUE"
            )
            db_platform_map = {row[1]: row[0] for row in db_platforms}

            invalid_platforms = [
                p.value for p in post.platforms if p.value not in db_platform_map
            ]
            if invalid_platforms:
                raise HTTPException(
                    status_code=500,
                    detail=f"Invalid or disabled platforms: {invalid_platforms}",
                )

            requested_platforms = {p.value for p in post.platforms}
            self._require_tiktok_in_platforms_if_settings(
                requested_platforms, post.tiktok_settings
            )

            tiktok_settings_id = await self._resolve_tiktok_settings_id_for_create(
                post.tiktok_settings
            )

            columns = [
                "user_id",
                "clip_id",
                "caption",
                "scheduled_datetime",
                "status_id",
                "title",
                "created_at",
                "updated_at",
                "tiktok_settings_id",
            ]
            values = (
                post.user_id,
                post.clip_id,
                post.caption,
                post.scheduled_datetime,
                scheduled_status_id,
                post.title,
                datetime.utcnow(),
                datetime.utcnow(),
                tiktok_settings_id,
            )

            scheduled_post_id = await self.db.insert("scheduled_post", columns, values)
            if not isinstance(scheduled_post_id, int):
                raise HTTPException(
                    status_code=500, detail="Failed to create scheduled_post"
                )

            inserted_platforms = []

            for platform in post.platforms:
                platform_id = db_platform_map[platform.value]
                await self.db.insert(
                    "scheduled_post_platforms",
                    ["scheduled_post_id", "platform_id", "status_id"],
                    [scheduled_post_id, platform_id, scheduled_status_id],
                )
                inserted_platforms.append(platform)

            return ScheduledPostFetch(
                id=scheduled_post_id,
                user_id=post.user_id,
                clip_id=post.clip_id,
                title=post.title,
                caption=post.caption,
                status=PostStatus.SCHEDULED,
                scheduled_datetime=post.scheduled_datetime,
                platforms=[
                    PlatformData(platform=platform, status=PostStatus.SCHEDULED)
                    for platform in inserted_platforms
                ],
                tiktok_settings=post.tiktok_settings if tiktok_settings_id else None,
            )

    async def get_all_scheduled_posts(
        self, filters: ScheduledPostFilters
    ) -> List[ScheduledPostFetch]:
        """Get all scheduled posts by user_id and date range, including platform info as array."""
        with span(
            logger,
            "get_all_scheduled_posts",
            {
                "user_id": filters.user_id,
                "date_ini": filters.date_ini,
                "date_end": filters.date_end,
            },
        ):
            query = """
                SELECT 
                    sp.id AS scheduled_post_id,
                    sp.user_id,
                    sp.clip_id,
                    sp.title,
                    sp.caption,
                    sp.scheduled_datetime,
                    psp.status,
                    c.url,
                    c.kind,
                    c.stream_id,
                    c.thumbnail_url,
                    c.watermark_url,
                    c.caption AS clip_caption,
                    c.favorited,
                    c.views,
                    c.start_time,
                    c.owner_username,
                    c.user_id AS clip_user_id,
                    COALESCE(
                        json_agg(
                            json_build_object(
                                'platform', json_build_object('name', p.name),
                                'status', json_build_object('status', ps.status)
                            )
                        ) FILTER (WHERE p.id IS NOT NULL AND ps.id IS NOT NULL),
                        '[]'
                    ) AS platforms,
                    (
                        SELECT CASE
                            WHEN ts.id IS NULL THEN NULL
                            ELSE json_build_object(
                                'id', ts.id,
                                'comment', ts.comment,
                                'duet', ts.duet,
                                'stitch', ts.stitch,
                                'promotional_content', ts.promotional_content,
                                'promoting_yourself', ts.promoting_yourself,
                                'paid_partnership', ts.paid_partnership,
                                'privacy_settings', ts.privacy_settings
                            )
                        END
                        FROM tiktok_settings ts
                        WHERE ts.id = sp.tiktok_settings_id
                    ) AS tiktok_settings
                FROM scheduled_post sp
                LEFT JOIN clips c ON sp.clip_id = c.id
                LEFT JOIN scheduled_post_platforms spp ON sp.id = spp.scheduled_post_id
                LEFT JOIN platforms p ON spp.platform_id = p.id
                LEFT JOIN post_statuses ps ON spp.status_id = ps.id
                LEFT JOIN post_statuses psp ON sp.status_id = psp.id
                WHERE sp.user_id = %s
                AND sp.scheduled_datetime >= %s
                AND sp.scheduled_datetime <= %s
                AND sp.deleted_at IS NULL
                GROUP BY 
                    sp.id,
                    sp.user_id,
                    sp.clip_id,
                    sp.title,
                    sp.caption,
                    sp.scheduled_datetime,
                    psp.status,
                    c.url,
                    c.kind,
                    c.stream_id,
                    c.thumbnail_url,
                    c.watermark_url,
                    c.caption,
                    c.favorited,
                    c.views,
                    c.start_time,
                    c.owner_username,
                    c.user_id
                ORDER BY sp.scheduled_datetime
            """
            try:
                rows = await self.db.fetch_all(
                    query,
                    [
                        filters.user_id,
                        filters.date_ini,
                        filters.date_end,
                    ],
                )

                if not rows:
                    logger.debug("No scheduled posts found for filters")
                    return []

                logger.info("Fetching scheduled posts:", extra={"rows": rows})

                results = []
                for row in rows:
                    platforms = row[18]
                    raw_tiktok = self._coerce_json(row[19], None)
                    tiktok_settings = (
                        TikTokSettings(**raw_tiktok) if raw_tiktok else None
                    )
                    results.append(
                        ScheduledPostFetch(
                            id=row[0],
                            user_id=row[1],
                            clip_id=row[2],
                            title=row[3],
                            caption=row[4],
                            scheduled_datetime=row[5],
                            status=row[6],
                            platforms=[
                                PlatformData(
                                    platform=Platform(entry["platform"]["name"]),
                                    status=PostStatus(entry["status"]["status"]),
                                )
                                for entry in platforms
                            ],
                            clip=ClipBase(
                                url=row[7],
                                kind=row[8],
                                stream_id=row[9],
                                thumbnail_url=row[10],
                                watermark_url=row[11],
                                caption=row[12],
                                favorited=row[13],
                                views=row[14],
                                start_time=row[15],
                                owner_username=row[16],
                                user_id=row[17],
                            )
                            if row[7] and row[8]
                            else None,
                            tiktok_settings=tiktok_settings,
                        ),
                    )

                return results

            except Exception as e:
                logger.error(
                    f"Error fetching scheduled posts: {e}", extra={"error": str(e)}
                )
                raise HTTPException(
                    status_code=500, detail=f"Error fetching scheduled posts: {e}"
                )

    async def get_due_scheduled_posts(self) -> List[ScheduledPostFetch]:
        """Get all scheduled posts that are due to be published now or earlier."""
        with span(logger, "get_due_scheduled_posts"):
            query = """
                SELECT 
                    sp.id AS scheduled_post_id,
                    sp.user_id,
                    ec.id as clip_id,
                    sp.title,
                    sp.caption,
                    sp.scheduled_datetime,
                    ec.url,
                    c.kind,
                    c.stream_id,
                    c.thumbnail_url,
                    c.watermark_url,
                    ec.name AS clip_caption,
                    c.favorited,
                    c.views,
                    c.start_time,
                    c.owner_username,
                    c.user_id AS clip_user_id,
                    COALESCE(
                        json_agg(
                            json_build_object(
                                'platform', json_build_object('name', p.name),
                                'status', json_build_object('status', ps.status),
                                'id', json_build_object('id',spp.id)
                            )
                        ) FILTER (WHERE p.id IS NOT NULL AND ps.id IS NOT NULL),
                        '[]'
                    ) AS platforms,
                    (
                        SELECT CASE
                            WHEN ts.id IS NULL THEN NULL
                            ELSE json_build_object(
                                'id', ts.id,
                                'comment', ts.comment,
                                'duet', ts.duet,
                                'stitch', ts.stitch,
                                'promotional_content', ts.promotional_content,
                                'promoting_yourself', ts.promoting_yourself,
                                'paid_partnership', ts.paid_partnership,
                                'privacy_settings', ts.privacy_settings
                            )
                        END
                        FROM tiktok_settings ts
                        WHERE ts.id = sp.tiktok_settings_id
                    ) AS tiktok_settings
                FROM scheduled_post sp
                INNER JOIN edited_clips ec ON sp.clip_id = ec.id
                INNER JOIN clips c on ec.original_clip_id = c.id
                INNER JOIN scheduled_post_platforms spp ON sp.id = spp.scheduled_post_id
                INNER JOIN platforms p ON spp.platform_id = p.id
                INNER JOIN post_statuses ps ON spp.status_id = ps.id
                INNER JOIN post_statuses ps2 ON sp.status_id = ps2.id
                WHERE ps2.status = 'scheduled' and ps.status = 'scheduled'
                AND sp.scheduled_datetime <= NOW() AT TIME ZONE 'UTC'
                GROUP BY 
                    sp.id,
                    sp.user_id,
                    ec.id,
                    sp.title,
                    sp.caption,
                    sp.scheduled_datetime,
                    ec.url,
                    c.kind,
                    c.stream_id,
                    c.thumbnail_url,
                    c.watermark_url,
                    ec.name,
                    c.favorited,
                    c.views,
                    c.start_time,
                    c.owner_username,
                    c.user_id
                ORDER BY sp.scheduled_datetime
            """
            try:
                rows = await self.db.fetch_all(query)

                if not rows:
                    logger.debug("No due scheduled posts found")
                    return []

                results = []
                for row in rows:
                    platforms = row[17]
                    raw_tiktok = self._coerce_json(row[18], None)
                    tiktok_settings = (
                        TikTokSettings(**raw_tiktok) if raw_tiktok else None
                    )
                    results.append(
                        ScheduledPostFetch(
                            id=row[0],
                            user_id=row[1],
                            clip_id=row[2],
                            title=row[3],
                            caption=row[4],
                            scheduled_datetime=row[5],
                            platforms=[
                                PlatformData(
                                    platform=Platform(entry["platform"]["name"]),
                                    status=PostStatus(entry["status"]["status"]),
                                    id=entry["id"]["id"],
                                )
                                for entry in platforms
                            ],
                            clip=ClipBase(
                                url=row[6],
                                kind=row[7],
                                stream_id=row[8],
                                thumbnail_url=row[9],
                                watermark_url=row[10],
                                caption=row[11],
                                favorited=row[12],
                                views=row[13],
                                start_time=row[14],
                                owner_username=row[15],
                                user_id=row[16],
                            )
                            if row[6] and row[7]
                            else None,
                            tiktok_settings=tiktok_settings,
                        )
                    )

                return results

            except Exception as e:
                logger.error(
                    f"Error fetching due scheduled posts: {e}", extra={"error": str(e)}
                )
                raise HTTPException(
                    status_code=500, detail="Error fetching due scheduled posts."
                )

    async def get_post_status_elements(self) -> List[PostStatusElement]:
        """
        Fetch statuses for content scheduler
        """
        with span(logger, "get_post_status_elements"):
            query = """
                SELECT id, status
                FROM post_statuses
                WHERE status IN ('published', 'failed', 'in_progress')
                ORDER BY status ASC;
            """
            try:
                rows = await self.db.fetch_all(query)
                if not rows:
                    return []
                return [PostStatusElement(id=row[0], status=row[1]) for row in rows]
            except Exception as e:
                logger.error(f"Error fetching post statuses: {e}")
                raise

    async def get_one_scheduled_post(
        self, post_id: int
    ) -> Optional[ScheduledPostFetch]:
        """Get a single scheduled post by its ID, including platform info as array."""
        with span(logger, "get_one_scheduled_post", {"post_id": post_id}):
            query = """
                SELECT 
                    sp.id AS scheduled_post_id,
                    sp.user_id,
                    sp.clip_id,
                    sp.title,
                    sp.caption,
                    sp.scheduled_datetime,
                    c.url,
                    c.kind,
                    c.stream_id,
                    c.thumbnail_url,
                    c.watermark_url,
                    c.caption AS clip_caption,
                    c.favorited,
                    c.views,
                    c.start_time,
                    c.owner_username,
                    c.user_id AS clip_user_id,
                    COALESCE(
                        json_agg(
                            json_build_object(
                                'platform', json_build_object('name', p.name),
                                'status', json_build_object('status', ps.status)
                            )
                        ) FILTER (WHERE p.id IS NOT NULL AND ps.id IS NOT NULL),
                        '[]'
                    ) AS platforms,
                    (
                        SELECT CASE
                            WHEN ts.id IS NULL THEN NULL
                            ELSE json_build_object(
                                'id', ts.id,
                                'comment', ts.comment,
                                'duet', ts.duet,
                                'stitch', ts.stitch,
                                'promotional_content', ts.promotional_content,
                                'promoting_yourself', ts.promoting_yourself,
                                'paid_partnership', ts.paid_partnership,
                                'privacy_settings', ts.privacy_settings
                            )
                        END
                        FROM tiktok_settings ts
                        WHERE ts.id = sp.tiktok_settings_id
                    ) AS tiktok_settings
                FROM scheduled_post sp
                LEFT JOIN clips c ON sp.clip_id = c.id
                LEFT JOIN scheduled_post_platforms spp ON sp.id = spp.scheduled_post_id
                LEFT JOIN platforms p ON spp.platform_id = p.id
                LEFT JOIN post_statuses ps ON spp.status_id = ps.id
                WHERE sp.id = %s
                AND sp.deleted_at IS NULL
                GROUP BY 
                    sp.id,
                    sp.user_id,
                    sp.clip_id,
                    sp.title,
                    sp.caption,
                    sp.scheduled_datetime,
                    c.url,
                    c.kind,
                    c.stream_id,
                    c.thumbnail_url,
                    c.watermark_url,
                    c.caption,
                    c.favorited,
                    c.views,
                    c.start_time,
                    c.owner_username,
                    c.user_id
            """

            try:
                row = await self.db.fetch_one(query, [post_id])

                if not row:
                    logger.debug(f"No scheduled post found with id {post_id}")
                    return None

                platforms = row[17]
                raw_tiktok = self._coerce_json(row[18], None)
                tiktok_settings = TikTokSettings(**raw_tiktok) if raw_tiktok else None
                return ScheduledPostFetch(
                    id=row[0],
                    user_id=row[1],
                    clip_id=row[2],
                    title=row[3],
                    caption=row[4],
                    scheduled_datetime=row[5],
                    platforms=[
                        PlatformData(
                            platform=Platform(entry["platform"]["name"]),
                            status=PostStatus(entry["status"]["status"]),
                        )
                        for entry in platforms
                    ],
                    clip=ClipBase(
                        url=row[6],
                        kind=row[7],
                        stream_id=row[8],
                        thumbnail_url=row[9],
                        watermark_url=row[10],
                        caption=row[11],
                        favorited=row[12],
                        views=row[13],
                        start_time=row[14],
                        owner_username=row[15],
                        user_id=row[16],
                    )
                    if row[6] and row[7]
                    else None,
                    tiktok_settings=tiktok_settings,
                )

            except Exception as e:
                logger.error(
                    f"Error fetching scheduled post {post_id}: {e}",
                    extra={"error": str(e)},
                )
                raise e

    async def get_user_access_tokens(
        self, user_id: int, platform: Platform
    ) -> UserAccessToken:
        """
        get oauth tokens for content scheduler
        """
        user_dao = UserDAO(self.db)
        query = """
            SELECT 
                uot.id,
                uot.user_id,
                uot.access_token,
                uot.refresh_token,
                uot.expires_in,
                uot.token_type,
                uot.scope,
                uot.platform,
                uot.created_at,
                uot.updated_at,
                uot.deleted_at
            FROM social_media_account sma
            INNER JOIN user_oauth_tokens uot 
                ON sma.oauth_token_id = uot.id
            WHERE sma.user_id = %s
            AND uot.platform = %s
        """

        try:
            row = await self.db.fetch_one(query, (user_id, platform.value))
            if not row:
                raise HTTPException(
                    status_code=500,
                    detail=f"Error getting oauth information for user {user_id} on platform {platform}.",
                )

            return UserAccessToken(
                id=row[0],
                user_id=row[1],
                access_token=user_dao.decrypt_token(row[2]),
                refresh_token=(
                    user_dao.decrypt_token(row[3])
                    if row[3] and row[3].strip() != ""
                    else ""
                ),
                expires_in=row[4],
                token_type=row[5],
                scope=row[6].split(",") if row[6] else None,
                platform=Platform(row[7]),
                created_at=row[8],
                updated_at=row[9],
                deleted_at=row[10],
            )

        except Exception as e:
            logger.error(f"Error fetching access tokens for user {user_id}: {e}")
            raise HTTPException(
                status_code=500, detail="Error fetching tokens for content scheduler"
            )

    async def update_user_access_token(
        self, token_id: int, token: UserAccessTokenUpdate
    ) -> bool:
        """
        update oauth tokens for content scheduler
        """
        user_dao = UserDAO(self.db)

        try:
            set_clauses = []
            values = []

            if token.access_token is not None:
                set_clauses.append("access_token")
                values.append(user_dao.encrypt_token(token.access_token))
            if token.expires_in is not None:
                set_clauses.append("expires_in")
                values.append(token.expires_in)
            if token.refresh_token is not None:
                set_clauses.append("refresh_token")
                values.append(user_dao.encrypt_token(token.refresh_token))
            if token.updated_at is not None:
                set_clauses.append("updated_at")
                values.append(token.updated_at)
            if token.token_type is not None:
                set_clauses.append("token_type")
                values.append(token.token_type)
            if token.scope is not None:
                set_clauses.append("scope")
                values.append(token.scope)

            await self.db.update(
                "user_oauth_tokens",
                set_columns=set_clauses,
                set_values=values,
                where_clause="id = %s",
                where_params=[token_id],
            )

            return True

        except Exception as e:
            logger.error(
                f"Error update access tokens for user_oauth_tokens id {token_id}: {e}"
            )
            raise HTTPException(
                status_code=500,
                detail=f"Error update access tokens for user_oauth_tokens id {token_id}",
            )

    async def update_scheduled_post_status(
        self, scheduled_post_id: int, status_id: int
    ) -> bool:
        """
        Update status_id and updated_at for a scheduled_post by id.
        """
        with span(
            logger,
            "update_scheduled_post_status",
            {"id": scheduled_post_id, "status_id": status_id},
        ):
            try:
                set_columns = ["status_id", "updated_at"]
                set_values = [status_id, datetime.utcnow()]

                await self.db.update(
                    "scheduled_post",
                    set_columns=set_columns,
                    set_values=set_values,
                    where_clause="id = %s",
                    where_params=[scheduled_post_id],
                )
                return True
            except Exception as e:
                logger.error(
                    f"Error updating scheduled_post.id={scheduled_post_id} -> status_id={status_id}: {e}"
                )
                raise HTTPException(
                    status_code=500, detail="Error updating scheduled_post status"
                )

    async def update_scheduled_post_platform_status(
        self, scheduled_post_platform_id: int, status_id: int
    ) -> bool:
        """
        Update status_id for a scheduled_post_platform by id.
        """
        with span(
            logger,
            "update_scheduled_post_platform_status",
            {"id": scheduled_post_platform_id, "status_id": status_id},
        ):
            try:
                set_columns = ["status_id"]
                set_values = [status_id]

                await self.db.update(
                    "scheduled_post_platforms",
                    set_columns=set_columns,
                    set_values=set_values,
                    where_clause="id = %s",
                    where_params=[scheduled_post_platform_id],
                )
                return True
            except Exception as e:
                logger.error(
                    f"Error updating scheduled_post_platforms.id={scheduled_post_platform_id} -> status_id={status_id}: {e}"
                )
                raise HTTPException(
                    status_code=500,
                    detail="Error updating scheduled_post_platform status",
                )
