"""Template for creating a custom Cleave backend plugin.

Copy this file to ~/.cleave/backends/ and implement the required methods.

Example:
    cp custom_backend.py ~/.cleave/backends/my_backend.py
    # Edit my_backend.py with your implementation
    cleave config set backend my-backend
    cleave tui
"""

from collections.abc import AsyncIterator

from cleave.tui.backends.base import (
    Backend,
    BackendConfig,
    BackendState,
    Message,
    MessageType,
)


class CustomBackend(Backend):
    """Template for custom backend implementation.

    Replace this class with your own LLM backend implementation.
    All methods marked with @abstractmethod must be implemented.
    """

    def __init__(self):
        """Initialize backend state."""
        self._state = BackendState.DISCONNECTED
        self._error: str | None = None
        self._config: BackendConfig | None = None

    @property
    def name(self) -> str:
        """Human-readable backend name.

        This name appears in the TUI and CLI.

        Returns:
            Backend display name (e.g., 'My Custom LLM').
        """
        return "Custom Backend"

    @property
    def state(self) -> BackendState:
        """Current connection state.

        Returns:
            Current state (DISCONNECTED, CONNECTING, CONNECTED, ERROR).
        """
        return self._state

    @property
    def error(self) -> str | None:
        """Last error message, if any.

        Returns:
            Error message or None if no error.
        """
        return self._error

    async def connect(self, config: BackendConfig) -> None:
        """Establish connection to the backend.

        This is called once when the TUI starts or when switching backends.
        Perform any initialization here:
        - Validate API keys
        - Establish network connections
        - Load model configurations

        Args:
            config: Backend configuration including model, API keys, etc.

        Raises:
            ConnectionError: If connection fails.
        """
        self._state = BackendState.CONNECTING
        self._config = config

        try:
            # TODO: Implement connection logic
            # Example:
            #   self._client = YourAPIClient(api_key=config.api_key)
            #   self._client.connect()

            self._state = BackendState.CONNECTED
            self._error = None

        except Exception as e:
            self._state = BackendState.ERROR
            self._error = str(e)
            raise ConnectionError(f"Failed to connect: {e}") from e

    async def disconnect(self) -> None:
        """Close the backend connection.

        Clean up any resources here:
        - Close network connections
        - Cancel pending requests
        - Release memory

        Safe to call multiple times or if not connected.
        """
        # TODO: Implement disconnection logic
        # Example:
        #   if self._client:
        #       await self._client.close()

        self._state = BackendState.DISCONNECTED
        self._error = None

    async def query(self, prompt: str) -> AsyncIterator[Message]:
        """Send a prompt and stream responses.

        This is the core method called for every user message.
        Implement streaming support for better UX.

        Args:
            prompt: User message to send.

        Yields:
            Message objects as they arrive. For streaming:
            1. Yield STREAM_START message
            2. Yield STREAM_DELTA messages for each token
            3. Yield STREAM_END message with full content

        Raises:
            RuntimeError: If not connected.
            ConnectionError: If connection is lost during query.
        """
        if self._state != BackendState.CONNECTED:
            raise RuntimeError("Not connected. Call connect() first.")

        try:
            # TODO: Implement query logic with streaming
            # Example for streaming:
            #
            #   yield Message(
            #       role="assistant",
            #       content="",
            #       message_type=MessageType.STREAM_START,
            #   )
            #
            #   full_response = ""
            #   async for chunk in self._client.stream(prompt):
            #       full_response += chunk
            #       yield Message(
            #           role="assistant",
            #           content=chunk,
            #           message_type=MessageType.STREAM_DELTA,
            #       )
            #
            #   yield Message(
            #       role="assistant",
            #       content=full_response,
            #       message_type=MessageType.STREAM_END,
            #   )

            # For non-streaming, just yield a complete message:
            response = "This is a placeholder response"
            yield Message(
                role="assistant",
                content=response,
                message_type=MessageType.COMPLETE,
            )

        except Exception as e:
            self._state = BackendState.ERROR
            self._error = str(e)
            yield Message(
                role="assistant",
                content=f"Error: {e}",
                metadata={"error": True},
            )

    @classmethod
    def check_auth(cls) -> tuple[bool, str]:
        """Check if authentication is configured.

        This is called to determine if the backend is ready to use.

        Returns:
            Tuple of (is_authenticated, status_message).
            Example: (True, "API key configured")
                     (False, "Missing CUSTOM_API_KEY environment variable")
        """
        # TODO: Implement auth check
        # Example:
        #   import os
        #   api_key = os.getenv("CUSTOM_API_KEY")
        #   if api_key:
        #       return (True, "API key configured")
        #   return (False, "Set CUSTOM_API_KEY environment variable")

        return (False, "Authentication not implemented")

    @classmethod
    def available_models(cls, config: BackendConfig | None = None) -> list[str]:
        """List models available from this backend.

        This is used to populate model selection in the TUI.

        Args:
            config: Optional config for dynamic model discovery.

        Returns:
            List of model identifiers.
            Example: ["gpt-4", "gpt-3.5-turbo"]
        """
        # TODO: Implement model listing
        # Example:
        #   if config and config.api_key:
        #       # Query API for available models
        #       return ["model-1", "model-2"]
        #   # Return static list
        #   return ["default-model"]

        return ["custom-model-1", "custom-model-2"]

    @classmethod
    def default_config(cls) -> BackendConfig:
        """Get default configuration for this backend.

        Override this to provide sensible defaults.

        Returns:
            BackendConfig with default values.
        """
        return BackendConfig(
            model="custom-model-1",
            timeout=120.0,
            max_turns=50,
        )


# Plugin metadata (REQUIRED)
__plugin_name__ = "custom-backend"  # Unique identifier (kebab-case)
__plugin_version__ = "1.0.0"  # Semver version string
__plugin_author__ = "Your Name"  # Author name
__plugin_backend__ = CustomBackend  # Backend class (must implement Backend interface)

# Optional metadata
__plugin_description__ = "A custom backend for [Your LLM Service]"
