"""External LLM providers — OpenAI and Anthropic SDK streaming."""

from __future__ import annotations

import logging
from collections.abc import AsyncIterator

from redhat_oracle.core.llm.base import LLMProvider

logger = logging.getLogger(__name__)


class ProviderUnavailableError(Exception):
    """Raised when an external LLM provider is unreachable or misconfigured."""


class OpenAILLM(LLMProvider):
    """OpenAI LLM provider using the openai SDK."""

    def __init__(
        self,
        api_key: str,
        model: str = "gpt-4o",
    ) -> None:
        self._api_key = api_key
        self._model = model

    async def _check_available(self) -> None:
        """Verify the API key works by listing models."""
        try:
            from openai import AsyncOpenAI  # type: ignore[import-not-found]

            client = AsyncOpenAI(api_key=self._api_key)
            await client.models.list()
        except Exception as e:
            raise ProviderUnavailableError(f"OpenAI not available: {e}") from e

    async def generate(self, prompt: str, system: str | None = None) -> str:
        """Generate a complete response from OpenAI."""
        chunks: list[str] = []
        async for chunk in self.stream(prompt, system):
            chunks.append(chunk)
        return "".join(chunks)

    async def stream(self, prompt: str, system: str | None = None) -> AsyncIterator[str]:
        """Stream response tokens from OpenAI."""
        try:
            from openai import AsyncOpenAI  # type: ignore[import-not-found]

            client = AsyncOpenAI(api_key=self._api_key)

            messages: list[dict[str, str]] = []
            if system:
                messages.append({"role": "system", "content": system})
            messages.append({"role": "user", "content": prompt})

            response = await client.chat.completions.create(
                model=self._model,
                messages=messages,  # type: ignore[arg-type]
                stream=True,
            )

            async for chunk in response:
                if chunk.choices and chunk.choices[0].delta.content:
                    yield chunk.choices[0].delta.content
        except ImportError as e:
            raise ProviderUnavailableError(
                "openai package not installed. Install with: pip install openai"
            ) from e
        except Exception as e:
            raise ProviderUnavailableError(f"OpenAI error: {e}") from e


class AnthropicLLM(LLMProvider):
    """Anthropic LLM provider using the anthropic SDK."""

    def __init__(
        self,
        api_key: str,
        model: str = "claude-sonnet-4-20250514",
    ) -> None:
        self._api_key = api_key
        self._model = model

    async def _check_available(self) -> None:
        """Verify the API key by making a minimal request."""
        try:
            from anthropic import AsyncAnthropic  # type: ignore[import-not-found]

            client = AsyncAnthropic(api_key=self._api_key)
            await client.messages.create(
                model=self._model,
                max_tokens=1,
                messages=[{"role": "user", "content": "hi"}],
            )
        except Exception as e:
            raise ProviderUnavailableError(f"Anthropic not available: {e}") from e

    async def generate(self, prompt: str, system: str | None = None) -> str:
        """Generate a complete response from Anthropic."""
        chunks: list[str] = []
        async for chunk in self.stream(prompt, system):
            chunks.append(chunk)
        return "".join(chunks)

    async def stream(self, prompt: str, system: str | None = None) -> AsyncIterator[str]:
        """Stream response tokens from Anthropic."""
        try:
            from anthropic import AsyncAnthropic  # type: ignore[import-not-found]

            client = AsyncAnthropic(api_key=self._api_key)

            kwargs: dict[str, object] = {
                "model": self._model,
                "max_tokens": 4096,
                "messages": [{"role": "user", "content": prompt}],
            }
            if system:
                kwargs["system"] = system

            async with client.messages.stream(**kwargs) as stream:  # type: ignore[arg-type]
                async for text in stream.text_stream:
                    yield text
        except ImportError as e:
            raise ProviderUnavailableError(
                "anthropic package not installed. Install with: pip install anthropic"
            ) from e
        except Exception as e:
            raise ProviderUnavailableError(f"Anthropic error: {e}") from e
