"""
Extractor factory for ETL pipelines.

Provides a registry pattern to select and instantiate the appropriate extractor
based on the source type specified in extract configuration.

Usage:
    from pycharter.etl_generator.extractors.factory import ExtractorFactory

    # Create extractor from config
    extractor = ExtractorFactory.create(extract_config)

    # Register custom extractor
    ExtractorFactory.register("kafka", KafkaExtractor)
"""

from __future__ import annotations

import importlib
import logging
from typing import Any, Type

from pycharter.etl_generator.extractors.base import BaseExtractor
from pycharter.etl_generator.extractors.cloud_storage import CloudStorageExtractor
from pycharter.etl_generator.extractors.database import DatabaseExtractor
from pycharter.etl_generator.extractors.file import FileExtractor
from pycharter.etl_generator.extractors.http import HTTPExtractor

logger = logging.getLogger(__name__)

# Optional extractors: type name -> (module_path, class_name). Loaded on first use.
_EXTRACTOR_OPTIONAL: dict[str, tuple[str, str]] = {
    "mongodb": ("pycharter.etl_generator.extractors.mongodb", "MongoDBExtractor"),
    "mongo": ("pycharter.etl_generator.extractors.mongodb", "MongoDBExtractor"),
    "sse": ("pycharter.etl_generator.extractors.sse", "SSEExtractor"),
    "websocket": ("pycharter.etl_generator.extractors.websocket", "WebSocketExtractor"),
    "file_watcher": (
        "pycharter.etl_generator.extractors.file_watcher",
        "FileWatcherExtractor",
    ),
    "kafka": ("pycharter.etl_generator.extractors.kafka", "KafkaExtractor"),
    "rabbitmq": ("pycharter.etl_generator.extractors.rabbitmq", "RabbitMQExtractor"),
    "sqs": ("pycharter.etl_generator.extractors.sqs", "SQSExtractor"),
}


class ExtractorFactory:
    """
    Factory for creating extractor instances based on type.

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

    Example:
        # With explicit type (recommended)
        config = {"type": "http", "url": "https://api.example.com/data"}
        extractor = ExtractorFactory.create(config)

        # Auto-detected from config keys
        config = {"file_path": "/path/to/data.csv"}
        extractor = ExtractorFactory.create(config)  # Detected as file
    """

    # Registry of extractors by source type (optional backends like mongodb are lazy-loaded)
    _registry: dict[str, Type[BaseExtractor]] = {
        "http": HTTPExtractor,
        "file": FileExtractor,
        "database": DatabaseExtractor,
        "cloud_storage": CloudStorageExtractor,
    }

    @classmethod
    def register(cls, type_name: str, extractor_class: Type[BaseExtractor]) -> None:
        """
        Register a custom extractor class.

        Args:
            type_name: Type identifier (e.g., 'kafka', 'mongodb')
            extractor_class: Extractor class that inherits from BaseExtractor

        Example:
            class KafkaExtractor(BaseExtractor):
                ...

            ExtractorFactory.register("kafka", KafkaExtractor)
        """
        if not issubclass(extractor_class, BaseExtractor):
            raise TypeError(
                f"Extractor class must inherit from BaseExtractor: {extractor_class}"
            )
        cls._registry[type_name.lower()] = extractor_class
        logger.debug(
            "Registered extractor: %s -> %s", type_name, extractor_class.__name__
        )

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

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

    @classmethod
    def _load_optional_extractor(cls, extract_type: str) -> Type[BaseExtractor]:
        """Load an optional extractor by type; register it for all aliases and return the class."""
        if extract_type not in _EXTRACTOR_OPTIONAL:
            raise ValueError(
                f"Unknown extractor type: '{extract_type}'. "
                f"Available types: {', '.join(cls.list_types())}. "
                f"Register custom extractors with ExtractorFactory.register()"
            )
        module_path, class_name = _EXTRACTOR_OPTIONAL[extract_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 extractor requires pymongo. "
                    "Install with: pip install pycharter[etl] or pip install pymongo"
                ) from e
            raise
        extractor_class = getattr(module, class_name)
        # Register for all type names that point to this (module_path, class_name)
        for name, (mod, clazz) in _EXTRACTOR_OPTIONAL.items():
            if (mod, clazz) == (module_path, class_name):
                cls._registry[name] = extractor_class
        return extractor_class

    @classmethod
    def create(cls, config: dict[str, Any]) -> BaseExtractor:
        """
        Create an extractor instance from configuration.

        Args:
            config: Extract configuration dictionary

        Returns:
            Configured extractor 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
        extract_type = config.get("type")

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

        extract_type = extract_type.lower()

        # Get extractor class from registry, or lazy-load optional backend
        extractor_class = cls._registry.get(extract_type)
        if not extractor_class and extract_type in _EXTRACTOR_OPTIONAL:
            extractor_class = cls._load_optional_extractor(extract_type)
        if not extractor_class:
            raise ValueError(
                f"Unknown extractor type: '{extract_type}'. "
                f"Available types: {', '.join(cls.list_types())}. "
                f"Register custom extractors with ExtractorFactory.register()"
            )

        # Create extractor using from_config if available
        if hasattr(extractor_class, "from_config"):
            extractor = extractor_class.from_config(config)
        else:
            extractor = extractor_class()
            if hasattr(extractor, "validate_config"):
                extractor.validate_config(config)

        logger.debug("Created %s for type: %s", extractor_class.__name__, extract_type)
        return extractor

    @classmethod
    def _detect_type(cls, config: dict[str, Any]) -> str | None:
        """
        Auto-detect extractor 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"

        # HTTP indicators
        if any(
            key in config for key in ("url", "base_url", "api_endpoint", "endpoint")
        ):
            return "http"

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

        # Database indicators (SQL databases)
        if "query" in config or (
            "database" in config
            and "connection_string" not in config.get("database", {}).get("url", "s3")
        ):
            return "database"

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

        return None
