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

# -----------------------------------------------------------------------------
# Copyright (c) 2016-2017 Anaconda, Inc.
#
# May be copied and distributed freely only as part of an Anaconda or
# Miniconda installation.
# -----------------------------------------------------------------------------
"""Components for environment management."""

__all__ = ['ApplicationsComponent']

import os
import typing
import webbrowser
from qtpy import QtCore
from anaconda_navigator.api import external_apps
from anaconda_navigator import config as anaconda_config
from anaconda_navigator.utils import constants
from anaconda_navigator.utils import launch
from anaconda_navigator.widgets import dialogs
from anaconda_navigator.widgets.dialogs import environment as environment_dialogs
from . import common

if typing.TYPE_CHECKING:
    import typing_extensions
    from anaconda_navigator.widgets import main_window


class ApplicationsComponent(common.Component):
    """Component for launching third-party applications."""

    __alias__ = 'applications'

    def __init__(self, parent: 'main_window.MainWindow') -> None:
        """Initialize new :class:`~EnvironmentsComponent` instance."""
        super().__init__(parent=parent)

        self.__running_processes: 'typing_extensions.Final[typing.List[launch.RunningProcess]]' = []

        self.__listener_timer: 'typing_extensions.Final[QtCore.QTimer]' = QtCore.QTimer()
        self.__listener_timer.timeout.connect(self.update_running_processes)
        self.__listener_timer.setInterval(5000)
        self.__listener_timer.start()

        self.__feedback_timer: typing.Optional[QtCore.QTimer] = None

    @property
    def running_processes(self) -> typing.List[launch.RunningProcess]:
        """Collection of currently launched processes."""
        return self.__running_processes[:]

    def update_running_processes(self) -> None:
        """Update status of applications launched from Navigator."""
        index: int
        for index in reversed(range(len(self.__running_processes))):
            running_application: launch.RunningProcess = self.__running_processes[index]
            if running_application.return_code is None:
                continue

            if (running_application.return_code != 0) and (running_application.age.total_seconds() < 15.0):
                self.show_application_launch_errors(running_application)

            del self.__running_processes[index]
            running_application.cleanup()

    def launch_application(
            self,
            package_name: str,
            command: str,
            leave_path_alone: bool,
            prefix: str,
            sender: str,
            non_conda: bool,
            app_type: constants.AppType,
    ) -> None:
        """
        Launch application from home screen.

        :param package_name: Name of the conda package, or alias of the external application.
        :param command: Exact command to launch application with.
        :param prefix: Conda prefix, which should be active.
        :param app_type: Type of the application.
        """
        self.main_window.update_status(action=f'Launching <b>{package_name}</b>', value=0, max_value=0)

        def next_step(*args) -> None:
            self.__launch_application(
                package_name=package_name,
                command=command,
                leave_path_alone=leave_path_alone,
                prefix=prefix,
                non_conda=non_conda,
            )

        if app_type == constants.AppType.CONDA:
            next_step()

        elif app_type == constants.AppType.INSTALLABLE:
            app = external_apps.apps[package_name](
                config=self.main_window.api.config,
                process_api=self.main_window.api._process_api,
            )
            app.update_config(prefix=self.main_window.current_prefix)

            # Install extensions first!
            worker = app.install_extensions()
            worker.sig_finished.connect(next_step)
            worker.start()

        elif app_type == constants.AppType.WEB:
            webbrowser.open_new_tab(command)

    def __launch_application(
        self,
        package_name: str,
        command: str,
        leave_path_alone: bool,
        prefix: str,
        non_conda: bool,
    ) -> None:
        """Second phase of the :meth:`~ApplicationComponent.launch_application`."""
        environment: typing.Dict[str, str] = dict(os.environ)
        environment.pop('QT_API')

        if anaconda_config.MAC:
            # See https://github.com/ContinuumIO/anaconda-issues/issues/3287
            os.environ['LANG'] = os.environ.get('LANG') or os.environ.get('LC_ALL') or 'en_US.UTF-8'
            os.environ['LC_ALL'] = os.environ.get('LC_ALL') or os.environ['LANG']

            # See https://github.com/ContinuumIO/navigator/issues/1233
            environment['EVENT_NOKQUEUE'] = '1'

        running_process: typing.Optional[launch.RunningProcess] = launch.launch(
            root_prefix=self.main_window.api.ROOT_PREFIX,
            prefix=prefix,
            command=command,
            package_name=package_name,
            environment=environment,
            leave_path_alone=leave_path_alone,
            non_conda=non_conda,
        )
        if running_process is None:
            return

        self.__running_processes.append(running_process)

        # Set timer
        def timeout() -> None:
            # actual feedback is done in `update_running_applications`
            self.main_window.update_status()
            self.__feedback_timer = None

        if self.__feedback_timer is not None:
            self.__feedback_timer.stop()
            self.__feedback_timer.timeout.disconnect()

        self.__feedback_timer = QtCore.QTimer()
        self.__feedback_timer.setSingleShot(True)
        self.__feedback_timer.setInterval(5000)
        self.__feedback_timer.timeout.connect(timeout)
        self.__feedback_timer.start()

    def show_application_launch_errors(self, application: launch.RunningProcess) -> None:
        """Show a dialog with details on application launch error."""
        self.main_window.update_status()
        if not self.main_window.config.get('main', 'show_application_launch_errors'):
            return

        content: typing.Optional[str] = application.stderr
        if content:
            content = content.strip()
        else:
            content = f'Exit code: {application.return_code}'

        self.main_window.tracker.track_page('/home/errors', pagetitle='Launch errors dialog.')
        dialogs.MessageBoxError(
            text=f'Application <b>{application.package}</b> launch may have produced errors.',
            title='Application launch error',
            error=content,
            report=False,
            learn_more=None,
        ).exec_()
        self.main_window.tracker.track_page('/home')

    def check_dependencies_before_install(self, worker, output, error):
        """
        Check if the package to be installed changes navigator dependencies.

        This check is made for Orange3 which is not qt5 compatible.
        """
        if isinstance(output, dict):
            exception_type = str(output.get('exception_type', ''))
            actions = output.get('actions', {})
        else:
            exception_type = ''
            actions = {}

        conflicts = False
        nav_deps_conflict = self.main_window.api.check_navigator_dependencies(actions, self.main_window.current_prefix)
        conflict_message = ''

        # Try to install in a new environment
        if 'UnsatisfiableError' in exception_type or nav_deps_conflict:
            conflicts = True
            # Try to set the default python to None to avoid issues that
            # prevent a package to be installed in a new environment due to
            # python pinning, fusion for 2.7, rstudio on win for 2.7 etc.
            self.main_window.api.conda_config_set('default_python', None)

        if conflicts:
            self.main_window.tracker.track_page(
                '/environments/create/conflict', pagetitle='Create new environment due to conflict import'
            )
            dlg = environment_dialogs.ConflictDialog(
                parent=self.main_window,
                package=worker.pkgs[0],
                extra_message=conflict_message,
                current_prefix=self.main_window.current_prefix,
            )
            self.main_window._dialog_environment_action = dlg
            worker_info = self.main_window.api.conda_data(prefix=self.main_window.current_prefix)
            worker_info.sig_chain_finished.connect(dlg.setup)

            if dlg.exec_():
                env_prefix = dlg.prefix
                action_msg = ('Installing application <b>{0}</b> on new'
                              'environment <b>{1}</b>').format(worker.pkgs[0], env_prefix)

                if env_prefix not in dlg.environments:
                    new_worker = self.main_window.api.create_environment(
                        prefix=env_prefix,
                        packages=worker.pkgs,
                        no_default_python=True,
                    )
                    # Save the old prefix in case of errors
                    new_worker.old_prefix = worker.prefix

                    new_worker.action_msg = action_msg
                    new_worker.sig_finished.connect(self.main_window._conda_output_ready)
                    new_worker.sig_partial.connect(self.main_window._conda_partial_output_ready)
                else:
                    new_worker = self.main_window.api.install_packages(
                        prefix=env_prefix,
                        pkgs=worker.pkgs,
                        no_default_python=True,
                    )
                    # Save the old prefix in case of errors
                    new_worker.old_prefix = worker.prefix

                    new_worker.action = constants.ACTION_INSTALL
                    new_worker.action_msg = action_msg
                    new_worker.pkgs = worker.pkgs
                    new_worker.sig_finished.connect(self.main_window._conda_output_ready)
                    new_worker.sig_partial.connect(self.main_window._conda_partial_output_ready)
                self.main_window.update_status(action_msg, value=0, max_value=0)
            else:
                self.main_window.set_widgets_enabled(True)
                self.main_window.set_busy_status(conda=False)
                self.main_window.update_status()

            self.main_window._dialog_environment_action = None
            self.main_window.tracker.track_page('/environments')
        else:
            if worker.action == constants.APPLICATION_INSTALL:
                action_msg = ('Install application <b>{0}</b> on <b>{1}</b>').format(worker.pkgs[0], worker.prefix)
            elif worker.action == constants.APPLICATION_UPDATE:
                action_msg = ('Updating application <b>{0}</b> on <b>{1}</b>').format(worker.pkgs[0], worker.prefix)
            new_worker = self.main_window.api.install_packages(
                prefix=worker.prefix,
                pkgs=worker.pkgs,
            )
            new_worker.action_msg = action_msg
            new_worker.action = worker.action
            new_worker.sender = worker.sender
            new_worker.non_conda = worker.non_conda
            new_worker.pkgs = worker.pkgs
            new_worker.sig_finished.connect(self.main_window._conda_output_ready)
            new_worker.sig_partial.connect(self.main_window._conda_partial_output_ready)
            self.main_window.update_status(action_msg, value=0, max_value=0)

    def check_license_requirements(self, worker, output, error):
        """Check if package requires licensing and try to get a trial."""
        worker.output = output
        self.check_dependencies_before_install(worker, output, error)

    def conda_application_action(self, action, package_name, version, sender, non_conda, app_type):
        if app_type == constants.AppType.INSTALLABLE and action == constants.APPLICATION_INSTALL:
            self.install_external_app(package_name)
            return

        self.main_window.tab_home.set_widgets_enabled(False)
        if 'environments' in self.main_window.components:
            self.main_window.components.environments.tab.set_widgets_enabled(False)
        self.main_window.set_busy_status(conda=True)
        current_version = self.main_window.api.conda_package_version(pkg=package_name, prefix=self.main_window.current_prefix)

        if version:
            pkgs = ['{0}=={1}'.format(package_name, version)]
        else:
            pkgs = ['{0}'.format(package_name)]

        if action == constants.APPLICATION_INSTALL:
            worker = self.main_window.api.install_packages(prefix=self.main_window.current_prefix, pkgs=pkgs, dry_run=True)
            text_action = 'Installing'

            if current_version:
                try:
                    from distutils.version import LooseVersion
                    cur_ver = LooseVersion(current_version)
                    ver = LooseVersion(version)
                    if cur_ver > ver:
                        text_action = 'Downgrading'
                    elif cur_ver < ver:
                        text_action = 'Upgrading'
                except Exception:
                    pass

            action_msg = ('{0} application <b>{1}</b> on <b>{2}</b>').format(text_action, package_name, self.main_window.current_prefix)
            worker.prefix = self.main_window.current_prefix
            worker.action = action
            worker.action_msg = action_msg
            worker.sender = sender
            worker.pkgs = pkgs
            worker.non_conda = non_conda
            worker.sig_finished.connect(self.check_license_requirements)
            worker.sig_partial.connect(self.main_window._conda_partial_output_ready)
        elif action == constants.APPLICATION_UPDATE:
            worker = self.main_window.api.install_packages(prefix=self.main_window.current_prefix, pkgs=pkgs, dry_run=True)
            action_msg = ('Updating application <b>{0}</b> on ' '<b>{1}</b>').format(package_name, self.main_window.current_prefix)
            worker.prefix = self.main_window.current_prefix
            worker.action = action
            worker.action_msg = action_msg
            worker.sender = sender
            worker.pkgs = pkgs
            worker.non_conda = non_conda
            worker.sig_finished.connect(self.check_license_requirements)
            worker.sig_partial.connect(self.main_window._conda_partial_output_ready)
        elif action == constants.APPLICATION_REMOVE:
            worker = self.main_window.api.remove_packages(prefix=self.main_window.current_prefix, pkgs=pkgs)
            action_msg = ('Removing application <b>{0}</b> from <b>{1}</b>').format(package_name, self.main_window.current_prefix)
            worker.action = action
            worker.action_msg = action_msg
            worker.sender = sender
            worker.pkgs = pkgs
            worker.non_conda = non_conda
            worker.sig_finished.connect(self.main_window._conda_output_ready)
            worker.sig_partial.connect(self.main_window._conda_partial_output_ready)
        self.main_window.update_status(action_msg, value=0, max_value=0)

    def install_external_app(self, app_name):
        """Installing external app (VSCode, pycharm)."""
        external_apps.apps[app_name](
            config=self.main_window.config,
            process_api=self.main_window.api._process_api,
        ).install()

    def setup(self, worker: typing.Any, output: typing.Any, error: str, initial: bool) -> None:
        """Perform component configuration from `conda_data`."""
        prefix: str
        for prefix in output['processed_info']['__environments']:
            launch.remove_package_logs(root_prefix=self.main_window.api.ROOT_PREFIX, prefix=prefix)
