"""Optional LLM provider abstractions used by chat and briefing features."""

from __future__ import annotations

import json
import os
from dataclasses import dataclass
from typing import Any, Protocol


def _normalize_messages(messages: list[dict[str, str]]) -> list[dict[str, str]]:
    normalized: list[dict[str, str]] = []
    for raw in messages:
        if not isinstance(raw, dict):
            continue
        role = str(raw.get("role") or "").strip().lower()
        content = str(raw.get("content") or "").strip()
        if not content:
            continue
        if role not in {"system", "user", "assistant"}:
            role = "user"
        normalized.append({"role": role, "content": content})
    if not normalized:
        normalized.append({"role": "user", "content": "Provide a concise response."})
    return normalized


def _error_text(provider: str, message: str) -> str:
    payload = {
        "error": {
            "provider": provider,
            "message": str(message or "unknown error").strip() or "unknown error",
        }
    }
    return json.dumps(payload, ensure_ascii=True)


class LLMProvider(Protocol):
    def chat(self, messages: list[dict[str, str]], *, max_tokens: int = 1024) -> str: ...


@dataclass
class AnthropicProvider:
    """Anthropic provider using ANTHROPIC_API_KEY and Claude Sonnet 4."""

    api_key: str
    model: str = "claude-sonnet-4-20250514"

    def chat(self, messages: list[dict[str, str]], *, max_tokens: int = 1024) -> str:
        normalized = _normalize_messages(messages)
        if not self.api_key:
            return _error_text("anthropic", "ANTHROPIC_API_KEY is not configured")
        try:
            import anthropic  # type: ignore
        except Exception:
            return _error_text("anthropic", "anthropic package is not installed")

        try:
            client = anthropic.Anthropic(api_key=self.api_key)
            system_parts = [row["content"] for row in normalized if row.get("role") == "system"]
            chat_messages = [row for row in normalized if row.get("role") in {"user", "assistant"}]
            if not chat_messages:
                chat_messages = [{"role": "user", "content": "Provide a concise response."}]
            payload: dict[str, Any] = {
                "model": self.model,
                "max_tokens": max(1, int(max_tokens)),
                "messages": chat_messages,
            }
            if system_parts:
                payload["system"] = "\n\n".join(system_parts)
            response = client.messages.create(**payload)
            blocks = getattr(response, "content", None)
            if isinstance(blocks, list):
                parts: list[str] = []
                for block in blocks:
                    text_value = getattr(block, "text", None)
                    if isinstance(text_value, str) and text_value.strip():
                        parts.append(text_value.strip())
                if parts:
                    return "\n".join(parts)
            text = str(response).strip()
            return text or _error_text("anthropic", "empty response")
        except Exception as exc:
            return _error_text("anthropic", str(exc))


@dataclass
class OpenAIProvider:
    """OpenAI provider using OPENAI_API_KEY and gpt-4o."""

    api_key: str
    model: str = "gpt-4o"

    def chat(self, messages: list[dict[str, str]], *, max_tokens: int = 1024) -> str:
        normalized = _normalize_messages(messages)
        if not self.api_key:
            return _error_text("openai", "OPENAI_API_KEY is not configured")
        try:
            import openai  # type: ignore
        except Exception:
            return _error_text("openai", "openai package is not installed")

        try:
            client = openai.OpenAI(api_key=self.api_key)
            response = client.chat.completions.create(
                model=self.model,
                messages=normalized,
                max_tokens=max(1, int(max_tokens)),
            )
            choices = getattr(response, "choices", None)
            if isinstance(choices, list) and choices:
                message = getattr(choices[0], "message", None)
                content = getattr(message, "content", None)
                if isinstance(content, str) and content.strip():
                    return content.strip()
                if isinstance(content, list):
                    parts: list[str] = []
                    for entry in content:
                        if isinstance(entry, dict):
                            text = str(entry.get("text") or "").strip()
                            if text:
                                parts.append(text)
                    if parts:
                        return "\n".join(parts)
            text = str(response).strip()
            return text or _error_text("openai", "empty response")
        except Exception as exc:
            return _error_text("openai", str(exc))


def resolve_provider() -> LLMProvider:
    """Return the first configured provider (Anthropic, then OpenAI)."""

    anthropic_key = str(os.getenv("ANTHROPIC_API_KEY") or "").strip()
    if anthropic_key:
        return AnthropicProvider(api_key=anthropic_key)
    openai_key = str(os.getenv("OPENAI_API_KEY") or "").strip()
    if openai_key:
        return OpenAIProvider(api_key=openai_key)
    raise RuntimeError("No LLM provider configured. Set ANTHROPIC_API_KEY or OPENAI_API_KEY.")


__all__ = [
    "LLMProvider",
    "AnthropicProvider",
    "OpenAIProvider",
    "resolve_provider",
]
