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

# -----------------------------------------------------------------------------
# Copyright 2017 Anaconda, Inc.
#
# May be copied and distributed freely only as part of an Anaconda or
# Miniconda installation.
# -----------------------------------------------------------------------------

"""Anaconda Navigator Updater main dialog."""

from __future__ import annotations

import os
import threading
import typing

from qtpy.QtCore import QSize, Qt, QTimer, Signal
from qtpy.QtSvg import QSvgWidget
from qtpy.QtWidgets import QHBoxLayout, QVBoxLayout

from navigator_updater.api.conda_api import CondaAPI
from navigator_updater.config import MAC, WIN, NAVIGATOR_LOCKFILE
from navigator_updater.external import filelock
from navigator_updater.static import images
from navigator_updater.utils import launch as launch_utils
from navigator_updater.utils import version_utils
from navigator_updater.utils.conda import types as conda_types
from navigator_updater.utils.misc import set_windows_appusermodelid
from navigator_updater.utils.styles import load_style_sheet
from navigator_updater.widgets import ButtonNormal, ButtonPrimary, LabelBase, SpacerHorizontal, SpacerVertical
from navigator_updater.widgets.dialogs import DialogBase

CondaSearchResult = typing.MutableMapping[str, typing.List[conda_types.CondaSearchPackageInfo]]


def split_canonical_name(cname: str) -> typing.Tuple[str, ...]:
    """Split a canonical package name into name, version, build."""
    return tuple(cname.rsplit('-', 2))


