# pylint: disable=import-outside-toplevel,broad-exception-caught

"""Factory for creating processor instances based on configuration."""

from typing import Dict, Type, Optional
from mini_videorag.core.processor import BaseVideoProcessor


class ProcessorFactory:
    """
    Factory for creating processor instances based on configuration.

    Allows easy switching between different provider implementations
    via configuration without changing code.

    Uses lazy imports to avoid loading heavy ML dependencies (torch, transformers)
    until processors are actually created.

    Example:
        # Via config
        factory = ProcessorFactory()
        processor = factory.create("ocr", provider="easyocr")

        # Or with custom config
        config = {
            "ocr": {"provider": "easyocr", "languages": ["en", "fr"]},
            "transcription": {"provider": "openai_whisper"},
            "person_detection": {"provider": "yolo", "model_path": "yolo11n.pt"},
            "music_detection": {"provider": "shazam"}
        }
        factory = ProcessorFactory(config)
        ocr = factory.create("ocr")
    """

    _PROVIDER_PATHS: Dict[str, Dict[str, str]] = {
        "transcription": {
            "openai_whisper": "mini_videorag.processors.transcription:OpenAIWhisperProcessor",
            "hf_whisper": "mini_videorag.processors.transcription:HFWhisperProcessor",
        },
        "ocr": {
            "easyocr": "mini_videorag.processors.ocr:EasyOCRProcessor",
        },
        "person_detection": {
            "yolo": "mini_videorag.processors.person_detection:YOLOPersonDetectionProcessor",
        },
        "music_detection": {
            "shazam": "mini_videorag.processors.music_detection:ShazamMusicDetectionProcessor",
        },
        "object_detection": {
            "yolo": "mini_videorag.processors.object_detection:YOLOObjectDetectionProcessor",
        },
        "brand_detection": {
            "huggingface": "mini_videorag.processors.brand_detection:HFBrandDetectionProcessor",
        },
        "content_safety": {
            "huggingface": "mini_videorag.processors.content_safety:HFContentSafetyProcessor",
        },
        "frame_extraction": {
            "opencv": "mini_videorag.processors.frame_extraction:OpenCVFrameExtractor",
        },
        "subtitle_check": {
            "ngram": "mini_videorag.processors.subtitle_check:NgramSubtitleCheckProcessor",
        },
    }

    _class_cache: Dict[str, Type[BaseVideoProcessor]] = {}

    DEFAULTS: Dict[str, str] = {
        "transcription": "openai_whisper",
        "ocr": "easyocr",
        "person_detection": "yolo",
        "music_detection": "shazam",
        "object_detection": "yolo",
        "brand_detection": "huggingface",
        "content_safety": "huggingface",
        "frame_extraction": "opencv",
        "subtitle_check": "ngram",
    }

    def get_available_processor_types(self) -> list[str]:
        """
        Get list of available processor types.

        Returns:
            List of processor type names (e.g., ["ocr", "transcription", ...])
        """
        return list(self._provider_paths.keys())

    def __init__(self, config: Optional[Dict] = None, auto_discover: bool = False):
        """
        Initialize processor factory.

        Args:
            config: Configuration dict with processor settings
                   Format: {processor_type: {provider: str, **kwargs}}
            auto_discover: If True, automatically discover plugins via entry points
        """
        self.config = config or {}

        self._provider_paths = {k: dict(v) for k, v in self._PROVIDER_PATHS.items()}
        self._defaults = dict(self.DEFAULTS)

        if auto_discover:
            self.discover_plugins()

    def _load_processor_class(self, class_path: str) -> Type[BaseVideoProcessor]:
        """
        Lazily load a processor class from its import path.

        Args:
            class_path: Import path in format "module.path:ClassName"

        Returns:
            Processor class
        """
        if class_path in self._class_cache:
            return self._class_cache[class_path]

        module_path, class_name = class_path.split(":")

        import importlib

        module = importlib.import_module(module_path)
        processor_class = getattr(module, class_name)

        self._class_cache[class_path] = processor_class

        return processor_class

    def create(
        self, processor_type: str, provider: Optional[str] = None, **kwargs
    ) -> BaseVideoProcessor:
        """
        Create a processor instance.

        Args:
            processor_type: Type of processor (e.g., "ocr", "transcription")
            provider: Provider name (e.g., "easyocr", "openai_whisper")
                     If None, uses default from config or DEFAULTS
            **kwargs: Additional arguments to pass to processor constructor

        Returns:
            Processor instance

        Raises:
            ValueError: If processor type or provider not found
        """
        if processor_type not in self._provider_paths:
            available = ", ".join(self._provider_paths.keys())
            raise ValueError(
                f"Unknown processor type: {processor_type}. " f"Available: {available}"
            )

        if provider is None:
            processor_config = self.config.get(processor_type, {})
            provider = processor_config.get("provider", self._defaults.get(processor_type))

        if provider not in self._provider_paths[processor_type]:
            available = ", ".join(self._provider_paths[processor_type].keys())
            raise ValueError(
                f"Unknown provider '{provider}' for {processor_type}. " f"Available: {available}"
            )

        class_path = self._provider_paths[processor_type][provider]
        processor_class = self._load_processor_class(class_path)

        processor_config = self.config.get(processor_type, {})
        final_kwargs = {**processor_config, **kwargs}
        final_kwargs.pop("provider", None)

        return processor_class(**final_kwargs)

    def get_available_providers(self, processor_type: str) -> list:
        """
        Get list of available providers for a processor type.

        Args:
            processor_type: Type of processor

        Returns:
            List of provider names
        """
        if processor_type not in self._provider_paths:
            return []
        return list(self._provider_paths[processor_type].keys())

    def get_default_provider(self, processor_type: str) -> Optional[str]:
        """
        Get default provider for a processor type.

        Args:
            processor_type: Type of processor

        Returns:
            Default provider name or None if not found
        """
        return self._defaults.get(processor_type)

    def register_processor(
        self,
        processor_type: str,
        provider: str,
        class_path: str,
        set_as_default: bool = False,
    ) -> None:
        """
        Register a custom processor at runtime.

        Allows third-party processors to be registered without modifying source code.

        Args:
            processor_type: Type of processor (e.g., "scene_detection")
            provider: Provider name (e.g., "opencv")
            class_path: Import path in format "module.path:ClassName"
            set_as_default: If True, set as default provider for this type

        Raises:
            ValueError: If class_path format is invalid
        """
        import logging

        logger = logging.getLogger(__name__)

        if ":" not in class_path:
            raise ValueError(
                f"Invalid class_path format: '{class_path}'. "
                "Expected format: 'module.path:ClassName'"
            )

        if (
            processor_type in self._PROVIDER_PATHS
            and provider in self._PROVIDER_PATHS[processor_type]
        ):
            logger.warning(
                f"Registering custom processor overwrites built-in: {processor_type}.{provider}"
            )

        if processor_type not in self._provider_paths:
            self._provider_paths[processor_type] = {}
            logger.info(f"Created new processor type: {processor_type}")

        self._provider_paths[processor_type][provider] = class_path
        logger.info(f"Registered processor: {processor_type}.{provider} -> {class_path}")

        if set_as_default:
            self._defaults[processor_type] = provider
            logger.info(f"Set default provider for {processor_type}: {provider}")

    def discover_plugins(self) -> int:
        """
        Discover and register processors from entry points.

        Searches for entry points in the group "mini_videorag.processors".
        Entry point names should follow format: "processor_type.provider"

        Returns:
            Number of plugins discovered
        """
        import logging

        logger = logging.getLogger(__name__)

        try:
            from importlib.metadata import entry_points
        except ImportError:
            # Python < 3.10
            from importlib_metadata import entry_points

        discovered = 0

        eps = entry_points()
        if hasattr(eps, "select"):
            group_eps = eps.select(group="mini_videorag.processors")
        else:
            group_eps = eps.get("mini_videorag.processors", [])

        for ep in group_eps:
            try:
                if "." not in ep.name:
                    logger.warning(
                        f"Invalid entry point name format: '{ep.name}'. "
                        "Expected: 'processor_type.provider'"
                    )
                    continue

                processor_type, provider = ep.name.rsplit(".", 1)

                class_path = f"{ep.value}"

                self.register_processor(processor_type, provider, class_path)
                discovered += 1

            except Exception as e:
                logger.error(f"Failed to load entry point {ep.name}: {e}")
                continue

        if discovered > 0:
            logger.info(f"Discovered {discovered} processor plugin(s)")

        return discovered

    @classmethod
    def from_env(cls) -> "ProcessorFactory":
        """
        Create factory from environment variables.

        Reads processor configuration from environment:
        - PROCESSOR_{TYPE}_PROVIDER: Provider name (e.g., PROCESSOR_OCR_PROVIDER=easyocr)
        - PROCESSOR_{TYPE}_{PARAM}: Constructor parameters

        Returns:
            ProcessorFactory instance
        """
        import os

        config = {}
        for processor_type in cls._PROVIDER_PATHS:
            env_prefix = f"PROCESSOR_{processor_type.upper()}_"

            provider_key = f"{env_prefix}PROVIDER"
            provider = os.getenv(provider_key)

            if provider:
                config[processor_type] = {"provider": provider}

                for key, value in os.environ.items():
                    if key.startswith(env_prefix) and key != provider_key:
                        param_name = key[len(env_prefix) :].lower()
                        config[processor_type][param_name] = value

        return cls(config)
