# -*- 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 user accounts management."""

__all__ = ['AccountsComponent']

import typing
from urllib import parse
from qtpy import QtCore
from qtpy import QtWidgets
from conda_token import repo_config
from anaconda_navigator.api import nucleus
from anaconda_navigator import config
from anaconda_navigator import utils
from anaconda_navigator import widgets
from anaconda_navigator.widgets.dialogs import login as login_dialogs
from . import common

if typing.TYPE_CHECKING:
    import typing_extensions
    from anaconda_navigator.widgets import main_window
    from anaconda_navigator.widgets.dialogs.login import account_dialogs


class ButtonLabelLogin(QtWidgets.QLabel):
    """Button used in CSS styling."""


class ButtonLogin(widgets.ButtonPrimary):
    """Button used in CSS styling."""


class AccountsComponent(common.Component):
    """Component for account management."""

    __alias__ = 'accounts'

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

        self.__authenticated: bool = False
        self.__brand: typing.Optional[str] = config.DEFAULT_BRAND
        self.__token: typing.Optional[str] = self.main_window.api._client_api.load_token()
        self.__url: typing.Optional[str] = None
        self.__username: str = ''

        self.__timer: 'typing_extensions.Final[QtCore.QTimer]' = QtCore.QTimer()
        self.__timer.setInterval(5000)
        self.__timer.timeout.connect(self.__check_for_new_login)

        self.__account_label: 'typing_extensions.Final[QtWidgets.QLabel]' = ButtonLabelLogin()
        self.__account_label.setFocusPolicy(QtCore.Qt.NoFocus)
        self.__account_label.setText('')
        self.__account_label.linkActivated.connect(lambda activated_url: self.main_window.open_url(activated_url))

        self.__login_button: 'typing_extensions.Final[QtWidgets.QPushButton]' = ButtonLogin()
        self.__login_button.setLayoutDirection(QtCore.Qt.RightToLeft)
        self.__login_button.setDefault(True)
        self.__login_button.setText('Connect')
        self.__login_button.clicked.connect(self.__show_accounts)

    @property
    def account_label(self) -> QtWidgets.QLabel:
        """Label with details of account login."""
        return self.__account_label

    @property
    def login_button(self) -> QtWidgets.QPushButton:
        """Button to trigger login action."""
        return self.__login_button

    def update_login_status(self, user_data=None):
        """Update login button and information."""
        if self.main_window.config.get('main', 'logged_api_url') or user_data:
            self.__username = user_data.get('login', '') if user_data else self.__username
            self.__authenticated = True

        self.__update_account_label_text()

        # See: https://github.com/ContinuumIO/navigator/issues/1325
        self.main_window.api.client_reload()

        def apply_api_urls(worker, output, error):
            if output:
                self.__url = output.get('main_url', config.DEFAULT_ANACONDA_MAIN_URL)
                self.__brand = output.get('brand', config.DEFAULT_BRAND)
            else:
                self.__url = config.DEFAULT_ANACONDA_MAIN_URL
                self.__brand = config.DEFAULT_BRAND

            try:
                self.login_button.setEnabled(True)
            except RuntimeError:
                pass  # On CI: wrapped C/C++ object of type ButtonLinkLogin has been deleted

        self.login_button.setEnabled(False)
        worker = self.main_window.api.api_urls()
        worker.username = self.__username
        worker.sig_chain_finished.connect(apply_api_urls)
        QtWidgets.QApplication.restoreOverrideCursor()

    def __update_account_label_text(self) -> None:
        """Update login button and information."""
        result: typing.List[str] = []

        nucleus_account: typing.Optional[str] = nucleus.NucleusAPI().token.username
        if nucleus_account:
            result.append('Nucleus')

        if self.__authenticated:
            brand: str
            brand, _ = self.get_logged_api_label_url()
            result.append(brand)

        content: str = ''
        if result:
            content = 'Connected to ' + ', '.join(
                f'<a href="" style="color:#43B049;text-decoration:none">{item}</a>' for item in result
            )

        self.account_label.setText(content)
        self.account_label.setVisible(bool(content))

    def __show_accounts(self):
        """Open up login dialog or log out depending on logged status."""
        states: 'account_dialogs.AccountStateMapping' = {}

        nucleus_username: typing.Optional[str] = nucleus.NucleusAPI().token.username
        if nucleus_username:
            states['nucleus'] = login_dialogs.AccountState(
                status=login_dialogs.AccountStatus.ACTIVE,
                username=nucleus_username,
            )
        else:
            states['nucleus'] = login_dialogs.AccountState(status=login_dialogs.AccountStatus.AVAILABLE)

        if self.__authenticated:
            brand, _ = self.get_logged_api_label_url()
            if brand == config.ANACONDA_ORG_BRAND:
                states['individual'] = login_dialogs.AccountState(
                    status=login_dialogs.AccountStatus.ACTIVE,
                    username=self.__username,
                )
            elif brand == config.COMMERCIAL_EDITION_BRAND:
                states['commercial'] = login_dialogs.AccountState(
                    status=login_dialogs.AccountStatus.ACTIVE,
                    username=self.__username,
                )
            elif brand == config.TEAM_EDITION_BRAND:
                states['team'] = login_dialogs.AccountState(
                    status=login_dialogs.AccountStatus.ACTIVE,
                    username=self.__username,
                )
            elif brand == config.ENTERPRISE_EDITION_BRAND:
                states['enterprise'] = login_dialogs.AccountState(
                    status=login_dialogs.AccountStatus.ACTIVE,
                    username=self.__username,
                )
        else:
            states['individual'] = login_dialogs.AccountState(status=login_dialogs.AccountStatus.AVAILABLE)
            states['commercial'] = login_dialogs.AccountState(status=login_dialogs.AccountStatus.AVAILABLE)
            states['team'] = login_dialogs.AccountState(status=login_dialogs.AccountStatus.AVAILABLE)
            states['enterprise'] = login_dialogs.AccountState(status=login_dialogs.AccountStatus.AVAILABLE)

        selector = login_dialogs.AccountsDialog(
            parent=self.main_window,
            anchor=self.login_button,
            states=states,
        )
        selector.sig_accepted.connect(self.__process_accounts)
        selector.show()

    def __process_accounts(self, outcome: login_dialogs.AccountOutcome, value: login_dialogs.AccountValue) -> None:
        """"""
        if outcome == login_dialogs.AccountOutcome.REJECT:
            return

        if outcome == login_dialogs.AccountOutcome.LOGIN_REQUEST:
            {
                login_dialogs.AccountValue.NUCLEUS: self.log_into_nucleus,
                login_dialogs.AccountValue.INDIVIDUAL_EDITION: self.log_into_individual_edition,
                login_dialogs.AccountValue.COMMERCIAL_EDITION: self.log_into_commercial_edition,
                login_dialogs.AccountValue.TEAM_EDITION: self.log_into_team_edition,
                login_dialogs.AccountValue.ENTERPRISE_EDITION: self.log_into_enterprise_edition,
            }[value]()
            return

        if outcome == login_dialogs.AccountOutcome.LOGOUT_REQUEST:
            {
                login_dialogs.AccountValue.NUCLEUS: self.log_out_from_nucleus,
                login_dialogs.AccountValue.INDIVIDUAL_EDITION: self.log_out_from_repository,
                login_dialogs.AccountValue.COMMERCIAL_EDITION: self.log_out_from_repository,
                login_dialogs.AccountValue.TEAM_EDITION: self.log_out_from_repository,
                login_dialogs.AccountValue.ENTERPRISE_EDITION: self.log_out_from_repository,
            }[value]()
            return

        raise ValueError('Unexpected login outcome')

    def log_into_nucleus(self) -> None:
        """Open dialogs to log into Anaconda Nucleus."""
        if login_dialogs.NucleusLoginPage(parent=self.main_window).exec_():
            self.__update_account_label_text()

    def log_into_individual_edition(self) -> None:
        """Open dialogs to log into Anaconda Individual Edition."""
        credentials_dialog: 'typing_extensions.Final[QtWidgets.QDialog]' = login_dialogs.AnacondaLoginPage(
            parent=self.main_window,
        )
        credentials_dialog.exec_()
        self.__postprocess_dialog(dialog=credentials_dialog)

    def log_into_commercial_edition(self) -> None:
        """Open dialogs to log into Anaconda Commercial Edition."""
        credentials_dialog: 'typing_extensions.Final[QtWidgets.QDialog]' = login_dialogs.CommercialEditionLoginPage(
            parent=self.main_window,
        )
        credentials_dialog.exec_()
        self.__postprocess_dialog(dialog=credentials_dialog)

    def log_into_team_edition(self) -> None:
        """Open dialogs to log into Anaconda Team Edition."""
        if not self.main_window.config.get('main', 'team_edition_api_url'):
            domain_dialog: 'typing_extensions.Final[QtWidgets.QDialog]' = login_dialogs.TeamEditionSetDomainPage(
                parent=self.main_window,
            )
            if not domain_dialog.exec_():
                return

        credentials_dialog: 'typing_extensions.Final[QtWidgets.QDialog]' = login_dialogs.TeamEditionLoginPage(
            parent=self.main_window,
        )
        if not credentials_dialog.exec_():
            return

        login_dialogs.TeamEditionAddChannelsPage(parent=self.main_window).exec_()
        self.__postprocess_dialog(credentials_dialog)

    def log_into_enterprise_edition(self) -> None:
        """Open dialogs to log into Anaconda Enterprise Edition."""
        if not self.main_window.config.get('main', 'enterprise_4_repo_api_url'):
            domain_dialog: 'typing_extensions.Final[QtWidgets.QDialog]' = login_dialogs.EnterpriseRepoSetDomainPage(
                parent=self.main_window,
            )
            if not domain_dialog.exec_():
                return

        credentials_dialog: 'typing_extensions.Final[QtWidgets.QDialog]' = login_dialogs.EnterpriseRepoLoginPage(
            parent=self.main_window,
        )
        if not credentials_dialog.exec_():
            return

        login_dialogs.NoticePage(parent=self.main_window).exec_()
        self.__postprocess_dialog(credentials_dialog)

    def log_out_from_nucleus(self) -> None:
        """Log out from Anaconda Nucleus."""
        nucleus.NucleusAPI().logout()
        self.__update_account_label_text()

    def log_out_from_repository(self) -> None:
        """Log out from all repositories (Individual, Commercial, Team and Enterprise editions)."""
        QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
        repo_config.token_remove()
        self.main_window.api.remove_login_data()
        self.main_window.api.logout()

        self.__authenticated = False
        self.__token = None
        self.__username = ''

        self.main_window.sig_logged_out.emit()
        self.main_window.tracker.track_event('authenticate', 'logout', label=self.__username)
        self.update_login_status()

    def __postprocess_dialog(self, dialog: QtWidgets.QDialog) -> None:
        """Apply changes from the login dialog."""
        if dialog.result():
            self.__authenticated = True
            self.__username = dialog.username
            self.main_window.api.detect_commercial_edition_login()
            self.main_window.sig_logged_in.emit()
            if self.main_window.tracker:
                self.main_window.tracker.track_event('authenticate', 'login', label=self.__username)
        self.main_window._track_tab()
        if dialog.result():
            self.update_login_status()

    def __check_for_new_login(self) -> None:
        """
        Check for new login status periodically on the system.

        Also checks for internet connectivity and updates.
        """
        new_token: typing.Optional[str] = self.main_window.api._client_api.load_token()
        if new_token != self.__token:
            self.__token = new_token
            if new_token is None:
                self.log_out_from_repository()
            else:
                pass  # TODO: How to relogin if logged from command line??

    def get_logged_api_label_url(self) -> typing.Tuple[str, str]:
        logged_api_url, anaconda_api_url, enterprise_4_repo_api_url, team_edition_api_url, commercial_edition_url = (
            parse.urlsplit(self.main_window.config.get('main', config_option) or '') for config_option in (
                'logged_api_url', 'anaconda_api_url', 'enterprise_4_repo_api_url', 'team_edition_api_url',
                'commercial_edition_url'
            )
        )
        brand: str = self.main_window.config.get('main', 'logged_brand') or config.ANACONDA_ORG_BRAND
        domain: str = utils.get_domain_from_api_url(anaconda_api_url)
        if logged_api_url.netloc:
            if logged_api_url == enterprise_4_repo_api_url:
                domain = utils.get_domain_from_api_url(enterprise_4_repo_api_url)

            elif logged_api_url == team_edition_api_url:
                domain = parse.urljoin(team_edition_api_url.geturl(), '/channels/')

            elif logged_api_url == commercial_edition_url:
                domain = commercial_edition_url.geturl()

        return brand, domain

    def start_timers(self) -> None:
        """Start component timers."""
        self.__timer.start()

    def stop_timers(self) -> None:
        """Stop component timers."""
        self.__timer.stop()
