"""
Loader factory for ETL pipelines.

Provides a registry pattern to select and instantiate the appropriate loader
based on the target type specified in load configuration.

Usage:
    from pycharter.etl_generator.loaders.factory import LoaderFactory

    # Create loader from config
    loader = LoaderFactory.create(load_config)

    # Register custom loader
    LoaderFactory.register("bigquery", BigQueryLoader)
"""

from __future__ import annotations

import importlib
import logging
from typing import Any, Type

from pycharter.etl_generator.loaders.base import BaseLoader
from pycharter.etl_generator.loaders.cloud_storage import CloudStorageLoader
from pycharter.etl_generator.loaders.database import DatabaseLoader, PostgresLoader
from pycharter.etl_generator.loaders.file import FileLoader

logger = logging.getLogger(__name__)

# Optional loaders: type name -> (module_path, class_name). Loaded on first use.
_LOADER_OPTIONAL: dict[str, tuple[str, str]] = {
    "mongodb": ("pycharter.etl_generator.loaders.mongodb", "MongoDBLoader"),
    "mongo": ("pycharter.etl_generator.loaders.mongodb", "MongoDBLoader"),
}


class LoaderFactory:
    """
    Factory for creating loader instances based on type.

    Supports:
    - Explicit 'type' field (recommended)
    - Auto-detection from config keys

    Example:
        # With explicit type (recommended)
        config = {"type": "postgres", "table": "users", "database": {"url": "..."}}
        loader = LoaderFactory.create(config)

        # Auto-detected from config keys
        config = {"file_path": "/path/to/output.json"}
        loader = LoaderFactory.create(config)  # Detected as file
    """

    # Registry of loaders by target type (optional backends like mongodb are lazy-loaded)
    _registry: dict[str, Type[BaseLoader]] = {
        "postgres": PostgresLoader,
        "postgresql": PostgresLoader,
        "database": DatabaseLoader,
        "sqlite": DatabaseLoader,
        "file": FileLoader,
        "cloud_storage": CloudStorageLoader,
    }

    @classmethod
    def register(cls, type_name: str, loader_class: Type[BaseLoader]) -> None:
        """
        Register a custom loader class.

        Args:
            type_name: Type identifier (e.g., 'bigquery', 'snowflake')
            loader_class: Loader class that inherits from BaseLoader

        Example:
            class BigQueryLoader(BaseLoader):
                ...

            LoaderFactory.register("bigquery", BigQueryLoader)
        """
        if not issubclass(loader_class, BaseLoader):
            raise TypeError(
                f"Loader class must inherit from BaseLoader: {loader_class}"
            )
        cls._registry[type_name.lower()] = loader_class
        logger.debug("Registered loader: %s -> %s", type_name, loader_class.__name__)

    @classmethod
    def unregister(cls, type_name: str) -> None:
        """Remove a loader from the registry."""
        cls._registry.pop(type_name.lower(), None)

    @classmethod
    def list_types(cls) -> list[str]:
        """List all registered loader types (including lazy optional backends)."""
        all_keys = set(cls._registry.keys()) | set(_LOADER_OPTIONAL.keys())
        return sorted(all_keys)

    @classmethod
    def _load_optional_loader(cls, load_type: str) -> Type[BaseLoader]:
        """Load an optional loader by type; register it for all aliases and return the class."""
        if load_type not in _LOADER_OPTIONAL:
            raise ValueError(
                f"Unknown loader type: '{load_type}'. "
                f"Available types: {', '.join(cls.list_types())}. "
                f"Register custom loaders with LoaderFactory.register()"
            )
        module_path, class_name = _LOADER_OPTIONAL[load_type]
        try:
            module = importlib.import_module(module_path)
        except ImportError as e:
            if "bson" in str(e) or "pymongo" in str(e):
                raise ImportError(
                    "MongoDB loader requires pymongo. "
                    "Install with: pip install pycharter[etl] or pip install pymongo"
                ) from e
            raise
        loader_class = getattr(module, class_name)
        # Register for all type names that point to this (module_path, class_name)
        for name, (mod, clazz) in _LOADER_OPTIONAL.items():
            if (mod, clazz) == (module_path, class_name):
                cls._registry[name] = loader_class
        return loader_class

    @classmethod
    def create(cls, config: dict[str, Any]) -> BaseLoader:
        """
        Create a loader instance from configuration.

        Args:
            config: Load configuration dictionary

        Returns:
            Configured loader instance

        Raises:
            ValueError: If type cannot be determined or is not registered
        """
        # Trigger lazy plugin discovery (no-op after first call)
        from pycharter.etl_generator._plugins import discover_plugins

        discover_plugins()

        # Get type from config
        load_type = config.get("type")

        # Auto-detect if not specified
        if not load_type:
            load_type = cls._detect_type(config)
            if load_type:
                logger.debug("Auto-detected loader type: %s", load_type)
            else:
                raise ValueError(
                    "Cannot determine loader type. "
                    f"Add 'type' field with one of: {', '.join(cls.list_types())}"
                )

        load_type = load_type.lower()

        # Get loader class from registry, or lazy-load optional backend
        loader_class = cls._registry.get(load_type)
        if not loader_class and load_type in _LOADER_OPTIONAL:
            loader_class = cls._load_optional_loader(load_type)
        if not loader_class:
            raise ValueError(
                f"Unknown loader type: '{load_type}'. "
                f"Available types: {', '.join(cls.list_types())}. "
                f"Register custom loaders with LoaderFactory.register()"
            )

        # Create loader using from_config if available
        if hasattr(loader_class, "from_config"):
            loader = loader_class.from_config(config)
        else:
            loader = loader_class()

        logger.debug("Created %s for type: %s", loader_class.__name__, load_type)
        return loader

    @classmethod
    def _detect_type(cls, config: dict[str, Any]) -> str | None:
        """
        Auto-detect loader type from configuration keys.

        This is for backward compatibility. New configs should use explicit 'type'.
        """
        # MongoDB indicators (check first as it has specific 'mongodb' config key)
        if "mongodb" in config:
            return "mongodb"

        # Database indicators (SQL databases)
        if "table" in config:
            if "connection_string" in config or "database" in config:
                # Check if it's SQLite
                conn_str = config.get("connection_string", "")
                if not conn_str and "database" in config:
                    conn_str = config["database"].get("url", "")
                if "sqlite" in conn_str.lower():
                    return "sqlite"
                return "postgres"

        # File indicators
        if (
            any(key in config for key in ("path", "file_path"))
            and "storage" not in config
        ):
            return "file"

        # Cloud storage indicators
        if any(key in config for key in ("storage", "bucket", "container")):
            return "cloud_storage"

        return None
