from typing import Optional

from sqlalchemy import func, select
from sqlalchemy.orm import Session

from vdash.config import Settings
from vdash.core import Database, inject_session
from vdash.shared import PaginatedResult, PaginationQuery
from vdash.utils import CryptoHelper

from .models import UserDB
from .schemas import CreateUserDBRequest, UpdateUserDBRequest


class UserDBService:
    def __init__(
        self,
        db: Database,
        settings: Settings,
        crypto_helper: CryptoHelper,
    ):
        self._db = db
        self._settings = settings
        self._crypto_helper = crypto_helper

    @inject_session
    async def list_user_dbs(
        self, pagination: PaginationQuery, session: Optional[Session] = None
    ) -> PaginatedResult[UserDB]:
        """
        List all user DB connections with pagination and optional search.

        Args:
            pagination (PaginationQuery): Pagination and search parameters
                (page, page_size, q).
            session (Optional[Session]): SQLAlchemy session.

        Returns:
            PaginatedResult[UserDB]: Paginated list of user DB instances.
        """
        assert session is not None

        if pagination.page < 1 or pagination.page_size < 1:
            return PaginatedResult(
                items=[], page=pagination.page, page_size=pagination.page_size, total=0
            )

        offset = (pagination.page - 1) * pagination.page_size
        stmt = select(UserDB)

        if pagination.q:
            stmt = stmt.where(UserDB.name.ilike(f"%{pagination.q}%"))

        count_stmt = select(func.count()).select_from(stmt.subquery())
        total = session.scalar(count_stmt) or 0

        stmt = (
            stmt.order_by(UserDB.created_at.desc())
            .offset(offset)
            .limit(pagination.page_size)
        )
        items = session.scalars(stmt).all()
        return PaginatedResult(
            items=items,
            page=pagination.page,
            page_size=pagination.page_size,
            total=total,
        )

    @inject_session
    async def create_user_db(
        self, request: CreateUserDBRequest, session: Optional[Session] = None
    ) -> UserDB:
        """
        Create a new user DB connection.

        Args:
            request (CreateUserDBRequest): The user DB connection data.
            session (Optional[Session]): SQLAlchemy session.

        Returns:
            UserDB: The created user DB instance.
        """
        assert session is not None

        encrypted_connection_string = self._crypto_helper.encrypt(
            request.connection_string
        )

        user_db = UserDB(
            name=request.name,
            connection_string=encrypted_connection_string,
        )
        session.add(user_db)
        return user_db

    @inject_session
    async def get_user_db(
        self, uuid: str, session: Optional[Session] = None
    ) -> Optional[UserDB]:
        """
        Get a user DB connection by uuid.

        Args:
            uuid (str): The UUID of the user DB.
            session (Optional[Session]): SQLAlchemy session.

        Returns:
            Optional[UserDB]: The user DB instance or None.
        """
        assert session is not None
        result = session.execute(select(UserDB).where(UserDB.uuid == uuid))
        return result.scalars().one_or_none()

    @inject_session
    async def update_user_db(
        self, uuid: str, request: UpdateUserDBRequest, session: Optional[Session] = None
    ) -> Optional[UserDB]:
        """
        Update a user DB connection by uuid.

        Args:
            uuid (str): The UUID of the user DB.
            request (UpdateUserDBRequest): The update data.
            session (Optional[Session]): SQLAlchemy session.

        Returns:
            Optional[UserDB]: The updated user DB instance or None.
        """
        assert session is not None
        result = session.execute(select(UserDB).where(UserDB.uuid == uuid))
        user_db = result.scalars().one_or_none()
        if not user_db:
            return None

        user_db.name = request.name
        session.add(user_db)
        return user_db

    @inject_session
    async def delete_user_db(
        self, uuid: str, session: Optional[Session] = None
    ) -> bool:
        """
        Delete a user DB connection by uuid.

        Args:
            uuid (str): The UUID of the user DB.
            session (Optional[Session]): SQLAlchemy session.

        Returns:
            bool: True if deleted, False if not found.
        """
        assert session is not None
        result = session.execute(select(UserDB).where(UserDB.uuid == uuid))
        user_db = result.scalars().one_or_none()
        if not user_db:
            return False

        session.delete(user_db)
        return True
