from __future__ import annotations

import os
from collections.abc import Callable
from pathlib import Path
from threading import RLock
from typing import Any

from app.config import config, require_env


MODEL_NOT_ALLOWED_MESSAGE = "Los proveedores legacy Groq/OpenAI están deshabilitados."


def _resolve_path(base_dir: Path, path_value: str) -> str:
    path = Path(path_value)
    return str(path if path.is_absolute() else (base_dir / path))


class LazyResourceProxy:
    def __init__(
        self,
        loader: Callable[[], Any],
        *,
        available: Callable[[], bool] | None = None,
        name: str,
    ) -> None:
        self._loader = loader
        self._available = available
        self._name = name
        self._resolved: Any | None = None
        self._lock = RLock()

    def __bool__(self) -> bool:
        if self._available is None:
            return True
        return bool(self._available())

    def __getattr__(self, item: str) -> Any:
        return getattr(self.force_load(), item)

    def __repr__(self) -> str:
        return f"<LazyResourceProxy name={self._name!r}>"

    def force_load(self) -> Any:
        if self._resolved is not None:
            return self._resolved
        with self._lock:
            if self._resolved is None:
                self._resolved = self._loader()
            return self._resolved

    def reset(self) -> None:
        with self._lock:
            self._resolved = None


class HeavyResourceRegistry:
    def __init__(self, base_dir: Path) -> None:
        self.base_dir = Path(base_dir)
        self._lock = RLock()
        self._resources: dict[str, Any] = {}
        self._soat_index_dir = self.base_dir / "soat_faiss"

    def groq_available(self) -> bool:
        return self.legacy_llm_providers_enabled() and bool(str(config.GROQ_API_KEY or "").strip())

    def gemini_available(self) -> bool:
        return bool(str(config.GEMINI_API_KEY or "").strip())

    def openai_stub_enabled(self) -> bool:
        return self.legacy_llm_providers_enabled() and bool(config.OPENAI_STUB_ENABLED)

    def legacy_llm_providers_enabled(self) -> bool:
        return bool(config.LEGACY_LLM_PROVIDERS_ENABLED)

    def soat_available(self) -> bool:
        return self._soat_index_dir.exists()

    def cie10_available(self) -> bool:
        cie10_index_path = Path(os.getenv("CIE10_FAISS_INDEX", "faiss_principal_jerarquico"))
        cie10_estructura_path = Path(os.getenv("CIE10_ESTRUCTURA_JSON", "cie10_estructura_completa.json"))
        resolved_index = cie10_index_path if cie10_index_path.is_absolute() else (self.base_dir / cie10_index_path)
        resolved_structure = (
            cie10_estructura_path if cie10_estructura_path.is_absolute() else (self.base_dir / cie10_estructura_path)
        )
        return resolved_index.exists() and resolved_structure.exists()

    def cups_available(self) -> bool:
        cups_index_path = Path(os.getenv("CUPS_FAISS_INDEX", "cups_faiss"))
        resolved_index = cups_index_path if cups_index_path.is_absolute() else (self.base_dir / cups_index_path)
        return resolved_index.exists()

    def set_soat_index_dir(self, index_dir: str | Path) -> None:
        with self._lock:
            self._soat_index_dir = Path(index_dir)
            self._resources.pop("soat", None)

    def _require_groq_api_key(self) -> str:
        return require_env(config.GROQ_API_KEY, "GROQ_API_KEY")

    def _get_or_create(self, key: str, loader: Callable[[], Any]) -> Any:
        resource = self._resources.get(key)
        if resource is not None:
            return resource
        with self._lock:
            resource = self._resources.get(key)
            if resource is None:
                resource = loader()
                self._resources[key] = resource
            return resource

    def get_groq_client(self) -> Any:
        def _loader() -> Any:
            if not self.legacy_llm_providers_enabled():
                raise RuntimeError(MODEL_NOT_ALLOWED_MESSAGE)
            from groq import Groq

            from app.core.logging import InstrumentedGroqClient

            return InstrumentedGroqClient(
                Groq(api_key=self._require_groq_api_key()),
                provider="groq",
                service="app_services",
            )

        return self._get_or_create("groq", _loader)

    def get_gemini_client(self) -> Any:
        def _loader() -> Any:
            from app.core.gemini_client import get_gemini_model

            return get_gemini_model(
                api_key=require_env(config.GEMINI_API_KEY, "GEMINI_API_KEY"),
                model_name=config.GEMINI_MODEL_DEFAULT,
            )

        return self._get_or_create("gemini", _loader)

    def get_groq_provider(self) -> Any:
        def _loader() -> Any:
            if not self.legacy_llm_providers_enabled():
                raise RuntimeError(MODEL_NOT_ALLOWED_MESSAGE)
            from app.llm.providers import GroqAdapter

            return GroqAdapter(
                api_key=config.GROQ_API_KEY,
                default_model=config.GROQ_MODEL_DEFAULT,
                service="heavy_resources_groq_provider",
            )

        return self._get_or_create("llm_groq_provider", _loader)

    def get_gemini_provider(self) -> Any:
        def _loader() -> Any:
            from app.llm.providers import GeminiAdapter

            return GeminiAdapter(
                api_key=config.GEMINI_API_KEY,
                default_model=config.GEMINI_MODEL_DEFAULT,
                service="heavy_resources_gemini_provider",
            )

        return self._get_or_create("llm_gemini_provider", _loader)

    def get_openai_provider(self) -> Any:
        def _loader() -> Any:
            if not self.legacy_llm_providers_enabled():
                raise RuntimeError(MODEL_NOT_ALLOWED_MESSAGE)
            from app.llm.providers import OpenAIAdapterStub

            return OpenAIAdapterStub(enabled=self.openai_stub_enabled())

        return self._get_or_create("llm_openai_provider", _loader)

    def get_model_selection_policy(self) -> Any:
        def _loader() -> Any:
            from app.llm.routing import DefaultModelSelectionPolicy

            return DefaultModelSelectionPolicy()

        return self._get_or_create("llm_policy", _loader)

    def get_llm_router(self) -> Any:
        def _loader() -> Any:
            from app.llm.routing import DefaultModelRouter

            providers = {
                "gemini": self.get_gemini_provider(),
            }
            if self.legacy_llm_providers_enabled():
                providers["groq"] = self.get_groq_provider()
                providers["openai"] = self.get_openai_provider()
            return DefaultModelRouter(
                providers=providers,
                policy=self.get_model_selection_policy(),
                service="heavy_resources_llm_router",
            )

        return self._get_or_create("llm_router", _loader)

    def get_cie10_retriever(self) -> Any:
        def _loader() -> Any:
            from modules.processing.cie10.RANGES_HTML import CIE10Retriever

            cie10_index_path = os.getenv("CIE10_FAISS_INDEX", "faiss_principal_jerarquico")
            cie10_estructura_path = os.getenv("CIE10_ESTRUCTURA_JSON", "cie10_estructura_completa.json")
            return CIE10Retriever(
                faiss_index_path=_resolve_path(self.base_dir, cie10_index_path),
                estructura_json_path=_resolve_path(self.base_dir, cie10_estructura_path),
                groq_api_key=config.GROQ_API_KEY if self.legacy_llm_providers_enabled() else None,
                groq_model=config.GROQ_MODEL_CIE10,
                llm_router=self.get_llm_router(),
            )

        return self._get_or_create("cie10", _loader)

    def get_cups_retriever(self) -> Any:
        def _loader() -> Any:
            from modules.processing.cie10.RANGES_CUPS import CUPSRetriever

            cups_index_path = os.getenv("CUPS_FAISS_INDEX", "cups_faiss")
            return CUPSRetriever(
                faiss_index_path=_resolve_path(self.base_dir, cups_index_path),
                groq_api_key=config.GROQ_API_KEY if self.legacy_llm_providers_enabled() else None,
                groq_model=config.GROQ_MODEL_CUPS,
                llm_router=self.get_llm_router(),
            )

        return self._get_or_create("cups", _loader)

    def get_soat_retriever(self) -> Any:
        def _loader() -> Any:
            from modules.processing.soat.soat_retriever import SOATRetriever

            if not self.soat_available():
                raise RuntimeError("Índice SOAT no disponible. Construye el índice antes de usar el módulo SOAT.")
            return SOATRetriever(
                faiss_index_path=str(self._soat_index_dir),
                groq_api_key=config.GROQ_API_KEY if self.legacy_llm_providers_enabled() else None,
                groq_model=config.GROQ_MODEL_SOAT,
                llm_router=self.get_llm_router(),
            )

        return self._get_or_create("soat", _loader)

    def warmup(self, *resource_names: str) -> dict[str, str]:
        requested = set(resource_names or ("groq", "gemini", "cie10", "cups", "soat"))
        results: dict[str, str] = {}
        for name in requested:
            try:
                if name == "groq":
                    if not self.groq_available():
                        results[name] = "skipped"
                        continue
                    self.get_groq_client()
                elif name == "gemini":
                    if not self.gemini_available():
                        results[name] = "skipped"
                        continue
                    self.get_gemini_client()
                elif name == "cie10":
                    self.get_cie10_retriever()
                elif name == "cups":
                    self.get_cups_retriever()
                elif name == "soat":
                    if not self.soat_available():
                        results[name] = "skipped"
                        continue
                    self.get_soat_retriever()
                else:
                    results[name] = "unknown"
                    continue
                results[name] = "ready"
            except Exception as exc:
                results[name] = f"error:{exc.__class__.__name__}"
        return results

    def status(self) -> dict[str, bool]:
        return {
            "legacy_llm_providers_enabled": self.legacy_llm_providers_enabled(),
            "groq_configured": self.groq_available(),
            "gemini_configured": self.gemini_available(),
            "openai_stub_enabled": self.openai_stub_enabled(),
            "cie10_available": self.cie10_available(),
            "cups_available": self.cups_available(),
            "soat_index_present": self.soat_available(),
            "llm_groq_provider_loaded": "llm_groq_provider" in self._resources,
            "llm_gemini_provider_loaded": "llm_gemini_provider" in self._resources,
            "llm_openai_provider_loaded": "llm_openai_provider" in self._resources,
            "llm_router_loaded": "llm_router" in self._resources,
            "cie10_loaded": "cie10" in self._resources,
            "cups_loaded": "cups" in self._resources,
            "soat_loaded": "soat" in self._resources,
        }

    def describe_status(self) -> dict[str, dict[str, bool]]:
        return {
            "groq_provider": {
                "enabled": self.legacy_llm_providers_enabled(),
                "configured": self.groq_available(),
                "loaded": "llm_groq_provider" in self._resources,
            },
            "gemini_provider": {
                "enabled": True,
                "configured": self.gemini_available(),
                "loaded": "llm_gemini_provider" in self._resources,
            },
            "openai_stub": {
                "enabled": self.openai_stub_enabled(),
                "loaded": "llm_openai_provider" in self._resources,
            },
            "llm_router": {
                "loaded": "llm_router" in self._resources,
            },
            "cie10_resource": {
                "available": self.cie10_available(),
                "loaded": "cie10" in self._resources,
            },
            "cups_resource": {
                "available": self.cups_available(),
                "loaded": "cups" in self._resources,
            },
            "soat_resource": {
                "available": self.soat_available(),
                "loaded": "soat" in self._resources,
            },
        }
