"""LLM service for centralized LLM invocation."""

import json
from typing import Any

import litellm

from agent_flows.exceptions import ExecutorError
from agent_flows.services.llm.models import LLMRequest, LLMResponse
from agent_flows.utils.logging import get_logger

log = get_logger(__name__)


class LLMService:
    """Stateless service for LLM completion requests."""

    async def complete(self, request: LLMRequest) -> LLMResponse:
        """Execute LLM completion request.

        Args:
            request: Structured LLM request parameters

        Returns:
            LLMResponse with content and metadata

        Raises:
            ExecutorError: On LLM invocation failure
        """
        completion_kwargs = self._build_completion_kwargs(request)

        log.debug(
            "Making LLM request",
            model=request.model,
            temperature=request.temperature,
            max_tokens=request.max_tokens,
            message_count=len(request.messages),
        )

        try:
            response = await litellm.acompletion(**completion_kwargs)
            return self._parse_response(response, request.response_format)

        except litellm.Timeout as e:
            raise ExecutorError(f"LLM request timed out after {request.timeout}s") from e
        except litellm.RateLimitError as e:
            raise ExecutorError(f"LLM rate limit exceeded: {e}") from e
        except litellm.ServiceUnavailableError as e:
            raise ExecutorError(f"LLM service unavailable: {e}") from e
        except litellm.AuthenticationError as e:
            raise ExecutorError(f"LLM authentication failed: {e}") from e
        except litellm.InvalidRequestError as e:
            raise ExecutorError(f"Invalid LLM request: {e}") from e
        except Exception as e:
            raise ExecutorError(f"LLM request failed: {e}") from e

    def build_messages(
        self,
        user_content: str,
        system_prompt: str | None = None,
    ) -> list[dict[str, str]]:
        """Build standard message list for LLM request.

        Args:
            user_content: Main user message content
            system_prompt: Optional system prompt

        Returns:
            List of message dicts ready for LLM API
        """
        messages: list[dict[str, str]] = []
        if system_prompt:
            messages.append({"role": "system", "content": system_prompt})
        messages.append({"role": "user", "content": user_content})
        return messages

    def _build_completion_kwargs(self, request: LLMRequest) -> dict[str, Any]:
        """Build kwargs dict for litellm.acompletion."""
        kwargs: dict[str, Any] = {
            "model": request.model,
            "messages": request.messages,
            "temperature": request.temperature,
            "timeout": request.timeout,
            "max_retries": request.max_retries,
        }

        if request.max_tokens is not None:
            kwargs["max_tokens"] = request.max_tokens

        if request.response_format is not None:
            kwargs["response_format"] = request.response_format

        if request.api_base is not None:
            kwargs["api_base"] = request.api_base

        return kwargs

    def _parse_response(
        self,
        response: Any,
        response_format: dict[str, Any] | None,
    ) -> LLMResponse:
        """Parse LiteLLM response into structured LLMResponse."""
        if not hasattr(response, "choices") or not response.choices:
            raise ExecutorError("Invalid LLM response: no choices found")

        content = response.choices[0].message.content
        if not content:
            raise ExecutorError("Empty response from LLM")

        usage = {}
        if hasattr(response, "usage") and response.usage:
            usage = (
                response.usage.model_dump()
                if hasattr(response.usage, "model_dump")
                else dict(response.usage)
            )

        model = getattr(response, "model", None)

        # Parse JSON if response format indicates structured output
        parsed_content = None
        if response_format and response_format.get("type") in ("json_object", "json_schema"):
            try:
                parsed_content = json.loads(content) if isinstance(content, str) else content
            except json.JSONDecodeError as e:
                raise ExecutorError(f"Failed to parse JSON response: {e}") from e

        log.debug(
            "LLM response received",
            model=model,
            content_length=len(content),
            has_parsed_content=parsed_content is not None,
        )

        return LLMResponse(
            content=content,
            parsed_content=parsed_content,
            model=model,
            usage=usage,
        )
