# SPDX-FileCopyrightText: 2025-2026 Global Computing Lab
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any

from a4x.orchestration.utils import StrCompatPathLike, StrCompatPathLikeForIsInstance

if TYPE_CHECKING:
    from a4x.orchestration.site import Site
    from a4x.orchestration.workflow import Workflow


class Plugin(ABC):
    """Base class for all A4X-Orchestration plugins."""

    def __init__(self, plugin_name: str, wflow: Workflow) -> None:
        """Initialize the plugin.

        :param plugin_name: the name of the plugin. Used for YAML config keys and certain messages produced by the base class
        :param wflow: the workflow to convert to the specific WMS/RJMS associated with the plugin
        """  # noqa: E501
        self.plugin_name = plugin_name
        self.a4x_wflow = wflow
        self.plugin_settings_key = f"{self.plugin_name}_plugin"
        self.plugin_settings = (
            self.a4x_wflow.annotations[self.plugin_settings_key]
            if self.plugin_settings_key in self.a4x_wflow.annotations
            else {}
        )

    def configure(
        self,
        *args,
        a4x_config_out_file: StrCompatPathLike | None = None,
        **kwargs,
    ) -> None:
        """Configure the workflow for the given WMS/RJMS.

        This method should do any work needed to create a concrete, runnable workflow.
        For many WMSes, this will involve generating config files and/or scripts for the WMS.

        :param args: positional arguments that are passed through to the plugin child class
        :param a4x_config_out_file: if provided, generate an A4X-Orchestration YAML config at the specified path
        :param kwargs: keyword arguments that are passed through to the plugin child class
        """
        self.configure_plugin(*args, **kwargs)
        self.plugin_settings = self.create_plugin_settings_for_a4x_config()
        if self.plugin_settings is not None and len(self.plugin_settings) > 0:
            self.a4x_wflow.annotations[f"{self.plugin_name}_plugin"] = self.plugin_settings
        if a4x_config_out_file is not None:
            if not isinstance(a4x_config_out_file, StrCompatPathLikeForIsInstance):
                raise TypeError(
                    "The 'a4x_config_out_file' argument must be a string or a type compliant with os.PathLike"
                )
            self.a4x_wflow.to_config(a4x_config_out_file)

    def get_plugin_settings_from_wflow(self) -> dict[str, Any] | None:
        """Get settings for the plugin from the YAML config file.

        This method is intended for use by plugin developers, not end users.

        When users invoke :code:`Plugin.configure`, A4X-Orchestration will add a special
        annotation named "<plugin_name>_settings" to the Workflow object. This annotation is used
        by the plugin to store settings that are needed to reload the workflow and plugin from
        a YAML config file. This method simply returns that annotation from the Workflow object.

        :return: the plugin settings, if present in the Workflow's annotations or :code:`None` otherwise
        """
        if f"{self.plugin_name}_plugin" not in self.a4x_wflow.annotations:
            return None
        return self.a4x_wflow.annotations[f"{self.plugin_name}_plugin"]

    @abstractmethod
    def execute(self, *args, **kwargs) -> None:
        """Run the workflow under the given WMS/RJMS.

        This method uses the files (or other information) generated by :code:`Plugin.configure`
        to actually run the workflow. This method may be blocking or non-blocking depending
        on the semantics of the WMS/RJMS.

        :param args: positional arguments defined by the child class
        :param kwargs: keyword arguments defined by the child class
        """

    @abstractmethod
    def create_plugin_settings_for_a4x_config(self) -> dict[str, Any]:
        """Create a dictionary of the plugin settings for the given WMS/RJMS.

        This method is intended for use by plugin developers, not end users.

        When users invoke :code:`Plugin.configure`, A4X-Orchestration will add a special
        annotation named "<plugin_name>_settings" to the Workflow object. This annotation is used
        by the plugin to store settings that are needed to reload the workflow and plugin from
        a YAML config file. This method generates the contents of that annotation.

        :return: a dict containing the plugin settings needed for restoration from an A4X-Orchestration YAML config
        """

    @abstractmethod
    def configure_plugin(self, *args, **kwargs) -> None:
        """Perform any WMS/RJMS-specific work needed for :code:`Plugin.configure`.

        This method is intended for use by plugin developers, not end users.

        This method is automatically called by :code:`self.configure`. Users
        do not need to call this method.

        :param args: positional arguments defined by the child class
        :param kwargs: keyword arguments defined by the child class
        """

    @classmethod
    @abstractmethod
    def get_default_site(cls) -> Site | None:
        """Return a default site on which workflow tasks should run if no other site is specified.

        This method is intended for plugin developers, not end users.

        Some WMSes/RJMSes have a default site that is used for tasks when no other site is specified.
        This method can be used to specify this default site in A4X-Orchestration. This method
        is automatically invoked by :code:`a4x.orchestration.convert_to_plugin` during plugin creation
        to automatically populate the default site as needed.

        :return: the default site or None if there is no default site
        """
