from typing import ClassVar, List, Optional

from grpc.aio import insecure_channel
from pydantic import BaseModel, validate_call, UUID4

from .api_constants import (
    SUCCESS_CODE,
    IndexedContentControllerStubs,
    IndexedContentRequestMessages,
    IndexedContentRPCMethodNames,
    ItemTypes,
)


class ValidationError(Exception):
    pass


class ResponseCodeError(Exception):
    pass


class IndexedContent(BaseModel):
    """
    Configure the host and port of the search server.

    Parameters:
        host (str): The host address of the search server.
        port (int): The port number of the search server.
    """

    host: str = "127.0.0.1"
    port: str | int = "50051"

    __instance: ClassVar = None

    def __new__(cls, *args, **kwargs):
        if cls.__instance is None:
            cls.__instance = super().__new__(cls)
        return cls.__instance

    async def perform_operation(
        self, rpc_method_name, request_message, controller_stub
    ):
        """Perform the operation.

        Parameters:
            rpc_method_name (str): The name of the RPC method (e.g., `Create`).
            request_message (gRPC message): The request message instance.
        """
        async with insecure_channel(f"{self.host}:{self.port}") as channel:
            controller_stub.channel = channel
            controller_stub.ready()
            RPCMethod = getattr(controller_stub, rpc_method_name, None)

            if RPCMethod:
                response = await RPCMethod(request_message)

                # Clear the request message for future use
                request_message.Clear()

                # Raise error if response is not successful
                if response.code != SUCCESS_CODE:
                    raise ResponseCodeError(response.code)

                return response
            else:
                raise ValidationError("Invalid RPC method name.")

    @validate_call
    async def create(
        self,
        title: str,
        content: str,
        item_identifier: str | UUID4,
        item_type: ItemTypes,
        keywords: Optional[List[str]] = None,
    ):
        """Create a new keyworded indexed content.
        Return:
            The search results in Darsman gRPC response format.
        """
        IndexedContentRequestMessages.CREATE.title = title
        IndexedContentRequestMessages.CREATE.content = content
        IndexedContentRequestMessages.CREATE.itemIdentifier = str(item_identifier)
        IndexedContentRequestMessages.CREATE.itemType = item_type.value
        if keywords:
            IndexedContentRequestMessages.CREATE.keywords.extend(keywords)

        return await self.perform_operation(
            IndexedContentRPCMethodNames.CREATE,
            IndexedContentRequestMessages.CREATE,
            IndexedContentControllerStubs.CREATE,
        )

    async def create_blog(
        self,
        title: str,
        content: str,
        identifier: str | UUID4,
        keywords: Optional[List[str]] = None,
    ):
        return await self.create(
            title=title,
            content=content,
            item_identifier=identifier,
            item_type=ItemTypes.BLOG,
            keywords=keywords,
        )

    async def create_course(
        self,
        title: str,
        content: str,
        identifier: str | UUID4,
        keywords: Optional[List[str]] = None,
    ):
        return await self.create(
            title=title,
            content=content,
            item_identifier=identifier,
            item_type=ItemTypes.COURSE,
            keywords=keywords,
        )

    async def create_master_course(
        self,
        title: str,
        content: str,
        identifier: str | UUID4,
        keywords: Optional[List[str]] = None,
    ):
        return await self.create(
            title=title,
            content=content,
            item_identifier=identifier,
            item_type=ItemTypes.MASTER_COURSE,
            keywords=keywords,
        )

    async def create_lesson(
        self,
        title: str,
        content: str,
        identifier: str | UUID4,
        keywords: Optional[List[str]] = None,
    ):
        return await self.create(
            title=title,
            content=content,
            item_identifier=identifier,
            item_type=ItemTypes.LESSON,
            keywords=keywords,
        )

    @validate_call
    async def search(self, search_phrase: str, item_types: List[ItemTypes]):
        """Search for a phrase across specified item types.

        Args:
            search_phrase (str): The phrase to search for.
            item_types (List[ItemTypes]): List of item types to filter the search.

        Return:
            The search results in Darsman gRPC response format.
        """
        # Set the search_phrase attribute in the request message
        IndexedContentRequestMessages.SEARCH.search_phrase = search_phrase
        IndexedContentRequestMessages.SEARCH.item_types.extend(item_types)

        # Perform the search operation
        return await self.perform_operation(
            IndexedContentRPCMethodNames.SEARCH,  # The name of the RPC method for searching
            IndexedContentRequestMessages.SEARCH,  # The request message for searching
            IndexedContentControllerStubs.SEARCH,  # The controller stub for searching
        )

    async def search_blog(self, search_phrase: str):
        return await self.search(
            search_phrase=search_phrase,
            item_types=[ItemTypes.BLOG],
        )

    async def search_course_lesson(self, search_phrase: str):
        return await self.search(
            search_phrase=search_phrase,
            item_types=[
                ItemTypes.COURSE,
                ItemTypes.LESSON,
                ItemTypes.MASTER_COURSE,
            ],
        )
