"""
OpenAI LLM client implementation.
"""

from typing import AsyncGenerator, Optional

from loguru import logger
from pydantic import SecretStr

from skene_growth.llm.base import LLMClient

# Default fallback model for rate limiting (429 errors)
DEFAULT_FALLBACK_MODEL = "gpt-4o-mini"


class OpenAIClient(LLMClient):
    """
    OpenAI LLM client.

    Handles rate limiting by automatically falling back to a secondary model
    when the primary model returns a 429 rate limit error.

    Example:
        client = OpenAIClient(
            api_key=SecretStr("your-api-key"),
            model_name="gpt-4o"
        )
        response = await client.generate_content("Hello!")
    """

    def __init__(
        self,
        api_key: SecretStr,
        model_name: str,
        fallback_model: Optional[str] = None,
    ):
        """
        Initialize the OpenAI client.

        Args:
            api_key: OpenAI API key (wrapped in SecretStr for security)
            model_name: Primary model to use (e.g., "gpt-4o", "gpt-4o-mini")
            fallback_model: Model to use when rate limited (default: gpt-4o-mini)
        """
        try:
            from openai import AsyncOpenAI
        except ImportError:
            raise ImportError(
                "openai is required for OpenAI support. "
                "Install with: pip install skene-growth[openai]"
            )

        self.model_name = model_name
        self.fallback_model = fallback_model or DEFAULT_FALLBACK_MODEL
        self.client = AsyncOpenAI(api_key=api_key.get_secret_value())

    async def generate_content(
        self,
        prompt: str,
    ) -> str:
        """
        Generate text from OpenAI.

        Automatically retries with fallback model on rate limit errors.

        Args:
            prompt: The prompt to send to the model

        Returns:
            Generated text as a string

        Raises:
            RuntimeError: If generation fails on both primary and fallback models
        """
        try:
            from openai import RateLimitError
        except ImportError:
            RateLimitError = Exception  # Fallback if import fails

        try:
            response = await self.client.chat.completions.create(
                model=self.model_name,
                messages=[{"role": "user", "content": prompt}],
            )
            return response.choices[0].message.content.strip()
        except RateLimitError:
            logger.warning(
                f"Rate limit (429) hit on model {self.model_name}, "
                f"falling back to {self.fallback_model}"
            )
            try:
                response = await self.client.chat.completions.create(
                    model=self.fallback_model,
                    messages=[{"role": "user", "content": prompt}],
                )
                logger.info(
                    f"Successfully generated content using fallback model {self.fallback_model}"
                )
                return response.choices[0].message.content.strip()
            except Exception as fallback_error:
                raise RuntimeError(
                    f"Error calling OpenAI (fallback model {self.fallback_model}): {fallback_error}"
                )
        except Exception as e:
            raise RuntimeError(f"Error calling OpenAI: {e}")

    async def generate_content_stream(
        self,
        prompt: str,
    ) -> AsyncGenerator[str, None]:
        """
        Generate content with streaming.

        Automatically retries with fallback model on rate limit errors.

        Args:
            prompt: The prompt to send to the model

        Yields:
            Text chunks as they are generated

        Raises:
            RuntimeError: If streaming fails on both primary and fallback models
        """
        try:
            from openai import RateLimitError
        except ImportError:
            RateLimitError = Exception

        model_to_use = self.model_name
        try:
            stream = await self.client.chat.completions.create(
                model=model_to_use,
                messages=[{"role": "user", "content": prompt}],
                stream=True,
            )

            async for chunk in stream:
                if chunk.choices and chunk.choices[0].delta.content:
                    yield chunk.choices[0].delta.content

        except RateLimitError:
            if model_to_use == self.model_name:
                logger.warning(
                    f"Rate limit (429) hit on model {self.model_name} during streaming, "
                    f"falling back to {self.fallback_model}"
                )
                try:
                    stream = await self.client.chat.completions.create(
                        model=self.fallback_model,
                        messages=[{"role": "user", "content": prompt}],
                        stream=True,
                    )
                    logger.info(
                        f"Successfully started streaming with fallback model {self.fallback_model}"
                    )
                    async for chunk in stream:
                        if chunk.choices and chunk.choices[0].delta.content:
                            yield chunk.choices[0].delta.content
                except Exception as fallback_error:
                    raise RuntimeError(
                        f"Error in streaming generation (fallback model {self.fallback_model}): "
                        f"{fallback_error}"
                    )
            else:
                raise RuntimeError(f"Rate limit error in streaming generation: {model_to_use}")
        except Exception as e:
            raise RuntimeError(f"Error in streaming generation: {e}")

    def get_model_name(self) -> str:
        """Return the primary model name."""
        return self.model_name

    def get_provider_name(self) -> str:
        """Return the provider name."""
        return "openai"
