from __future__ import annotations

from ..typing import AsyncResult, Messages, MediaListType
from ..errors import ModelNotFoundError
from ..providers.retry_provider import IterListProvider
from ..image import is_data_an_audio
from ..providers.response import JsonConversation, ProviderInfo
from ..Provider.needs_auth import OpenaiChat, CopilotAccount
from ..Provider.hf import HuggingFace, HuggingFaceMedia
from ..Provider.hf_space import HuggingSpace
from .. import Provider
from .. import models
from ..Provider import Cloudflare, LMArenaProvider, Gemini, Grok, DeepSeekAPI, PerplexityLabs, LambdaChat, PollinationsAI, FreeRouter
from ..Provider import Microsoft_Phi_4, DeepInfraChat, Blackbox
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin

class AnyProvider(AsyncGeneratorProvider, ProviderModelMixin):
    default_model = "default"
    working = True

    @classmethod
    def get_models(cls, ignored: list[str] = []) -> list[str]:
        cls.audio_models = {}
        cls.image_models = []
        cls.vision_models = []
        cls.video_models = []
        model_with_providers = { 
            model: [
                provider for provider in providers
                if provider.working and getattr(provider, "parent", provider.__name__) not in ignored
            ] for model, (_, providers) in models.__models__.items()
        }
        model_with_providers = { 
            model: providers for model, providers in model_with_providers.items()
            if providers
        }
        cls.models_count = {
            model: len(providers) for model, providers in model_with_providers.items() if len(providers) > 1
        }
        all_models = [cls.default_model] + list(model_with_providers.keys())
        for provider in [OpenaiChat, PollinationsAI, HuggingSpace, Cloudflare, PerplexityLabs, Gemini, Grok]:
            if not provider.working or getattr(provider, "parent", provider.__name__) in ignored:
                continue
            if provider == PollinationsAI:
                all_models.extend([f"{provider.__name__}:{model}" for model in provider.get_models() if model not in all_models])
                cls.audio_models.update({f"{provider.__name__}:{model}": [] for model in provider.get_models() if model in provider.audio_models})
                cls.image_models.extend([f"{provider.__name__}:{model}" for model in provider.get_models() if model in provider.image_models])
                cls.vision_models.extend([f"{provider.__name__}:{model}" for model in provider.get_models() if model in provider.vision_models])
            else:
                all_models.extend(provider.get_models())
            cls.image_models.extend(provider.image_models)
            cls.vision_models.extend(provider.vision_models)
            cls.video_models.extend(provider.video_models)
        if CopilotAccount.working and CopilotAccount.parent not in ignored:
            all_models.extend(list(CopilotAccount.model_aliases.keys()))
        if PollinationsAI.working and PollinationsAI.__name__ not in ignored:
            all_models.extend(list(PollinationsAI.model_aliases.keys()))
        def clean_name(name: str) -> str:
            return name.split("/")[-1].split(":")[0].lower(
                ).replace("-instruct", ""
                ).replace("-chat", ""
                ).replace("-08-2024", ""
                ).replace("-03-2025", ""
                ).replace("-20250219", ""
                ).replace("-20241022", ""
                ).replace("-20240904", ""
                ).replace("-2025-04-16", ""
                ).replace("-2025-04-14", ""
                ).replace("-0125", ""
                ).replace("-2407", ""
                ).replace("-2501", ""
                ).replace("-0324", ""
                ).replace("-2409", ""
                ).replace("-2410", ""
                ).replace("-2411", ""
                ).replace("-1119", ""
                ).replace("-0919", ""
                ).replace("-02-24", ""
                ).replace("-03-25", ""
                ).replace("-03-26", ""
                ).replace("-01-21", ""
                ).replace("-002", ""
                ).replace(".1-", "-"
                ).replace("_", "."
                ).replace("c4ai-", ""
                ).replace("-preview", ""
                ).replace("-experimental", ""
                ).replace("-v1", ""
                ).replace("-fp8", ""
                ).replace("-bf16", ""
                ).replace("-hf", ""
                ).replace("llama3", "llama-3")
        for provider in [HuggingFace, HuggingFaceMedia, LMArenaProvider, LambdaChat, DeepInfraChat]:
            if not provider.working or getattr(provider, "parent", provider.__name__) in ignored:
                continue
            model_map = {clean_name(model): model for model in provider.get_models()}
            provider.model_aliases.update(model_map)
            all_models.extend(list(model_map.keys()))
            cls.image_models.extend([clean_name(model) for model in provider.image_models])
            cls.vision_models.extend([clean_name(model) for model in provider.vision_models])
            cls.video_models.extend([clean_name(model) for model in provider.video_models])
        for provider in [Microsoft_Phi_4, PollinationsAI]:
            if provider.working and getattr(provider, "parent", provider.__name__) not in ignored:
                cls.audio_models.update(provider.audio_models)
        cls.models_count.update({model: all_models.count(model) for model in all_models if all_models.count(model) > cls.models_count.get(model, 0)})
        return list(dict.fromkeys([model if model else cls.default_model for model in all_models]))

    @classmethod
    async def create_async_generator(
        cls,
        model: str,
        messages: Messages,
        stream: bool = True,
        media: MediaListType = None,
        ignored: list[str] = [],
        conversation: JsonConversation = None,
        **kwargs
    ) -> AsyncResult:
        providers = []
        if model and ":" in model:
            providers = model.split(":")
            model = providers.pop()
            providers = [getattr(Provider, provider) for provider in providers]
        elif not model or model == cls.default_model:
            model = ""
            has_image = False
            has_audio = "audio" in kwargs
            if not has_audio and media is not None:
                for media_data, filename in media:
                    if is_data_an_audio(media_data, filename):
                        has_audio = True
                        break
                    has_image = True
            if has_audio:
                providers = [PollinationsAI, Microsoft_Phi_4]
            elif has_image:
                providers = models.default_vision.best_provider.providers
            else:
                providers = models.default.best_provider.providers
        else:
            for provider in [
                OpenaiChat, Cloudflare, LMArenaProvider, PerplexityLabs, Gemini, Grok, DeepSeekAPI, FreeRouter, Blackbox,
                HuggingFace, HuggingFaceMedia, HuggingSpace, LambdaChat, CopilotAccount, PollinationsAI, DeepInfraChat
            ]:
                if provider.working:
                    if not model or model in provider.get_models() or model in provider.model_aliases:
                       providers.append(provider)
            if model in models.__models__:
                for provider in models.__models__[model][1]:
                    providers.append(provider)
        providers = [provider for provider in providers if provider.working and getattr(provider, "parent", provider.__name__) not in ignored]
        if len(providers) == 0:
            raise ModelNotFoundError(f"Model {model} not found in any provider.")
        if len(providers) == 1:
            provider = providers[0]
            if conversation is not None:
                child_conversation = getattr(conversation, provider.__name__, None)
                if child_conversation is not None:
                    kwargs["conversation"] = JsonConversation(**child_conversation)
            yield ProviderInfo(**provider.get_dict(), model=model)
            async for chunk in provider.get_async_create_function()(
                model,
                messages,
                stream=stream,
                media=media,
                **kwargs
            ):
                if isinstance(chunk, JsonConversation):
                    if conversation is None:
                        conversation = JsonConversation()
                    setattr(conversation, provider.__name__, chunk.get_dict())
                    yield conversation
                else:
                    yield chunk
            return
        async for chunk in IterListProvider(providers).get_async_create_function()(
            model,
            messages,
            stream=stream,
            media=media,
            **kwargs
        ):
            yield chunk

setattr(Provider, "AnyProvider", AnyProvider)
Provider.__map__["AnyProvider"] = AnyProvider
Provider.__providers__.append(AnyProvider)