from dataclasses import dataclass
from typing import Any, Dict, List, Optional
import json

from openai import OpenAI

from .logger import Logger
from .provider import ProviderConfig


@dataclass
class ToolCall:
    id: str
    name: str
    arguments: Dict[str, Any]


@dataclass
class LLMResult:
    content: str
    tool_calls: List[ToolCall]


class LLMClient:
    """Minimal LLM client wrapper supporting Responses API (fallback to ChatCompletions)."""

    def __init__(self, provider: ProviderConfig, logger: Optional[Logger] = None):
        self.provider = provider
        self.log = logger or Logger()
        base_url = provider.base_url
        if base_url and not base_url.rstrip("/").endswith("/v1"):
            base_url = base_url.rstrip("/") + "/v1"
        self.client = OpenAI(api_key=provider.api_key, base_url=base_url)

    def invoke(self, messages: List[dict], tools: Optional[List[dict]] = None) -> LLMResult:
        if self.provider.api_mode == "responses":
            try:
                return self._invoke_responses(messages, tools)
            except Exception as err:
                self.log.warn(f"Responses API failed ({err}), falling back to chat.completions")
        return self._invoke_chat(messages, tools)

    def _invoke_chat(self, messages: List[dict], tools: Optional[List[dict]]) -> LLMResult:
        response = self.client.chat.completions.create(
            model=self.provider.model,
            messages=messages,
            tools=tools or None,
        )
        if not response or not response.choices:
            raise RuntimeError("LLM response is empty")
        choice = response.choices[0].message
        content = choice.content or ""
        tool_calls: List[ToolCall] = []
        for call in choice.tool_calls or []:
            args = {}
            try:
                args = json.loads(call.function.arguments or "{}")
            except json.JSONDecodeError:
                self.log.warn(f"Failed to parse tool args for {call.function.name}")
            tool_calls.append(ToolCall(id=call.id, name=call.function.name, arguments=args))
        return LLMResult(content=content, tool_calls=tool_calls)

    def _invoke_responses(self, messages: List[dict], tools: Optional[List[dict]]) -> LLMResult:
        response = self.client.responses.create(
            model=self.provider.model,
            input=messages,
            tools=tools or None,
        )
        output = response.output_text or ""
        tool_calls: List[ToolCall] = []
        # Best-effort extraction of tool calls from Responses API payload.
        for item in response.output or []:
            content = getattr(item, "content", None)
            if not content:
                continue
            for block in content:
                if getattr(block, "type", "") != "tool_call":
                    continue
                name = getattr(block, "name", "")
                args = getattr(block, "arguments", {}) or {}
                tool_calls.append(ToolCall(id=getattr(block, "id", ""), name=name, arguments=args))
        return LLMResult(content=output, tool_calls=tool_calls)
