"""Provider system for Logos ADK."""
from __future__ import annotations

from abc import ABC, abstractmethod
import asyncio
from fnmatch import fnmatch
from typing import Any, AsyncIterator, Callable

from logos_adk.errors import UnknownModelError, AmbiguousModelError
from logos_adk.types import Message, Response

ProviderFactory = Callable[[str], "Provider"]


class Provider(ABC):
    """Abstract base class for LLM providers."""

    @abstractmethod
    async def generate(
        self,
        messages: list[Message],
        tools: list[dict] | None = None,
        output_schema: Any = None,
        **kwargs: Any,
    ) -> Response:
        ...

    @abstractmethod
    async def stream(
        self,
        messages: list[Message],
        tools: list[dict] | None = None,
        **kwargs: Any,
    ) -> AsyncIterator[Any]:
        ...

    async def generate_batch(
        self,
        batch: list[list[Message]],
        *,
        tools: list[dict] | None = None,
        output_schema: Any = None,
        max_concurrency: int = 4,
        **kwargs: Any,
    ) -> list[Response]:
        """Generate responses for multiple conversations with bounded concurrency."""
        if not batch:
            return []
        semaphore = asyncio.Semaphore(max(1, max_concurrency))
        results: list[Response | None] = [None] * len(batch)

        async def run_one(index: int, messages: list[Message]) -> None:
            async with semaphore:
                results[index] = await self.generate(
                    messages,
                    tools=tools,
                    output_schema=output_schema,
                    **kwargs,
                )

        await asyncio.gather(*(run_one(index, messages) for index, messages in enumerate(batch)))
        return [result for result in results if result is not None]


class ProviderRegistry:
    """Registry mapping model name patterns to provider factories."""

    def __init__(self) -> None:
        self._entries: list[tuple[str, ProviderFactory]] = []

    def register(self, pattern: str, factory: ProviderFactory) -> None:
        self._entries.append((pattern, factory))

    def resolve(self, model_name: str) -> Provider:
        exact_matches: list[ProviderFactory] = []
        prefix_matches: list[ProviderFactory] = []

        for pattern, factory in self._entries:
            if pattern == model_name:
                exact_matches.append(factory)
            elif "*" in pattern and fnmatch(model_name, pattern):
                prefix_matches.append(factory)

        if exact_matches:
            if len(exact_matches) > 1:
                raise AmbiguousModelError(
                    f"Model '{model_name}' matches multiple exact registrations"
                )
            return exact_matches[0](model_name)

        if not prefix_matches:
            raise UnknownModelError(f"No provider registered for model '{model_name}'")

        if len(prefix_matches) > 1:
            raise AmbiguousModelError(
                f"Model '{model_name}' matches multiple provider patterns"
            )

        return prefix_matches[0](model_name)


provider_registry = ProviderRegistry()
