import inspect
from collections.abc import AsyncGenerator, AsyncIterator
from functools import partial
from typing import get_args, get_origin

from ..docstring import DocstringParser
from ..reflection.factory import BookPollFactory


class PollFunction:
    """Wrapper for poll functions."""

    def __init__(self, poll_fn, name: str, description: str):
        self.poll_fn = poll_fn
        self.id = poll_fn.__name__  # Use function name as ID
        self.name = name
        self.description = description
        self._poll_descriptor = None

        # Create the descriptor immediately
        self._create_descriptor()

    def _create_descriptor(self):
        """Create the BookPollDescriptor."""
        poll_sig = inspect.signature(self.poll_fn, eval_str=True)
        poll_docstring = DocstringParser.parse(self.poll_fn.__doc__)

        configuration_params = [param_name for param_name in poll_sig.parameters.keys() if param_name not in ("self", "filter_expression")]

        self._poll_descriptor = BookPollFactory.create(
            identifier=self.id,
            name=self.name,
            description=self.description,
            python_signature=poll_sig,
            docstring=poll_docstring,
            configuration_params=configuration_params,
        )

        # Attach metadata to poll function
        self.poll_fn.__poll__ = self._poll_descriptor

    def __get__(self, instance, owner):
        """Support the descriptor protocol for proper method binding."""
        if instance is None:
            return self
        return partial(self.poll_fn, instance)

    def __call__(self, *args, **kwargs):
        """Allow the wrapper to be called like the original function."""
        return self.poll_fn(*args, **kwargs)

    def __getattr__(self, name):
        """Delegate attribute access to the wrapped function."""
        return getattr(self.poll_fn, name)


def poll(name: str, description: str = ""):
    """
    Decorator for long-running polling/streaming procedures.

    This decorator is applied to an async iterator function that yields items continuously.
    The function name is automatically used as the poll ID.

    The poll function must:
    - Be an async function
    - Return AsyncIterator[T] or AsyncGenerator[T, None]
    - Have a docstring

    Args:
        name: Display name for the polling operation (e.g., "Retrieve emails")
        description: Detailed description of what the poll does

    Example:
        @poll(
            name="Retrieve emails",
            description="Continuously monitors the specified folder and yields emails as they arrive"
        )
        async def poll_for_emails(self, folder: str = "INBOX") -> AsyncIterator[Email]:
            '''Poll for new emails continuously.

            This procedure monitors the specified folder and yields emails as they arrive.

            Arguments:
                folder: Email folder to monitor
            '''
            async for email in self._client.retrieve_emails(folder):
                yield email
    """

    def decorator(poll_fn):
        if not inspect.isfunction(poll_fn):
            raise TypeError("The poll decorator can only be applied to functions.")

        # Check if it's an async function (either async def or async generator)
        if not (inspect.iscoroutinefunction(poll_fn) or inspect.isasyncgenfunction(poll_fn)):
            raise TypeError(f"Poll function '{poll_fn.__name__}' must be an async function. " f"Use 'async def {poll_fn.__name__}(...)' instead of 'def {poll_fn.__name__}(...)'.")

        if not poll_fn.__doc__:
            raise ValueError(f"Poll function '{poll_fn.__name__}' is missing docstring. " f"Docstrings are required for all poll functions.")

        # Parse python signature and validate return type
        python_signature = inspect.signature(poll_fn, eval_str=True)
        return_annotation = python_signature.return_annotation

        if return_annotation == inspect.Signature.empty:
            raise TypeError(f"Poll function '{poll_fn.__name__}' must have a return type annotation. " f"Expected: AsyncIterator[YourType] or AsyncGenerator[YourType, None]")

        origin = get_origin(return_annotation)
        if origin not in (AsyncIterator, AsyncGenerator):
            raise TypeError(f"Poll function '{poll_fn.__name__}' must return AsyncIterator[T] or " f"AsyncGenerator[T, None], got {return_annotation}")

        args = get_args(return_annotation)
        if not args:
            raise TypeError(f"Poll function '{poll_fn.__name__}' must specify the yielded type: " f"AsyncIterator[YourType]")

        poll_fn.__is_poll__ = True

        # Return PollFunction wrapper
        # Don't use wraps() as it creates a circular reference
        return PollFunction(poll_fn, name, description)

    return decorator
