"""FailoverLLM — tries providers in order until one succeeds."""

from typing import AsyncIterator

import structlog

from fliiq.runtime.llm.providers import BaseLLM, LLMConfig, LLMResponse, create_llm

log = structlog.get_logger()


class FailoverLLM(BaseLLM):
    """Wraps multiple LLM configs; tries each in order on failure."""

    def __init__(self, configs: list[LLMConfig]):
        if not configs:
            raise ValueError("FailoverLLM requires at least one LLMConfig")
        # Use first config as the "primary" for BaseLLM init
        super().__init__(configs[0])
        self._providers: list[BaseLLM] = [create_llm(c) for c in configs]

    async def generate(
        self, messages: list[dict], system: str | None = None, tools: list[dict] | None = None
    ) -> LLMResponse:
        errors = []
        for provider in self._providers:
            try:
                return await provider.generate(messages, system, tools=tools)
            except Exception as e:
                log.warning("provider_failed", provider=provider.config.provider, error=str(e))
                errors.append((provider.config.provider, e))

        error_summary = "; ".join(f"{p}: {e}" for p, e in errors)
        raise RuntimeError(f"All LLM providers failed: {error_summary}")

    async def generate_stream(self, messages: list[dict], system: str | None = None) -> AsyncIterator[str]:
        errors = []
        for provider in self._providers:
            try:
                async for chunk in provider.generate_stream(messages, system):
                    yield chunk
                return
            except Exception as e:
                log.warning("provider_stream_failed", provider=provider.config.provider, error=str(e))
                errors.append((provider.config.provider, e))

        error_summary = "; ".join(f"{p}: {e}" for p, e in errors)
        raise RuntimeError(f"All LLM providers failed (streaming): {error_summary}")
