"""
Snippet and Pipeline Loader.

Pure business logic for discovering and loading BioPype snippets and pipelines.
No UI dependencies - can be used by any interface (Panel, FastAPI, CLI, etc.).

Refactored to use bio-pype's validation classes instead of duplicating logic.
"""

import logging
import os
from pathlib import Path
from typing import Dict, List, Optional

import yaml

# Import only what's needed at module level
from pype.__config__ import PYPE_PIPELINES, reload_pype_snippets
from pype.misc import package_files
from pype.utils.pipeline import Pipeline, get_visible_pipelines
from pype.utils.snippets import snippets_modules_list

logger = logging.getLogger(__name__)


class _DummyLog:
    """Dummy log object for pipeline argument extraction."""

    def __init__(self):
        class _DummyLogger:
            def info(self, msg):
                pass

            def error(self, msg):
                pass

            def warning(self, msg):
                pass

            def debug(self, msg):
                pass

        self.log = _DummyLogger()
        self.__path__ = "/tmp"


class SnippetLoader:
    """Load and discover snippets and pipelines.

    This class provides a unified interface for discovering both:
    - Snippets (Python modules with argparse definitions)
    - Pipelines (YAML files with pipeline definitions)

    Refactored to use bio-pype's validation infrastructure for consistency.

    Example:
        >>> loader = SnippetLoader()
        >>> items = loader.discover()
        >>> snippet = loader.get_snippet("my_snippet")
        >>> print(snippet["arguments"])
    """

    def __init__(self):
        """Initialize the loader."""
        self.snippets_dict = snippets_modules_list({})
        # Separate collections for snippets and pipelines
        self.snippets_metadata: List[Dict] = []
        self.pipelines_metadata: List[Dict] = []
        # Cache for expensive operations
        self._snippet_args_cache: Dict[str, List[Dict]] = {}
        self._pipeline_args_cache: Dict[str, List[Dict]] = {}
        self._dummy_log = _DummyLog()

    def _convert_argument_info_to_dict(self, arg_info) -> Dict:
        """Convert introspect's ArgumentInfo to loader's dict format.

        Maintains backward compatibility with existing API consumers.

        Args:
            arg_info: ArgumentInfo object from pype.utils.introspect

        Returns:
            Dictionary with argument metadata in loader's format
        """
        return {
            "name": arg_info.name,
            "alias": arg_info.alias,
            "long_alias": arg_info.long_alias,  # NEW: for --bt/--tumor style flags
            "type": arg_info.type,
            "required": arg_info.required,
            "help": arg_info.help,
            "default": arg_info.default,
            "choices": arg_info.choices,  # NEW field from introspect
            "nargs": arg_info.nargs,
            # "action" field removed - was buggy, introspect detects bool via type field
        }

    def discover_snippets(self) -> List[Dict]:
        """Discover all available snippets.

        Returns:
            List of snippet metadata dictionaries
        """
        self.snippets_metadata = []

        for snippet_name, snippet_obj in sorted(self.snippets_dict.items()):
            try:
                metadata = self._extract_snippet_metadata(snippet_name, snippet_obj)
                metadata["item_type"] = "snippet"
                self.snippets_metadata.append(metadata)
            except Exception as e:
                logger.warning(
                    f"Failed to extract metadata for snippet {snippet_name}: {e}"
                )

        return self.snippets_metadata

    def discover_pipelines(self) -> List[Dict]:
        """Discover all available pipelines.

        Returns:
            List of pipeline metadata dictionaries
        """
        self.pipelines_metadata = []

        try:
            pipeline_names = get_visible_pipelines()
            for pipeline_name in pipeline_names:
                try:
                    metadata = self._extract_pipeline_metadata(pipeline_name)
                    metadata["item_type"] = "pipeline"
                    self.pipelines_metadata.append(metadata)
                except Exception as e:
                    logger.warning(
                        f"Failed to extract metadata for pipeline {pipeline_name}: {e}"
                    )
        except Exception as e:
            logger.warning(f"Failed to discover pipelines: {e}")

        return self.pipelines_metadata

    def _extract_snippet_metadata(self, snippet_name: str, snippet_obj) -> Dict:
        """Extract metadata from snippet using introspect module.

        Args:
            snippet_name: Name of the snippet
            snippet_obj: Snippet object (SnippetMd or Python module)

        Returns:
            Dictionary with name, description, arguments, and results
        """
        from pype.utils.introspect import get_snippet_interface

        try:
            # Use introspect for comprehensive metadata
            snippet_interface = get_snippet_interface(snippet_obj, snippet_name)

            return {
                "name": snippet_interface.name,
                "description": snippet_interface.description,
                "arguments": [
                    self._convert_argument_info_to_dict(arg)
                    for arg in snippet_interface.arguments
                ],
                "results": {k: f"Result: {k}" for k in snippet_interface.results},
                # Skip: requirements, namespaces, profile_vars (execution-only, not builder concern)
            }
        except Exception as e:
            logger.warning(
                f"Failed to extract metadata for snippet {snippet_name}: {e}"
            )
            # Fallback to minimal metadata
            return {
                "name": snippet_name,
                "description": "",
                "arguments": [],
                "results": {},
            }

    def _extract_snippet_arguments(self, snippet_name: str, snippet_obj) -> List[Dict]:
        """Extract argument information using pype.utils.introspect.

        Uses bio-pype's canonical introspection instead of custom parsing.

        Args:
            snippet_name: Name of the snippet
            snippet_obj: Snippet object

        Returns:
            List of argument dictionaries (see _convert_argument_info_to_dict)
        """
        from pype.utils.introspect import get_snippet_interface

        # Check cache first
        if snippet_name in self._snippet_args_cache:
            return self._snippet_args_cache[snippet_name]

        try:
            # Use introspect module
            snippet_interface = get_snippet_interface(snippet_obj, snippet_name)

            # Convert ArgumentInfo list to dict list
            arguments = [
                self._convert_argument_info_to_dict(arg_info)
                for arg_info in snippet_interface.arguments
            ]

            # Cache the result
            self._snippet_args_cache[snippet_name] = arguments
            return arguments

        except Exception as e:
            logger.warning(f"Failed to introspect snippet {snippet_name}: {e}")
            # Fallback to empty list
            return []

    def _extract_pipeline_metadata(self, pipeline_name: str) -> Dict:
        """Extract metadata from pipeline using bio-pype's pipeline_argparse_ui.

        Uses the same approach as PipelineValidator._extract_pipeline_arguments.
        This is MUCH better than parsing YAML because it uses the actual argparse
        interface that BioPype generates at runtime.

        Args:
            pipeline_name: Name of the pipeline

        Returns:
            Dictionary with pipeline metadata including properly extracted arguments
        """
        # Check cache first
        if pipeline_name in self._pipeline_args_cache:
            return {
                "name": pipeline_name,
                "description": self._get_pipeline_description(pipeline_name),
                "arguments": self._pipeline_args_cache[pipeline_name],
                "results": {},  # Pipelines don't have results like snippets
            }

        # Find pipeline YAML file
        pipelines = package_files(PYPE_PIPELINES, ".yaml")
        pipeline_file = None
        for p in pipelines:
            if Path(p).stem == pipeline_name:
                pipeline_file = p
                break

        if not pipeline_file:
            raise Exception(f"Pipeline file not found for {pipeline_name}")

        # Set PYPE_MODULES for snippet loading
        pype_modules_path = str(Path(pipeline_file).parents[1])
        os.environ["PYPE_MODULES"] = pype_modules_path

        # Load snippet modules
        pype_snippets = reload_pype_snippets()
        pype_snippets_modules = snippets_modules_list({}, pype_snippets)

        # Load pipeline using Pipeline class (same as PipelineValidator)
        try:
            pipeline = Pipeline(
                str(pipeline_file), pipeline_name, pype_snippets_modules
            )
        except Exception as e:
            logger.error(f"Failed to load pipeline {pipeline_name}: {e}")
            raise

        # Extract arguments using pipeline_argparse_ui
        # This is the KEY improvement - same as PipelineValidator._extract_pipeline_arguments
        arguments = self._extract_pipeline_arguments_via_argparse(pipeline)

        # Get description from YAML info section
        description = self._get_pipeline_description(pipeline_name)

        # Cache the arguments
        self._pipeline_args_cache[pipeline_name] = arguments

        return {
            "name": pipeline_name,
            "description": description,
            "arguments": arguments,
            "results": {},  # Pipelines don't have results like snippets
        }

    def _extract_pipeline_arguments_via_argparse(self, pipeline) -> List[Dict]:
        """Extract pipeline arguments using pype.utils.introspect.

        Args:
            pipeline: Loaded Pipeline object

        Returns:
            List of argument dictionaries
        """
        from pype.utils.introspect import get_pipeline_interface

        try:
            # Use introspect module
            pipeline_interface = get_pipeline_interface(pipeline, self._dummy_log)

            # Convert ArgumentInfo list to dict list
            return [
                self._convert_argument_info_to_dict(arg_info)
                for arg_info in pipeline_interface.arguments
            ]

        except Exception as e:
            logger.error(f"Could not introspect pipeline: {e}")
            return []

    def _get_pipeline_description(self, pipeline_name: str) -> str:
        """Get pipeline description from YAML info section.

        Args:
            pipeline_name: Name of the pipeline

        Returns:
            Description string
        """

        try:
            pipelines = package_files(PYPE_PIPELINES, ".yaml")
            for p in pipelines:
                if Path(p).stem == pipeline_name:
                    with open(p, "r") as f:
                        pipeline_data = yaml.safe_load(f)
                        return pipeline_data.get("info", {}).get("description", "")
        except Exception as e:
            logger.warning(
                f"Could not extract description for pipeline {pipeline_name}: {e}"
            )

        return ""

    def get_snippet(self, snippet_name: str) -> Optional[Dict]:
        """Get specific snippet metadata by name.

        Args:
            snippet_name: Name of snippet

        Returns:
            Snippet metadata dictionary or None if not found
        """
        for snippet in self.snippets_metadata:
            if snippet["name"] == snippet_name:
                return snippet
        return None

    def get_pipeline(self, pipeline_name: str) -> Optional[Dict]:
        """Get specific pipeline metadata by name.

        Args:
            pipeline_name: Name of pipeline

        Returns:
            Pipeline metadata dictionary or None if not found
        """
        for pipeline in self.pipelines_metadata:
            if pipeline["name"] == pipeline_name:
                return pipeline
        return None
