import importlib.util
import os
import sys  # Added this line

from .logger import belief_scope, logger as _logger
from .plugin_base import PluginBase, PluginConfig


# #region PluginLoader [C:3] [TYPE Class] [SEMANTICS plugin, loader, dynamic, import]
# @BRIEF Scans a specified directory for Python modules, dynamically loads them, and registers any classes that are valid implementations of the PluginBase interface.
# @LAYER Core
# @PURPOSE Discovers and manages available PluginBase implementations.
# @RATIONALE Replaced print() with _logger calls to eliminate silent failures. Added top-level logger import, removed late imports.
# @REJECTED Keeping print() statements with "Replace with proper logging" comments was rejected — they produce no structured output and cannot be filtered by log level.
class PluginLoader:
    """
    Scans a directory for Python modules, loads them, and identifies classes
    that inherit from PluginBase.
    """
    # #region __init__ [TYPE Function]
    # @PURPOSE: Initializes the PluginLoader with a directory to scan.
    # @PRE      plugin_dir is a valid directory path.
    # @POST     Plugins are loaded and registered.
    # @PARAM  plugin_dir (str) - The directory containing plugin modules.
    def __init__(self, plugin_dir: str):
        with belief_scope("__init__"):
            self.plugin_dir = plugin_dir
            self._plugins: dict[str, PluginBase] = {}
            self._plugin_configs: dict[str, PluginConfig] = {}
            self._load_plugins()
    # #endregion __init__
    # #region _load_plugins [TYPE Function]
    # @PURPOSE: Scans the plugin directory and loads all valid plugins.
    # @PRE      plugin_dir exists or can be created.
    # @POST     _load_module is called for each .py file.
    def _load_plugins(self):
        with belief_scope("_load_plugins"):
            """
            Scans the plugin directory, imports modules, and registers valid plugins.
            """
        if not os.path.exists(self.plugin_dir):
            os.makedirs(self.plugin_dir)
        # Add the plugin directory's parent to sys.path to enable relative imports within plugins
        # This assumes plugin_dir is something like 'backend/src/plugins'
        # and we want 'backend/src' to be on the path for 'from ..core...' imports
        plugin_parent_dir = os.path.abspath(os.path.join(self.plugin_dir, os.pardir))
        if plugin_parent_dir not in sys.path:
            sys.path.insert(0, plugin_parent_dir)
        for filename in os.listdir(self.plugin_dir):
            file_path = os.path.join(self.plugin_dir, filename)

            # Handle directory-based plugins (packages)
            if os.path.isdir(file_path):
                init_file = os.path.join(file_path, "__init__.py")
                if os.path.exists(init_file):
                    self._load_module(filename, init_file)
                continue
            # Handle single-file plugins
            if filename.endswith(".py") and filename != "__init__.py":
                module_name = filename[:-3]
                self._load_module(module_name, file_path)
    # #endregion _load_plugins
    # #region _load_module [TYPE Function]
    # @PURPOSE: Loads a single Python module and discovers PluginBase implementations.
    # @PRE      module_name and file_path are valid.
    # @POST     Plugin classes are instantiated and registered.
    # @PARAM  module_name (str) - The name of the module.
    # @PARAM  file_path (str) - The path to the module file.
    def _load_module(self, module_name: str, file_path: str):
        with belief_scope("_load_module"):
            """
            Loads a single Python module and extracts PluginBase subclasses.
            """
        # All runtime code is imported through the canonical `src` package root.
        package_name = f"src.plugins.{module_name}"

        spec = importlib.util.spec_from_file_location(package_name, file_path)
        if spec is None or spec.loader is None:
            _logger.error(f"Could not load module spec for {package_name}")
            return
        module = importlib.util.module_from_spec(spec)
        try:
            spec.loader.exec_module(module)
        except Exception as e:
            _logger.error(f"Error loading plugin module {module_name}: {e}")
            return
        for attribute_name in dir(module):
            attribute = getattr(module, attribute_name)
            if (
                isinstance(attribute, type)
                and issubclass(attribute, PluginBase)
                and attribute is not PluginBase
            ):
                try:
                    plugin_instance = attribute()
                    self._register_plugin(plugin_instance)
                except Exception as e:
                    _logger.error(f"Error instantiating plugin {attribute_name} in {module_name}: {e}")
    # #endregion _load_module
    # #region _register_plugin [TYPE Function]
    # @PURPOSE: Registers a PluginBase instance and its configuration.
    # @PRE      plugin_instance is a valid implementation of PluginBase.
    # @POST     Plugin is added to _plugins and _plugin_configs.
    # @PARAM  plugin_instance (PluginBase) - The plugin instance to register.
    def _register_plugin(self, plugin_instance: PluginBase):
        with belief_scope("_register_plugin"):
            """
            Registers a valid plugin instance.
            """
        plugin_id = plugin_instance.id
        if plugin_id in self._plugins:
            _logger.warning(f"Warning: Duplicate plugin ID '{plugin_id}' found. Skipping.")
            return
        try:
            schema = plugin_instance.get_schema()
            # Basic validation to ensure it's a dictionary
            if not isinstance(schema, dict):
                raise TypeError("get_schema() must return a dictionary.")

            plugin_config = PluginConfig(
                id=plugin_instance.id,
                name=plugin_instance.name,
                description=plugin_instance.description,
                version=plugin_instance.version,
                ui_route=plugin_instance.ui_route,
                schema=schema,
            )
            # The following line is commented out because it requires a schema to be passed to validate against.
            # The schema provided by the plugin is the one being validated, not the data.
            # validate(instance={}, schema=schema)
            self._plugins[plugin_id] = plugin_instance
            self._plugin_configs[plugin_id] = plugin_config
            _logger.info(f"Plugin '{plugin_instance.name}' (ID: {plugin_id}) loaded successfully.")
        except Exception as e:
            _logger.error(f"Error validating plugin '{plugin_instance.name}' (ID: {plugin_id}): {e}")
    # #endregion _register_plugin
    # #region get_plugin [TYPE Function]
    # @PURPOSE: Retrieves a loaded plugin instance by its ID.
    # @PRE      plugin_id is a string.
    # @POST     Returns plugin instance or None.
    # @PARAM  plugin_id (str) - The unique identifier of the plugin.
    # @RETURN  Optional[PluginBase] - The plugin instance if found, otherwise None.
    def get_plugin(self, plugin_id: str) -> PluginBase | None:
        with belief_scope("get_plugin"):
            """
            Returns a loaded plugin instance by its ID.
            """
        return self._plugins.get(plugin_id)
    # #endregion get_plugin
    # #region get_all_plugin_configs [TYPE Function]
    # @PURPOSE: Returns a list of all registered plugin configurations.
    # @PRE      None.
    # @POST     Returns list of all PluginConfig objects.
    # @RETURN  List[PluginConfig] - A list of plugin configurations.
    def get_all_plugin_configs(self) -> list[PluginConfig]:
        with belief_scope("get_all_plugin_configs"):
            """
            Returns a list of all loaded plugin configurations.
            """
        return list(self._plugin_configs.values())
    # #endregion get_all_plugin_configs
    # #region has_plugin [TYPE Function]
    # @PURPOSE: Checks if a plugin with the given ID is registered.
    # @PRE      plugin_id is a string.
    # @POST     Returns True if plugin exists.
    # @PARAM  plugin_id (str) - The unique identifier of the plugin.
    # @RETURN  bool - True if the plugin is registered, False otherwise.
    def has_plugin(self, plugin_id: str) -> bool:
        with belief_scope("has_plugin"):
            """
            Checks if a plugin with the given ID is loaded.
            """
        return plugin_id in self._plugins
    # #endregion has_plugin
# #endregion PluginLoader
