from rath.rath import Rath, current_rath
from typing import Optional, Dict, Any, Protocol, AsyncIterator, Iterator
from pydantic import BaseModel


class TurmsMeta(Protocol):
    """A TurmsMeta is a protocol that defines the meta class for a TurmsOperation"""

    document: str
    operation: str


class TurmsOperation(Protocol):
    """A TurmsOperation is a protocol that defines a GraphQL operation
    that was generated by Turms."""

    def __call__(self, *args: Any, **kwds: Any) -> BaseModel:
        """Mimics the __init__ method of base model"""
        ...

    @property
    def Meta(self) -> TurmsMeta:
        """A meta class that contains the document and operation name for the operation"""
        ...

    @property
    def __name__(self) -> str:
        """The name of the operation"""
        ...


def execute(
    operation: TurmsOperation, variables: Dict[str, Any], rath: Optional[Rath] = None
) -> BaseModel:
    """Synchronously Executes an a query or mutation using rath


    This function will execute an operation using rath, retrieving
    the currentliy active rath client from the current_rath context
    if no rath client is provided.

    Parameters
    ----------
    operation : TurmsOperation
        The turms operation to execute
    variables : Dict[str, Any]
        The variables to use
    rath : Optional[Rath], optional
        The rath client, by default the current rath client

    Returns
    -------
    BaseModel
        The result of the operation
    """
    rath = rath or current_rath.get()
    assert (
        rath is not None
    ), "No rath client provided and no rath client in current_rath context"
    return operation(**rath.query(operation.Meta.document, variables).data)


async def aexecute(
    operation: TurmsOperation, variables: Dict[str, Any], rath: Optional[Rath] = None
) -> BaseModel:
    """Asynchronously Executes a query or mutation using rath


    This function will execute an operation using rath, retrieving
    the currentliy active rath client from the current_rath context
    if no rath client is provided.

    Parameters
    ----------
    operation : TurmsOperation
        The turms operation to execute
    variables : Dict[str, Any]
        The variables to use
    rath : Optional[Rath], optional
        The rath client, by default the current rath client

    Returns
    -------
    BaseModel
        The result of the operation
    """
    rath = rath or current_rath.get()
    assert (
        rath is not None
    ), "No rath client provided and no rath client in current_rath context"
    x = await rath.aquery(operation.Meta.document, variables)
    return operation(**x.data)


def subscribe(
    operation: TurmsOperation, variables: Dict[str, Any], rath: Optional[Rath] = None
) -> Iterator[BaseModel]:
    """Synchronously subscribte to a subscription using rath


    This function will execute an operation using rath, retrieving
    the currentliy active rath client from the current_rath context
    if no rath client is provided.

    Parameters
    ----------
    operation : TurmsOperation
        The turms operation to execute
    variables : Dict[str, Any]
        The variables to use
    rath : Optional[Rath], optional
        The rath client, by default the current rath client

    Yields
    -------
    BaseModel
        The result of the operation
    """
    rath = rath or current_rath.get()
    assert (
        rath is not None
    ), "No rath client provided and no rath client in current_rath context"

    for event in rath.subscribe(operation.Meta.document, variables):
        yield operation(**event.data)


async def asubscribe(
    operation: TurmsOperation, variables: Dict[str, Any], rath: Optional[Rath] = None
) -> AsyncIterator[BaseModel]:
    """Asynchronously subscribte to a subscription using rath


    This function will execute an operation using rath, retrieving
    the currentliy active rath client from the current_rath context
    if no rath client is provided.

    Parameters
    ----------
    operation : TurmsOperation
        The turms operation to execute
    variables : Dict[str, Any]
        The variables to use
    rath : Optional[Rath], optional
        The rath client, by default the current rath client

    yields
    -------
    BaseModel
        The result of the operation
    """
    rath = rath or current_rath.get()
    assert (
        rath is not None
    ), "No rath client provided and no rath client in current_rath context"

    async for event in rath.asubscribe(operation.Meta.document, variables):
        yield operation(**event.data)
