from typing import ClassVar

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

from .api_constants import (
    SUCCESS_CODE,
    KeywordControllerStubs,
    KeywordRequestMessages,
    KeywordRPCMethodNames,
)


class ValidationError(Exception):
    pass


class ResponseCodeError(Exception):
    pass


class Keyword(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, keyword: str, content: str, title: str, url: HttpUrl):
        """Create a new keyword.

        Args:
            keyword (str): The keyword to be created.
            content (str): The content of the keyword.
            title (str): The title of the keyword.
            url (HttpUrl): The URL of the keyword.

        Return:
            The response from the gRPC server in Darsman format.
        """
        # Assign attributes to the request message
        KeywordRequestMessages.CREATE.keyword = keyword
        KeywordRequestMessages.CREATE.content = content
        KeywordRequestMessages.CREATE.title = title
        KeywordRequestMessages.CREATE.url = str(url)

        # Perform the create operation
        return await self.perform_operation(
            KeywordRPCMethodNames.CREATE,  # The name of the RPC method
            KeywordRequestMessages.CREATE,  # The request message
            KeywordControllerStubs.CREATE,  # The controller stub
        )

    @validate_call
    async def search(self, keyword: str):
        """Search for a `keyword`.

        Args:
            keyword (str): The keyword to be searched.

        Return:
            Essentially the keyword info in format of the Darsman response.
        """
        # Set the keyword attribute in the request message
        KeywordRequestMessages.SEARCH.keyword = keyword

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