class MainDialog(DialogBase):  # pylint: disable=too-many-instance-attributes
    """Main dialog for the anaconda navgator updater."""

    # Signals
    sig_application_updated = Signal()
    sig_ready = Signal()

    # Class variables
    PACKAGE = 'anaconda-navigator'  # pylint: disable=invalid-name
    WIDTH = 450  # pylint: disable=invalid-name
    HEIGHT = 200  # pylint: disable=invalid-name

    def __init__(self):  # pylint: disable=too-many-statements
        """Main dialog for the anaconda navgator updater."""
        super().__init__()

        # Variables
        self.api = CondaAPI()

        self.current_version = version_utils.Version(None)
        self.latest_version = version_utils.Version(None)
        self.busy = False

        self.style_sheet = load_style_sheet()
        self.lookup_timer = QTimer()
        self.refresh_timer = QTimer()

        # Widgets
        self.label_icon = QSvgWidget()
        self.label_message = LabelBase('We strongly recommend you to keep Anaconda Navigator up to date.')

        self.label_status = LabelBase('')
        self.button_cancel = ButtonNormal('Dismiss')
        self.button_update = ButtonPrimary('Update now')
        self.button_launch = ButtonPrimary('Launch Navigator')

        # Widgets setup

        self._windows_appusermodelid = set_windows_appusermodelid() if WIN else None

        self.label_message.setAlignment(Qt.AlignLeft | Qt.AlignTop)
        self.label_message.setWordWrap(True)
        self.label_status.setWordWrap(True)

        self.button_update.setAutoDefault(True)
        self.button_update.setFocusPolicy(Qt.NoFocus)
        self.button_update.setDisabled(True)
        self.button_launch.setAutoDefault(True)
        self.button_launch.setFocusPolicy(Qt.NoFocus)
        self.button_cancel.setFocusPolicy(Qt.NoFocus)

        self.refresh_timer.setInterval(3000)

        self.label_icon.load(images.ANACONDA_LOGO)
        self.label_icon.setMaximumSize(QSize(64, 64))
        self.label_icon.setMinimumSize(QSize(64, 64))

        self.setWindowTitle('Anaconda Navigator Updater')
        self.setMinimumSize(self.WIDTH, self.HEIGHT)
        self.setMinimumWidth(self.WIDTH)
        self.setMaximumWidth(self.WIDTH)
        self.setMinimumHeight(self.HEIGHT)

        # Layouts
        layout_status = QHBoxLayout()
        layout_status.addWidget(self.label_status)
        layout_status.addWidget(SpacerHorizontal())

        layout_text = QVBoxLayout()
        layout_text.addWidget(self.label_message)
        layout_text.addStretch()
        layout_text.addWidget(SpacerVertical())
        layout_text.addLayout(layout_status)

        layout_icon = QVBoxLayout()
        layout_icon.addWidget(self.label_icon)
        layout_icon.addStretch()

        layout_top = QHBoxLayout()
        layout_top.addLayout(layout_icon)
        layout_top.addWidget(SpacerHorizontal())
        layout_top.addLayout(layout_text)

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_cancel)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_update)
        layout_buttons.addWidget(self.button_launch)

        layout = QVBoxLayout()
        layout.addLayout(layout_top)
        layout.addWidget(SpacerVertical())
        layout.addWidget(SpacerVertical())
        layout.addStretch()
        layout.addLayout(layout_buttons)

        self.navigator_lookup()
        self.setLayout(layout)

        # Signals
        self.button_update.clicked.connect(self.install_update)
        self.button_cancel.clicked.connect(self.reject)
        self.button_launch.clicked.connect(self.launch)

        self.refresh_timer.timeout.connect(self.refresh)
        self.refresh_timer.start()

    @property
    def up_to_date(self) -> bool:
        """Flag latest Anaconda Navigator is installed."""
        return version_utils.compare(self.current_version, self.latest_version) >= 0

    def __set_messages(self, label_message: str, status_message: str | None = None) -> None:
        self.label_message.setText(label_message)
        if status_message is not None:
            self.label_status.setText(status_message)

    def refresh(self):  # pylint: disable=too-many-branches,too-many-statements
        """Refresh enabled/disabled status of widgets."""
        if self.up_to_date:
            self.lookup_timer.stop()

        current_version = str(self.current_version)
        latest_version = str(self.latest_version) or '-'

        self.button_update.setDisabled(True)
        if self.current_version.version is None:
            current_version = 'Not installed'
            self.button_launch.setDisabled(True)

        message = None
        main_message = (
            f'Current version:    &nbsp;&nbsp;&nbsp;&nbsp;<i>{current_version}</i><br>'
            f'Available version: &nbsp;&nbsp;<b>{latest_version}</b><br>'
        )

        if self.check_running():
            if self.up_to_date:
                message = 'Anaconda Navigator is up to date and running.'
            else:
                message = 'Please close Anaconda Navigator before updating.'

            self.button_launch.setDisabled(True)
            self.__set_messages(main_message, message)
            return

        if self.up_to_date:
            message = 'Anaconda Navigator is up to date.'
            self.busy = False
            self.button_launch.setDisabled(False)
            self.__set_messages(main_message, message)
            return

        if not self.api.root_writable and WIN:
            message = 'Need to run with elevated privileges'
            self.__set_messages(main_message, message)
            return

        self.button_update.setDisabled(self.busy)
        self.__set_messages(main_message, message)

    def navigator_lookup(self) -> None:
        """Check installed packages in case conda was used."""
        package = next((package for package in self.api.linked() if self.PACKAGE in package), None)
        self.current_version = version_utils.Version(split_canonical_name(package)[1] if package else None)

        if self.latest_version.version is not None:
            self.refresh()
            return

        search_result = self.api.search(self.PACKAGE)

        packages: typing.List[conda_types.CondaSearchPackageInfo] = []
        if search_result:
            packages = search_result.get(self.PACKAGE, [])

        if packages:
            self.latest_version = version_utils.as_version(version_utils.sort(
                (package.get('version', '') for package in packages), unique=True, reverse=True)[0])

        self.refresh()

    def install_update(self):
        """Start Anaconda Navigator update thread."""
        self.lookup_timer.setInterval(6000)
        self.lookup_timer.timeout.connect(self.navigator_lookup)
        self.lookup_timer.start()

        name = self.api.conda_info.get('active_prefix_name', os.path.basename(self.api.active_prefix))

        if self.latest_version.version is not None:
            msg = f'Installing package on <b>{name}</b>...'
        else:
            msg = f'Updating package on <b>{name}</b>...'

        threading.Thread(target=self.api.install, args=(self.PACKAGE,)).start()
        self.busy = True

        self.label_status.setText(msg)
        self.button_update.setDisabled(True)
        self.button_launch.setDisabled(True)

    def launch(self):
        """Launch Anaconda Navigator."""
        command = 'anaconda-navigator'

        # Use the app bundle on OSX
        if MAC:
            command = f'open \'{os.path.join(self.prefix, "Anaconda-Navigator.app")}\''

        launch_utils.launch(
            root_prefix=self.api.root_prefix,
            prefix=self.api.conda_prefix,
            command=command,
            package_name='anaconda-navigator-app'
        )
        self.close()

    @staticmethod
    def check_running() -> bool:
        """Check if Anaconda Navigator is running."""
        # Create file lock
        lock = filelock.FileLock(NAVIGATOR_LOCKFILE)  # type: ignore
        running = False
        try:
            with lock.acquire(timeout=0.01):
                pass
        except filelock.Timeout:  # type: ignore
            running = True
        return running
