"""
Anthropic Claude model provider.

Supports:
  - Claude 4 Sonnet, Claude 4 Opus, Claude 3.5 Sonnet, etc.
  - Tool use (function calling)
  - System prompts as top-level parameter
  - Streaming (future)
"""

from __future__ import annotations

import json
import os
from typing import Any

from nexus.core.message import Message
from nexus.models.base import ModelProvider
from nexus.types import TokenUsage


class AnthropicProvider(ModelProvider):
    """
    LLM provider for Anthropic Claude models.

    Usage:
        provider = AnthropicProvider(model="claude-sonnet-4-20250514")
        response = await provider.chat(messages=[...])
    """

    def __init__(
        self,
        model: str = "claude-sonnet-4-20250514",
        api_key: str | None = None,
        base_url: str | None = None,
        default_temperature: float = 0.7,
        default_max_tokens: int = 4096,
    ) -> None:
        self._model = model
        self._api_key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
        self._base_url = base_url
        self._default_temperature = default_temperature
        self._default_max_tokens = default_max_tokens
        self._client: Any = None

    async def _get_client(self) -> Any:
        """Lazy-initialize the anthropic async client."""
        if self._client is None:
            try:
                from anthropic import AsyncAnthropic
            except ImportError:
                raise ImportError(
                    "anthropic package is required for AnthropicProvider. "
                    "Install it with: pip install nexus-agents[anthropic]"
                )
            kwargs: dict[str, Any] = {"api_key": self._api_key}
            if self._base_url:
                kwargs["base_url"] = self._base_url
            self._client = AsyncAnthropic(**kwargs)
        return self._client

    def _convert_messages(
        self, messages: list[dict[str, Any]]
    ) -> tuple[str | None, list[dict[str, Any]]]:
        """
        Convert OpenAI-format messages to Anthropic format.

        Extracts system message and converts tool results.
        Returns (system_prompt, anthropic_messages).
        """
        system_prompt: str | None = None
        anthropic_messages: list[dict[str, Any]] = []

        for msg in messages:
            role = msg.get("role", "user")

            if role == "system":
                system_prompt = msg.get("content", "")
                continue

            if role == "tool":
                # Convert OpenAI tool_result → Anthropic tool_result content block
                anthropic_messages.append({
                    "role": "user",
                    "content": [
                        {
                            "type": "tool_result",
                            "tool_use_id": msg.get("tool_call_id", ""),
                            "content": msg.get("content", ""),
                        }
                    ],
                })
                continue

            if role == "assistant" and "tool_calls" in msg:
                # Convert OpenAI tool_calls → Anthropic tool_use content blocks
                content_blocks: list[dict[str, Any]] = []
                if msg.get("content"):
                    content_blocks.append({"type": "text", "text": msg["content"]})
                for tc in msg["tool_calls"]:
                    func = tc.get("function", {})
                    content_blocks.append({
                        "type": "tool_use",
                        "id": tc.get("id", ""),
                        "name": func.get("name", ""),
                        "input": json.loads(func.get("arguments", "{}")),
                    })
                anthropic_messages.append({
                    "role": "assistant",
                    "content": content_blocks,
                })
                continue

            # Standard user/assistant message
            anthropic_messages.append({
                "role": role,
                "content": msg.get("content", ""),
            })

        return system_prompt, anthropic_messages

    def _convert_tools(
        self, tools: list[dict[str, Any]]
    ) -> list[dict[str, Any]]:
        """Convert OpenAI tool format to Anthropic tool format."""
        anthropic_tools: list[dict[str, Any]] = []
        for tool in tools:
            func = tool.get("function", tool)
            anthropic_tools.append({
                "name": func.get("name", ""),
                "description": func.get("description", ""),
                "input_schema": func.get("parameters", {"type": "object", "properties": {}}),
            })
        return anthropic_tools

    async def chat(
        self,
        messages: list[dict[str, Any]],
        tools: list[dict[str, Any]] | None = None,
        temperature: float | None = None,
        max_tokens: int | None = None,
        **kwargs: Any,
    ) -> dict[str, Any]:
        client = await self._get_client()

        system_prompt, anthropic_messages = self._convert_messages(messages)

        params: dict[str, Any] = {
            "model": self._model,
            "messages": anthropic_messages,
            "max_tokens": max_tokens or self._default_max_tokens,
            "temperature": temperature if temperature is not None else self._default_temperature,
        }

        if system_prompt:
            params["system"] = system_prompt

        if tools:
            params["tools"] = self._convert_tools(tools)

        params.update(kwargs)

        response = await client.messages.create(**params)

        # Parse response content blocks
        text_parts: list[str] = []
        tool_calls: list[dict[str, Any]] = []

        for block in response.content:
            if block.type == "text":
                text_parts.append(block.text)
            elif block.type == "tool_use":
                tool_calls.append({
                    "id": block.id,
                    "type": "function",
                    "function": {
                        "name": block.name,
                        "arguments": json.dumps(block.input),
                    },
                })

        content = "\n".join(text_parts)

        # Build normalized Message
        message = Message.agent(
            content=content,
            agent_name=self._model,
        )
        if tool_calls:
            message.metadata["tool_calls"] = tool_calls

        # Build token usage
        usage = TokenUsage(
            prompt_tokens=response.usage.input_tokens,
            completion_tokens=response.usage.output_tokens,
            total_tokens=response.usage.input_tokens + response.usage.output_tokens,
        )

        return {
            "message": message,
            "usage": usage,
            "model": self._model,
            "finish_reason": response.stop_reason,
            "tool_calls": tool_calls if tool_calls else None,
            "raw": response,
        }

    async def close(self) -> None:
        if self._client is not None:
            await self._client.close()
            self._client = None

    @property
    def model_name(self) -> str:
        return self._model
