"""OpenAI provider — concrete implementation of LLMProvider + EmbeddingProvider.

Uses the official openai SDK (AsyncOpenAI). Includes timeout via asyncio.wait_for,
JSON parse, and markdown fence stripping.

This is the first concrete provider shipped with the SDK. Users can implement
their own providers for Anthropic, Cohere, local models, etc.
"""

import asyncio
import logging
import re

logger = logging.getLogger(__name__)


class OpenAIProvider:
    """OpenAI implementation of LLMProvider + EmbeddingProvider.

    Implements both protocols with a single class via composition (not inheritance).
    The openai SDK is imported lazily to keep it an optional dependency.

    Usage::

        from arandu.providers.openai import OpenAIProvider

        provider = OpenAIProvider(api_key="sk-...")
        memory = MemoryClient(
            database_url="postgresql+psycopg://...",
            llm=provider,
            embeddings=provider,
        )
    """

    def __init__(
        self,
        api_key: str,
        model: str = "gpt-4o",
        embedding_model: str = "text-embedding-3-small",
        timeout: float = 30.0,
    ) -> None:
        try:
            from openai import AsyncOpenAI
        except ImportError as err:
            raise ImportError(
                "openai package is required for OpenAIProvider. Install it with: pip install openai"
            ) from err
        self._client = AsyncOpenAI(api_key=api_key)
        self._model = model
        self._embedding_model = embedding_model
        self._timeout = timeout

    # -- LLMProvider --

    async def complete(
        self,
        messages: list[dict],
        temperature: float = 0,
        response_format: dict | None = None,
        max_tokens: int | None = None,
    ) -> str:
        """Send a chat completion request and return the response text."""
        kwargs: dict = {
            "model": self._model,
            "messages": messages,
            "temperature": temperature,
        }
        if response_format is not None:
            kwargs["response_format"] = response_format
        if max_tokens is not None:
            kwargs["max_tokens"] = max_tokens

        response = await asyncio.wait_for(
            self._client.chat.completions.create(**kwargs),
            timeout=self._timeout,
        )
        content = response.choices[0].message.content or ""

        # Strip markdown fences (per openai-integration pattern)
        if content.startswith("```"):
            content = re.sub(r"^```\w*\n?", "", content)
            content = re.sub(r"\n?```$", "", content)

        return content

    # -- EmbeddingProvider --

    async def embed(self, texts: list[str]) -> list[list[float]]:
        """Generate embeddings for a batch of texts."""
        clean = [t.strip()[:8000] for t in texts if t.strip()]
        if not clean:
            return []

        response = await asyncio.wait_for(
            self._client.embeddings.create(
                model=self._embedding_model,
                input=clean,
            ),
            timeout=self._timeout,
        )
        return [d.embedding for d in response.data]

    async def embed_one(self, text: str) -> list[float] | None:
        """Generate an embedding for a single text."""
        if not text or not text.strip():
            return None
        results = await self.embed([text])
        return results[0] if results else None
