# -*- coding: utf-8 -*-

"""Core structures for third-party application descriptions."""

__all__ = ['BaseApp', 'BaseWebApp', 'BaseInstallableApp']

import abc
import typing
from anaconda_navigator.utils.constants import AppType

if typing.TYPE_CHECKING:
    import typing_extensions
    from anaconda_navigator.api import process
    from anaconda_navigator.config import user as user_config
    from . import detectors


class BaseApp(metaclass=abc.ABCMeta):  # pylint: disable=too-few-public-methods,too-many-instance-attributes
    """
    Root description of the application.

    :param app_type: Type of the application (web application, installable, etc.).
    :param app_name: Alias of the application in a package-naming format.
    :param display_name: Name of the application to show in home tile.
    :param description: Description of the application to show in home tile.
    :param image_path: Application icon to show in home tile.
    :param config: Application configuration.
    """

    def __init__(  # pylint: disable=too-many-arguments
            self,
            app_type: AppType,
            app_name: str,
            display_name: str,
            description: str,
            image_path: str,
            is_available: bool,
            non_conda: bool,
            config: 'user_config.UserConfig',
    ) -> None:
        """Initialize new :class:`~BaseApp` instance."""
        self.app_type: 'typing_extensions.Final[AppType]' = app_type
        self.app_name: 'typing_extensions.Final[str]' = app_name
        self.display_name: 'typing_extensions.Final[str]' = display_name
        self.description: 'typing_extensions.Final[str]' = description
        self.image_path: 'typing_extensions.Final[str]' = image_path
        self.non_conda: 'typing_extensions.Final[bool]' = non_conda
        self.config: 'typing_extensions.Final[user_config.UserConfig]' = config
        self.is_available: 'typing_extensions.Final[bool]' = is_available


class BaseWebApp(BaseApp, metaclass=abc.ABCMeta):  # pylint: disable=too-few-public-methods
    """
    Common base for all external web applications.

    Most of the params reused from the :class:`~BaseApp`.

    :param url: Web page to open for the application.
    """

    def __init__(  # pylint: disable=too-many-arguments
            self,
            app_name: str,
            display_name: str,
            description: str,
            image_path: str,
            url: str,
            is_available: bool,
            config: 'user_config.UserConfig',
    ) -> None:
        """Initialize new :class:`~BaseWebApp` instance."""
        super().__init__(
            app_type=AppType.WEB,
            app_name=app_name,
            display_name=display_name,
            description=description,
            image_path=image_path,
            is_available=is_available,
            non_conda=True,
            config=config,
        )

        self.url: 'typing_extensions.Final[str]' = url


class BaseInstallableApp(BaseApp, metaclass=abc.ABCMeta):
    """
    Common base for all external installable applications.

    Most of the params reused from the :class:`~BaseApp`.

    :param detector: Detector of the application location.
    :param process_api: API to spawn new processes (for extensions installation etc.).
    """

    def __init__(  # pylint: disable=too-many-arguments
            self,
            app_name: str,
            display_name: str,
            description: str,
            image_path: str,
            detector: 'detectors.Source',
            is_available: bool,
            process_api: 'process.WorkerManager',
            config: 'user_config.UserConfig',
            extra_arguments: typing.Sequence[typing.Any],
    ) -> None:
        """Initialize new :class:`~BaseInstallableApp` instance."""
        super().__init__(
            app_type=AppType.INSTALLABLE,
            app_name=app_name,
            display_name=display_name,
            description=description,
            image_path=image_path,
            is_available=is_available,
            non_conda=True,
            config=config,
        )
        self.extra_arguments: 'typing_extensions.Final[typing.Sequence[typing.Any]]' = extra_arguments
        self._process_api: 'typing_extensions.Final[process.WorkerManager]' = process_api

        location: 'typing.Optional[detectors.DetectedApplication]'
        for location in detector():
            if location.complete:
                break
        else:
            location = None
        self.__location: 'typing_extensions.Final[typing.Optional[detectors.DetectedApplication]]' = location

        self.config.set('main', f'{self.app_name}_path', self.root or '')

    @property
    def executable(self) -> typing.Optional[str]:
        """Return executable command."""
        if self.__location is None:
            return None

        from anaconda_navigator.utils import launch  # pylint: disable=import-outside-toplevel
        return launch.safe_argument(self.__location.executable)

    @property
    def is_installation_enabled(self) -> bool:
        """Application can be installed."""
        return hasattr(self, 'install')

    @property
    def is_installed(self) -> bool:
        """Return whether the app is installed."""
        return bool(self.executable)

    @property
    def root(self) -> typing.Optional[str]:
        """Return application root directory."""
        if self.__location is None:
            return None
        return self.__location.root

    @property
    def version(self) -> typing.Optional[str]:
        """Version of the installed application."""
        if self.__location is None:
            return None
        return self.__location.version

    @property
    def versions(self) -> typing.Sequence[str]:
        """List of available application versions."""
        if self.__location is None:
            return []
        return [self.__location.version]

    @abc.abstractmethod
    def update_config(self, prefix: str) -> None:
        """Update user config to use selected Python prefix interpreter."""

    @abc.abstractmethod
    def install_extensions(self) -> 'process.ProcessWorker':
        """Install app extensions."""
