"""Anthropic provider — concrete implementation of LLMProvider.

Uses the official anthropic SDK (AsyncAnthropic). Implements LLMProvider
only — Anthropic does not offer an embeddings API. Use OpenAIProvider or
another embedding provider alongside this one.

Usage::

    from arandu.providers.anthropic import AnthropicProvider
    from arandu.providers.openai import OpenAIProvider

    llm = AnthropicProvider(api_key="sk-ant-...")
    embeddings = OpenAIProvider(api_key="sk-...", model="text-embedding-3-small")

    memory = MemoryClient(
        database_url="postgresql+psycopg://...",
        llm=llm,
        embeddings=embeddings,
    )
"""

import asyncio
import logging
import re

from arandu.protocols import LLMResult, TokenUsage

logger = logging.getLogger(__name__)


class AnthropicProvider:
    """Anthropic implementation of LLMProvider.

    Implements LLMProvider only (no embeddings — use a separate provider).
    The anthropic SDK is imported lazily to keep it an optional dependency.
    """

    def __init__(
        self,
        api_key: str,
        model: str = "claude-sonnet-4-20250514",
        timeout: float = 30.0,
        max_tokens: int = 4096,
    ) -> None:
        try:
            from anthropic import AsyncAnthropic
        except ImportError as err:
            raise ImportError(
                "anthropic package is required for AnthropicProvider. Install it with: pip install anthropic"
            ) from err
        self._client = AsyncAnthropic(api_key=api_key)
        self._model = model
        self._timeout = timeout
        self._default_max_tokens = max_tokens

    async def complete(
        self,
        messages: list[dict],
        temperature: float = 0,
        response_format: dict | None = None,
        max_tokens: int | None = None,
    ) -> LLMResult:
        """Send a message to Anthropic and return the result with usage.

        Maps the OpenAI-style messages format to Anthropic's API:
        - System messages are extracted and passed as the `system` parameter.
        - User/assistant messages are passed as the `messages` list.
        - `response_format={"type": "json_object"}` is not natively supported
          by Anthropic — instead, we append a JSON instruction to the system
          prompt when requested.
        """
        # Separate system messages from conversation
        system_parts: list[str] = []
        conversation: list[dict] = []
        for msg in messages:
            if msg.get("role") == "system":
                system_parts.append(msg.get("content", ""))
            else:
                conversation.append({"role": msg["role"], "content": msg.get("content", "")})

        system_text = "\n\n".join(system_parts) if system_parts else ""

        # Handle JSON response format — Anthropic doesn't have response_format
        if response_format and response_format.get("type") == "json_object":
            json_instruction = (
                "\n\nIMPORTANT: Respond with valid JSON only. No markdown, no explanation, just the JSON object."
            )
            system_text = system_text + json_instruction if system_text else json_instruction

        # Ensure conversation is not empty and starts with user message
        if not conversation:
            conversation = [{"role": "user", "content": ""}]

        kwargs: dict = {
            "model": self._model,
            "messages": conversation,
            "temperature": temperature,
            "max_tokens": max_tokens or self._default_max_tokens,
        }
        if system_text:
            kwargs["system"] = system_text

        response = await asyncio.wait_for(
            self._client.messages.create(**kwargs),
            timeout=self._timeout,
        )

        # Extract text from response
        content = ""
        for block in response.content:
            if hasattr(block, "text"):
                content += block.text

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

        usage: TokenUsage | None = None
        if response.usage:
            usage = TokenUsage(
                input_tokens=response.usage.input_tokens or 0,
                output_tokens=response.usage.output_tokens or 0,
                total_tokens=(response.usage.input_tokens or 0) + (response.usage.output_tokens or 0),
            )

        return LLMResult(text=content, usage=usage)
