from jupyter_deploy import verify_utils
from jupyter_deploy.engine.engine_config import EngineConfigHandler
from jupyter_deploy.engine.enum import EngineType
from jupyter_deploy.engine.supervised_execution import CompletionContext, TerminalHandler
from jupyter_deploy.engine.terraform import tf_config
from jupyter_deploy.engine.vardefs import TemplateVariableDefinition
from jupyter_deploy.handlers.base_project_handler import BaseProjectHandler


class InvalidPreset(ValueError):
    """Raised when an invalid preset name is provided."""

    def __init__(self, preset_name: str, valid_presets: list[str]) -> None:
        self.preset_name = preset_name
        self.valid_presets = valid_presets
        super().__init__(f"Preset '{preset_name}' is invalid for this template.")


class ConfigHandler(BaseProjectHandler):
    _handler: EngineConfigHandler

    def __init__(self, output_filename: str | None = None, terminal_handler: TerminalHandler | None = None) -> None:
        """Base class to manage the configuration of a jupyter-deploy project."""
        super().__init__()
        self.preset_name: str | None = None

        if self.engine == EngineType.TERRAFORM:
            self._handler = tf_config.TerraformConfigHandler(
                project_path=self.project_path,
                project_manifest=self.project_manifest,
                command_history_handler=self.command_history_handler,
                output_filename=output_filename,
                terminal_handler=terminal_handler,
            )
        else:
            raise NotImplementedError(f"ConfigHandler implementation not found for engine: {self.engine}")

    def has_recorded_variables(self) -> bool:
        """Return True if the file generated by jd config to record variable values exists."""
        return self._handler.has_recorded_variables()

    def verify_preset_exists(self, preset_name: str) -> bool:
        """Return True if the requested preset is defined by this template."""
        return self._handler.verify_preset_exists(preset_name)

    def list_presets(self) -> list[str]:
        """Return the list of default presets that this template supports."""
        return self._handler.list_presets()

    def validate_preset(self, preset_name: str) -> None:
        """Validate that the preset exists, raise InvalidPreset if not.

        Raises:
            InvalidPreset: If the preset is not valid for this template.
        """
        if not self.verify_preset_exists(preset_name):
            valid_presets = self.list_presets()
            raise InvalidPreset(preset_name, valid_presets)

    def set_preset(self, preset_name: str | None) -> None:
        """Set the preset name to use for configuration."""
        self.preset_name = preset_name

    def verify_requirements(self) -> None:
        """Verify that all required tools are installed.

        Raises:
            ToolRequiredError: If a required tool is not installed or has an incorrect version.
        """
        requirements = self.project_manifest.get_requirements()
        verify_utils.verify_tools_installation(requirements)

    def has_used_preset(self, expected_preset_name: str | None) -> bool:
        """Return True if the handler has used the preset.

        Always return True if expected_preset argument is None.
        This method returns False when it detected recorded variables values that the users
        decided not to reset.
        """
        return expected_preset_name is None or expected_preset_name == self.preset_name

    def reset_recorded_variables(self) -> None:
        """Delete the file in the project dir where the previous inputs were recorded."""
        self._handler.reset_recorded_variables()

    def reset_recorded_secrets(self) -> None:
        """Delete the file in the project dir where the secrets were recorded."""
        self._handler.reset_recorded_secrets()

    def configure(
        self, variable_overrides: dict[str, TemplateVariableDefinition] | None = None
    ) -> CompletionContext | None:
        """Set inputs of the project, calls the engine, return CompletionContext."""
        return self._handler.configure(preset_name=self.preset_name, variable_overrides=variable_overrides)

    def record(self, record_vars: bool = False, record_secrets: bool = False) -> None:
        """Save the values of the variables to disk in the project dir."""
        self._handler.record(record_vars=record_vars, record_secrets=record_secrets)
