from __future__ import annotations

from typing import Any

from .base import BaseProvider, JsonDict, ProviderError, ensure_trailing_slashless, flatten_text_content, json_dumps_compact

DEFAULT_ANTHROPIC_MODELS = [
    "claude-3-5-haiku-latest",
    "claude-3-7-sonnet-latest",
    "claude-sonnet-4-5",
]


class AnthropicProvider(BaseProvider):
    provider_type = "anthropic"

    def __init__(
        self,
        *,
        api_key: str,
        base_url: str = "https://api.anthropic.com/v1",
        provider_name: str = "claude",
        timeout: float = 60.0,
        models: list[str] | None = None,
        http_client=None,
    ) -> None:
        super().__init__(provider_name=provider_name, timeout=timeout, http_client=http_client)
        self.api_key = api_key
        self.base_url = ensure_trailing_slashless(base_url)
        self.models = list(models or DEFAULT_ANTHROPIC_MODELS)

    def _ensure_configured(self) -> None:
        if not self.api_key:
            raise ProviderError(
                f"{self.provider_name} requires an API key. Configure it via Telegram /settings or /apikey {self.provider_name} <key>."
            )

    def _headers(self) -> dict[str, str]:
        self._ensure_configured()
        return {
            "x-api-key": self.api_key,
            "anthropic-version": "2023-06-01",
            "content-type": "application/json",
        }

    def _build_messages(self, messages: list[JsonDict]) -> list[JsonDict]:
        prepared: list[JsonDict] = []
        for message in messages:
            role = message.get("role") or "user"
            if role == "system":
                continue
            if role == "tool":
                tool_result = {
                    "type": "tool_result",
                    "tool_use_id": message.get("tool_call_id") or message.get("name") or "tool",
                    "content": json_dumps_compact(message.get("content", {})),
                }
                prepared.append({"role": "user", "content": [tool_result]})
                continue
            if role == "assistant" and message.get("tool_calls"):
                content: list[JsonDict] = []
                text = flatten_text_content(message.get("content"))
                if text:
                    content.append({"type": "text", "text": text})
                for tool_call in message.get("tool_calls") or []:
                    content.append(
                        {
                            "type": "tool_use",
                            "id": tool_call.get("id") or "tool-use",
                            "name": tool_call["name"],
                            "input": tool_call.get("arguments") or {},
                        }
                    )
                prepared.append({"role": "assistant", "content": content or [{"type": "text", "text": ""}]})
                continue
            text = flatten_text_content(message.get("content"))
            prepared.append({"role": "assistant" if role == "assistant" else "user", "content": [{"type": "text", "text": text}]})
        return prepared

    def _build_tools(self, tools: list[JsonDict] | None) -> list[JsonDict]:
        prepared: list[JsonDict] = []
        for tool in tools or []:
            if tool.get("type") == "function" and isinstance(tool.get("function"), dict):
                fn = tool["function"]
                prepared.append(
                    {
                        "name": fn["name"],
                        "description": fn.get("description", ""),
                        "input_schema": fn.get("parameters", {"type": "object", "properties": {}}),
                    }
                )
            else:
                prepared.append(
                    {
                        "name": tool["name"],
                        "description": tool.get("description", ""),
                        "input_schema": tool.get("parameters", {"type": "object", "properties": {}}),
                    }
                )
        return prepared

    def _normalise_response(self, payload: JsonDict, *, fallback_model: str) -> JsonDict:
        content = payload.get("content") or []
        text_chunks: list[str] = []
        tool_calls: list[JsonDict] = []
        for index, item in enumerate(content):
            if item.get("type") == "text":
                text_chunks.append(str(item.get("text", "")))
            if item.get("type") == "tool_use":
                tool_calls.append(
                    {
                        "id": item.get("id") or f"claude-tool-{index + 1}",
                        "name": item.get("name", "unknown"),
                        "arguments": item.get("input") or {},
                    }
                )
        return {
            "text": "\n".join(chunk for chunk in text_chunks if chunk).strip(),
            "tool_calls": tool_calls,
            "usage": payload.get("usage") or {},
            "model": payload.get("model") or fallback_model,
            "provider": self.provider_name,
            "raw": payload,
        }

    async def generate(
        self,
        messages: list[JsonDict],
        tools: list[JsonDict] | None,
        model: str,
        system_prompt: str | None = None,
        temperature: float | None = None,
    ) -> JsonDict:
        payload: JsonDict = {
            "model": model,
            "max_tokens": 8192,
            "messages": self._build_messages(messages),
        }
        if system_prompt:
            payload["system"] = system_prompt
        prepared_tools = self._build_tools(tools)
        if prepared_tools:
            payload["tools"] = prepared_tools
        if temperature is not None:
            payload["temperature"] = temperature
        response = await self.http.post_json(f"{self.base_url}/messages", payload=payload, headers=self._headers())
        return self._normalise_response(response.payload, fallback_model=model)

    async def list_models(self) -> list[str]:
        if not self.api_key:
            return self.models
        try:
            response = await self.http.get_json(f"{self.base_url}/models", headers=self._headers())
        except ProviderError:
            return self.models
        data = response.payload.get("data") or []
        names = [item.get("id") for item in data if item.get("id")]
        return sorted(set(names)) or self.models
