from __future__ import annotations

import importlib.util
import logging
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Tuple

from mockmesh.config.loader import ConfigLoader
from mockmesh.core.fallback import FallbackConfig
from mockmesh.core.logger import configure_logging, get_logger
from mockmesh.core.registry import PluginRegistry
from mockmesh.response.engine import ResponseEngine
from mockmesh.storage.manager import StorageManager


def _pkg_available(package: str) -> bool:
    return importlib.util.find_spec(package) is not None


_PROVIDER_SENTINELS: dict[str, list[str]] = {
    "http":     ["requests"],
    "aws":      ["botocore"],
    "azure":    ["azure.core"],
    "gcp":      ["requests"],
    "kafka":    ["confluent_kafka", "kafka"],
    "rabbitmq": ["pika"],
    "sql":      ["psycopg2", "psycopg", "pymysql", "mysql.connector", "asyncpg", "aiomysql"],
    "mongodb":  ["pymongo"],
    "redis":    ["redis"],
}

_PLUGIN_ORDER: List[str] = ["http", "gcp", "aws", "azure", "kafka", "rabbitmq", "sql", "mongodb", "redis"]


def detect_available_providers() -> List[str]:
    available = []
    for provider in _PLUGIN_ORDER:
        sentinels = _PROVIDER_SENTINELS[provider]
        if any(_pkg_available(s) for s in sentinels):
            available.append(provider)
    return available


class MockMeshEngine:

    def __init__(
        self,
        config_path: Optional[str | Path] = None,
        storage_path: Optional[str | Path] = None,
        log_level: int = logging.DEBUG,
        console_log: bool = False,
        json_console: bool = True,
        plugins: Optional[list] = None,
        responses_path: Optional[str | Path] = None,
        fallback_mode: str = "mock",
        on_intercept_error: Optional[Callable[..., Any]] = None,
    ) -> None:
        ws = Path(storage_path) if storage_path else Path.cwd() / ".mockmesh"
        log_dir = ws / "logs"

        configure_logging(level=log_level, console_log=console_log,
                          json_console=json_console, log_dir=log_dir)
        self._log = get_logger("engine")

        self._responses_path = Path(responses_path) if responses_path else None
        self._config_path = config_path
        self._workspace = ws
        self._registry = PluginRegistry.instance()
        self._response_engine = ResponseEngine()
        self._storage = StorageManager(workspace=ws)
        self._fallback_config = FallbackConfig(
            mode=fallback_mode,
            on_intercept_error=on_intercept_error,
        )
        self._custom_handlers: Dict[Tuple[str, str], Callable] = {}
        self._active = False

        if plugins is not None:
            for p in plugins:
                self._registry.register(p)
            self._active_providers = [p.name for p in plugins]
        else:
            self._active_providers = detect_available_providers()
            self._register_available_plugins(self._active_providers)

    def _resolve_responses_folder(self) -> Optional[Path]:
        if self._responses_path is not None:
            if self._responses_path.is_dir():
                self._log.info("Using explicit responses_path",
                               extra={"mm_folder": str(self._responses_path)})
                return self._responses_path
            self._log.warning("responses_path is not a directory — ignored",
                              extra={"mm_path": str(self._responses_path)})

        auto = self._workspace
        if auto.is_dir() and any(auto.glob("*.json")):
            self._log.info("Auto-detected .mockmesh/ responses folder",
                           extra={"mm_folder": str(auto)})
            return auto

        return None

    def start(self) -> None:
        if self._active:
            self._log.warning("MockMesh already active – ignoring start()")
            return

        self._storage.initialize()

        folder = self._resolve_responses_folder()

        from mockmesh.interceptors.loader import load_all_provider_defaults
        provider_defaults = load_all_provider_defaults(folder)

        self._config_loader = ConfigLoader(
            user_config_path=self._config_path,
            responses_folder=folder,
        )

        resolver = self._config_loader.load()
        self._response_engine.load(resolver)
        flat_config = self._config_loader.load_flat()

        self._registry.activate_all(
            flat_config,
            storage=self._storage,
            provider_defaults=provider_defaults,
            fallback_config=self._fallback_config,
            custom_handlers=self._custom_handlers,
        )
        self._active = True
        self._log.info("MockMesh started", extra={
            "mm_plugins": [p.name for p in self._registry.plugins],
            "mm_providers": self._active_providers,
            "mm_config": str(self._config_path or "built-in defaults"),
            "mm_responses_folder": str(folder or "built-in defaults"),
            "mm_workspace": str(self._storage.workspace),
        })

    def stop(self) -> None:
        if not self._active:
            return
        self._registry.deactivate_all()
        self._storage.close()
        self._active = False
        self._log.info("MockMesh stopped")

    def __enter__(self) -> "MockMeshEngine":
        self.start()
        return self

    def __exit__(self, *_: object) -> None:
        self.stop()

    @property
    def response_engine(self) -> ResponseEngine:
        return self._response_engine

    @property
    def storage(self) -> StorageManager:
        return self._storage

    @property
    def active_providers(self) -> List[str]:
        return list(self._active_providers)

    @property
    def fallback_config(self) -> FallbackConfig:
        return self._fallback_config

    def register_handler(
        self, provider: str, operation: str, handler_fn: Callable[..., Any],
    ) -> None:
        self._custom_handlers[(provider, operation)] = handler_fn
        self._log.info(
            "Custom handler registered",
            extra={"mm_provider": provider, "mm_operation": operation},
        )

    def register_fallback(
        self, provider: str, fallback_fn: Callable[..., Any],
    ) -> None:
        original_cb = self._fallback_config.on_intercept_error

        def _chained_callback(prov: str, op: str, exc: Exception) -> Any:
            if prov == provider:
                result = fallback_fn(prov, op, exc)
                if result is not None:
                    return result
            if original_cb is not None:
                return original_cb(prov, op, exc)
            return None

        self._fallback_config.on_intercept_error = _chained_callback
        self._log.info(
            "Provider fallback registered",
            extra={"mm_provider": provider},
        )

    def _register_available_plugins(self, active_providers: List[str]) -> None:
        from mockmesh.interceptors.http import HttpInterceptor
        from mockmesh.interceptors.aws import AwsInterceptor
        from mockmesh.interceptors.azure import AzureInterceptor
        from mockmesh.interceptors.gcp import GcpInterceptor
        from mockmesh.interceptors.streaming import KafkaInterceptor
        from mockmesh.interceptors.streaming import RabbitMQInterceptor
        from mockmesh.interceptors.sql import SqlInterceptor
        from mockmesh.interceptors.nosql import MongoDBInterceptor
        from mockmesh.interceptors.nosql import RedisInterceptor

        _all = [
            ("http", HttpInterceptor(self._response_engine)),
            ("gcp", GcpInterceptor(self._response_engine)),
            ("aws", AwsInterceptor(self._response_engine)),
            ("azure", AzureInterceptor(self._response_engine)),
            ("kafka", KafkaInterceptor(self._response_engine)),
            ("rabbitmq", RabbitMQInterceptor(self._response_engine)),
            ("sql", SqlInterceptor(self._response_engine)),
            ("mongodb", MongoDBInterceptor(self._response_engine)),
            ("redis", RedisInterceptor(self._response_engine)),
        ]
        for name, plugin in _all:
            if name in active_providers:
                self._registry.register(plugin)
