import asyncio
import time
from queue import Empty, Queue
from typing import List, Optional

from pydantic import Field

from inputs.base import Message, SensorConfig
from inputs.base.loop import FuserInput
from providers.io_provider import IOProvider
from providers.simple_paths_provider import SimplePathsProvider


class SimplePathsConfig(SensorConfig):
    """
    Configuration for SimplePaths Sensor.

    Parameters
    ----------
    api_key : Optional[str]
        API Key for authentication.
    use_sim : bool
        Whether to use the simulation endpoint instead of a local one.
    """

    api_key: Optional[str] = Field(default=None, description="API Key")
    use_sim: bool = Field(
        default=False,
        description="Whether to use the simulation endpoint instead of a local one.",
    )


class SimplePaths(FuserInput[SimplePathsConfig, Optional[str]]):
    """
    SimplePaths input handler.

    A class that processes simple paths inputs and generates text descriptions.
    It maintains an internal buffer of processed messages.
    """

    def __init__(self, config: SimplePathsConfig):
        """
        Initialize the SimplePaths input handler.

        Sets up the required providers and buffers for handling simple path data.

        Parameters
        ----------
        config : SimplePathsConfig
            Configuration for the sensor input.
        """
        super().__init__(config)

        # Track IO
        self.io_provider = IOProvider()

        # Buffer for storing the final output
        self.messages: List[Message] = []

        # Buffer for storing messages
        self.message_buffer: Queue[str] = Queue()

        # Initialize SimplePaths Provider
        self.paths_provider: SimplePathsProvider = SimplePathsProvider(
            api_key=self.config.api_key,
            use_sim=self.config.use_sim,
        )
        self.paths_provider.start()

        self.descriptor_for_LLM = (
            "Information about objects and walls around you, to plan your movements and avoid bumping into things."
        )

    async def _poll(self) -> Optional[str]:
        """
        Poll for new messages from the SimplePaths Provider.

        Checks the message buffer for new messages with a brief delay
        to prevent excessive CPU usage.

        Returns
        -------
        Optional[str]
            The next message from the buffer if available, None otherwise
        """
        await asyncio.sleep(0.2)

        try:
            return self.paths_provider.lidar_string
        except Empty:
            return None

    async def _raw_to_text(self, raw_input: Optional[str]) -> Optional[Message]:
        """
        Process raw input to generate a timestamped message.

        Creates a Message object from the raw input string, adding
        the current timestamp.

        Parameters
        ----------
        raw_input : Optional[str]
            Raw input string to be processed

        Returns
        -------
        Optional[Message]
            A timestamped message containing the processed input
        """
        if raw_input is None:
            return None

        return Message(timestamp=time.time(), message=raw_input)

    async def raw_to_text(self, raw_input: Optional[str]):
        """
        Convert raw input to text and update message buffer.

        Processes the raw input if present and adds the resulting
        message to the internal message buffer.

        Parameters
        ----------
        raw_input : Optional[str]
            Raw input to be processed, or None if no input is available
        """
        if raw_input is None:
            return

        pending_message = await self._raw_to_text(raw_input)

        if pending_message is not None:
            self.messages.append(pending_message)

    def formatted_latest_buffer(self) -> Optional[str]:
        """
        Format and clear the latest buffer contents.

        Retrieves the most recent message from the buffer, formats it
        with timestamp and class name, adds it to the IO provider,
        and clears the buffer.

        Returns
        -------
        Optional[str]
            Formatted string containing the latest message and metadata,
            or None if the buffer is empty

        """
        if len(self.messages) == 0:
            return None

        latest_message = self.messages[-1]

        result = f"""
{self.descriptor_for_LLM}: "{latest_message.message}"
"""
        self.io_provider.add_input(self.descriptor_for_LLM, latest_message.message, latest_message.timestamp)
        self.messages = []

        return result
