from datetime import datetime
from typing import Optional

from common.database import DBConfig, Db
from common.logging import get_logger, span
from common.models.render_session import (
    RenderSession,
    RenderSessionCreate,
    RenderSessionUpdate,
)

logger = get_logger(__name__)


class RenderSessionDAO:
    """Data Access Object for render_sessions table 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())
        logger.debug(
            "RenderSessionDAO initialized", extra={"component": "RenderSessionDAO"}
        )

    async def create_session(self, session: RenderSessionCreate) -> RenderSession:
        """Create a new render session in the database."""
        with span(logger, "create_session"):
            columns = [
                "user_id",
                "original_clip_id",
                "render_id",
                "edited_clip_id",
                "status",
                "failure_reason",
            ]
            values = (
                session.user_id,
                session.original_clip_id,
                session.render_id,
                session.edited_clip_id,
                session.status.value,
                session.failure_reason,
            )

            try:
                res = await self.db.insert("render_sessions", columns, values)
                if not res:
                    logger.error("Failed to create render session")
                    raise Exception("Failed to create render session")

                return RenderSession(
                    id=res,
                    user_id=session.user_id,
                    original_clip_id=session.original_clip_id,
                    render_id=session.render_id,
                    edited_clip_id=session.edited_clip_id,
                    status=session.status,
                    failure_reason=session.failure_reason,
                    created_at=datetime.now(),
                    updated_at=datetime.now(),
                )
            except Exception as e:
                logger.error(f"Error creating render session: {e}")
                raise

    async def update_session(
        self, session_id: int, update: RenderSessionUpdate
    ) -> bool:
        """Update an existing render session."""
        with span(logger, "update_session", {"session_id": session_id}):
            update_data = update.model_dump(exclude_unset=True)
            if not update_data:
                return True

            # Convert Enums to values
            if "status" in update_data and update_data["status"]:
                update_data["status"] = update_data["status"].value

            # Prepare update query
            sets = []
            values = []
            for k, v in update_data.items():
                sets.append(f"{k} = %s")
                values.append(v)

            # Add updated_at if not trigger-based or as explicit update
            # (Though migration added a trigger)

            query = f"UPDATE render_sessions SET {', '.join(sets)} WHERE id = %s"
            values.append(session_id)

            try:
                await self.db.execute(query, tuple(values))
                return True
            except Exception as e:
                logger.error(f"Error updating render session {session_id}: {e}")
                return False

    async def get_session_by_render_id(self, render_id: str) -> Optional[RenderSession]:
        """Fetch a render session by Creatomate render_id."""
        with span(logger, "get_session_by_render_id", {"render_id": render_id}):
            query = "SELECT id, user_id, original_clip_id, render_id, edited_clip_id, status, failure_reason, expired_at, completed_at, created_at, updated_at FROM render_sessions WHERE render_id = %s"
            row = await self.db.fetch_one(query, (render_id,))
            if not row:
                return None
            return self._row_to_session(row)

    async def get_session_by_id(self, session_id: int) -> Optional[RenderSession]:
        """Fetch a render session by internal ID."""
        with span(logger, "get_session_by_id", {"session_id": session_id}):
            query = "SELECT id, user_id, original_clip_id, render_id, edited_clip_id, status, failure_reason, expired_at, completed_at, created_at, updated_at FROM render_sessions WHERE id = %s"
            row = await self.db.fetch_one(query, (session_id,))
            if not row:
                return None
            return self._row_to_session(row)

    def _row_to_session(self, row: tuple) -> RenderSession:
        """Convert database row to RenderSession model."""
        from common.models.render_session import RenderSessionStatus

        return RenderSession(
            id=row[0],
            user_id=row[1],
            original_clip_id=row[2],
            render_id=row[3],
            edited_clip_id=row[4],
            status=RenderSessionStatus(row[5]),
            failure_reason=row[6],
            expired_at=row[7],
            completed_at=row[8],
            created_at=row[9],
            updated_at=row[10],
        )
