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

"""Anaconda Cloud login dialogs."""

from __future__ import annotations

__all__ = ['CloudOutcome', 'CloudPage', 'CloudLoginPage']

import contextlib
import enum
import typing

from qtpy import QtCore
from qtpy import QtGui
from qtpy import QtWidgets
import requests

from anaconda_navigator.api import cloud
from anaconda_navigator import config
from anaconda_navigator.config import preferences
from anaconda_navigator.static import images
from anaconda_navigator.utils import analytics
from anaconda_navigator.utils import attribution
from anaconda_navigator.utils import singletons
from anaconda_navigator.utils import workers
from anaconda_navigator.widgets import common
from anaconda_navigator.widgets.dialogs import bases
from . import utils


TRACKER: typing.Final[singletons.Singleton[analytics._GATracker]] = singletons.SingleInstanceOf(analytics.GATracker)


class CloudOutcome(enum.IntEnum):
    """Possible outcomes from Cloud dialogs related to account management."""

    SUCCESS = enum.auto()
    CANCEL = enum.auto()

    REQUESTED_LOGIN = enum.auto()
    REQUESTED_SIGNUP = enum.auto()
    REQUESTED_PASSWORD_RESTORE = enum.auto()


class CloudHeading(QtWidgets.QLabel):  # pylint: disable=too-few-public-methods
    """Primary header for Cloud dialogs."""


class CloudSubtitle(QtWidgets.QLabel):  # pylint: disable=too-few-public-methods
    """Subtitle for Cloud dialogs."""

    def __init__(self, *args: typing.Any) -> None:
        """Initialize new :class:`~CloudSubtitle` instance."""
        super().__init__(*args)
        self.setWordWrap(True)


class CloudErrorMessage(QtWidgets.QLabel):  # pylint: disable=too-few-public-methods
    """Container for error messages to use in Cloud dialogs."""

    def __init__(self, *args: typing.Any) -> None:
        """Initialize new :class:`~CloudErrorMessage` instance."""
        super().__init__(*args)
        self.setWordWrap(True)

        self.setVisible(bool(self.text()))

    def setText(self, value: str) -> None:  # pylint: disable=invalid-name
        """Set displayed error message."""
        super().setText(value)
        self.setVisible(bool(value))


class CloudLabel(QtWidgets.QLabel):  # pylint: disable=too-few-public-methods
    """Common style for labels in Cloud dialogs."""


class CloudInputLabel(QtWidgets.QLabel):  # pylint: disable=too-few-public-methods
    """Label to use in input group."""


class CloudInput(common.LineEdit):  # pylint: disable=too-few-public-methods
    """Text input to use in input group."""


class CloudInputError(QtWidgets.QLabel):  # pylint: disable=too-few-public-methods
    """Line with error message to use in input group."""


class CloudCheckBox(QtWidgets.QCheckBox):  # pylint: disable=too-few-public-methods
    """Customized checkbox for general purpose."""


class CloudInputGroup(QtWidgets.QVBoxLayout):
    """Text input control with additional elements around it."""

    NORMAL: typing.Final[str] = 'normal'  # pylint: disable=invalid-name
    ERROR: typing.Final[str] = 'error'  # pylint: disable=invalid-name

    def __init__(self, title: str, *, show_toggle: bool = False) -> None:
        """Initialize new :class:`~CloudInputGroup` instance."""
        super().__init__()

        self._title: typing.Final[CloudInputLabel] = CloudInputLabel(title)

        self.input: typing.Final[CloudInput] = CloudInput(show_toggle=show_toggle)
        self.input.setProperty('status', self.NORMAL)

        self._error: typing.Final[CloudInputError] = CloudInputError()
        self._error.setWordWrap(True)

        self.setContentsMargins(0, 0, 0, 0)
        self.setSpacing(0)
        self.addWidget(self._title)
        self.addSpacing(4)
        self.addWidget(self.input)
        self.addSpacing(4)
        self.addWidget(self._error)

    @property
    def error(self) -> str:  # noqa: D401
        """Displayed error message."""
        return self._error.text()

    @error.setter
    def error(self, value: str) -> None:
        """Update :attr:`~CloudInputGroup.error` value."""
        self._error.setText(value)

        if value:
            self.status = self.ERROR
        elif self.status == self.ERROR:
            self.status = self.NORMAL

    @property
    def status(self) -> str:  # noqa: D401
        """Current status of the input."""
        return self.input.property('status')

    @status.setter
    def status(self, value: str) -> None:
        """Update :attr:`~CloudInputGroup.status` value."""
        if self.status == value:
            return
        self.input.setProperty('status', value)
        self.input.style().unpolish(self.input)
        self.input.style().polish(self.input)

    @property
    def title(self) -> str:  # noqa: D401
        """Displayed title for the input."""
        return self._title.text()

    @title.setter
    def title(self, value: str) -> None:
        """Update :attr:`~CloudInputGroup.title` value."""
        self._title.setText(value)

    @property
    def value(self) -> str:  # noqa: D401
        """Text entered in the text input."""
        return self.input.text()

    @value.setter
    def value(self, value: str) -> None:
        """Update :attr:`~CloudInputGroup.value` value."""
        self.input.setText(value)


class CloudButton(QtWidgets.QPushButton):  # pylint: disable=too-few-public-methods
    """Generic button to use in Cloud dialogs."""

    NORMAL: typing.Final[str] = 'normal'  # pylint: disable=invalid-name
    BUSY: typing.Final[str] = 'busy'  # pylint: disable=invalid-name

    def __init__(self, *args: typing.Any) -> None:
        """Initialize new :class:`~CloudButton` instance."""
        super().__init__(*args)
        self.setProperty('status', self.NORMAL)

    @property
    def status(self) -> str:  # noqa: D401
        """Current status of the input."""
        return self.property('status')

    @status.setter
    def status(self, value: str) -> None:
        """Update :attr:`~CloudButton.status` value."""
        if self.status == value:
            return
        self.setProperty('status', value)
        self.style().unpolish(self)
        self.style().polish(self)


class CloudPage(bases.CloudDialogBase):
    """Common base for Cloud account dialogs."""

    def __init__(self, parent: typing.Optional[QtCore.QObject] = None) -> None:
        """Initialize new :class:`~CloudPage` instance."""
        super().__init__(parent)

        self._api: typing.Final[cloud._CloudAPI] = cloud.CloudAPI()

        self.__outcome: CloudOutcome = CloudOutcome.CANCEL

        self.setMaximumWidth(660)
        self.setMinimumWidth(660)
        self.setMinimumHeight(610)

    @property
    def outcome(self) -> CloudOutcome:  # noqa: D401
        """Result of the dialog execution."""
        return self.__outcome

    def accept(self, outcome: CloudOutcome = CloudOutcome.SUCCESS) -> None:
        """Hide the dialog and set result code to `Accepted`."""
        self.__outcome = outcome
        super().accept()

    def reject(self, outcome: CloudOutcome = CloudOutcome.CANCEL) -> None:
        """Hide the dialog and set result code to `Rejected`."""
        self.__outcome = outcome
        super().reject()


class CloudLoginPage(CloudPage):  # pylint: disable=too-many-instance-attributes
    """Sign-in page for Cloud accounts."""

    def __init__(  # pylint: disable=too-many-statements
            self,
            parent: typing.Optional[QtCore.QObject] = None,
            welcome_mode: bool = False,
    ) -> None:
        """Initialize new :class:`~CloudLoginPage` instance."""
        super().__init__(parent)

        self._heading: typing.Final[CloudHeading] = CloudHeading('Log In')

        self._subtitle: typing.Final[CloudSubtitle] = CloudSubtitle(
            'Get the most out of Navigator by signing into your Anaconda Cloud account.'
        )

        self._error_message: typing.Final[CloudErrorMessage] = CloudErrorMessage()

        self._username: typing.Final[CloudInputGroup] = CloudInputGroup('Email Address')
        self._username.input.setValidator(utils.EMAIL_RE_VALIDATOR)
        self._username.input.textEdited.connect(self._validate_input)

        self._password: typing.Final[CloudInputGroup] = CloudInputGroup('Password', show_toggle=True)
        self._password.input.textEdited.connect(self._validate_input)

        self._accept_button: typing.Final[CloudButton] = CloudButton()
        self._accept_button.setIconSize(QtCore.QSize(24, 24))
        self._accept_button.setLayoutDirection(QtCore.Qt.RightToLeft)
        self._accept_button.setText('Sign In')
        self._accept_button.clicked.connect(self._login)

        self._accept_button_movie: typing.Final[QtGui.QMovie] = QtGui.QMovie(images.BUSY)
        self._accept_button_movie.frameChanged.connect(self._update_accept_button_icon)

        self._buttons_layout: typing.Final[QtWidgets.QHBoxLayout] = QtWidgets.QHBoxLayout()
        self._buttons_layout.setAlignment(QtCore.Qt.AlignLeft)
        self._buttons_layout.setContentsMargins(0, 0, 0, 0)
        self._buttons_layout.setSpacing(0)
        self._buttons_layout.addWidget(self._accept_button)

        self._disable_checkbox: typing.Final[CloudCheckBox] = CloudCheckBox()
        self._disable_checkbox.setText('Do not show again')

        self._restore_password_label: typing.Final[CloudLabel] = CloudLabel(
            'Forgot your <a href="#" style="color:#43b049; text-decoration:underline">password</a>?',
        )
        self._restore_password_label.linkActivated.connect(self._restore_password)

        self._create_account_label: typing.Final[CloudLabel] = CloudLabel(
            'Create new <a href="#" style="color:#43b049; text-decoration:underline">account</a>',
        )
        self._create_account_label.linkActivated.connect(self._create_account)

        self._links_layout: typing.Final[QtWidgets.QHBoxLayout] = QtWidgets.QHBoxLayout()
        self._links_layout.setAlignment(QtCore.Qt.AlignLeft)
        self._links_layout.setContentsMargins(0, 0, 0, 0)
        self._links_layout.setSpacing(0)
        self._links_layout.addWidget(self._restore_password_label)
        self._links_layout.addStretch(1)
        self._links_layout.addWidget(self._create_account_label)

        self._content_layout: typing.Final[QtWidgets.QVBoxLayout] = QtWidgets.QVBoxLayout()
        self._content_layout.setContentsMargins(90, 8, 90, 50)
        self._content_layout.setSpacing(0)
        self._content_layout.setAlignment(QtCore.Qt.AlignTop)
        self._content_layout.addWidget(self._heading)
        self._content_layout.addSpacing(12)
        self._content_layout.addWidget(self._subtitle)
        self._content_layout.addSpacing(16)
        self._content_layout.addWidget(self._error_message)
        self._content_layout.addSpacing(16)
        self._content_layout.addLayout(self._username)
        self._content_layout.addSpacing(6)
        self._content_layout.addLayout(self._password)
        self._content_layout.addSpacing(24)
        self._content_layout.addLayout(self._buttons_layout)
        if welcome_mode:
            self._content_layout.addSpacing(24)
            self._content_layout.addWidget(self._disable_checkbox)
        self._content_layout.addSpacing(24)
        self._content_layout.addLayout(self._links_layout)

        self.setLayout(self._content_layout)

        self._validate_input()
        self.finished.connect(self._finished)

    def keyPressEvent(self, event: QtGui.QKeyEvent) -> None:  # pylint: disable=invalid-name
        """Process press on a keyboard key."""
        if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
            self._login()
            return

        if event.key() == QtCore.Qt.Key_Escape:
            self.reject(outcome=CloudOutcome.CANCEL)
            return

        super().keyPressEvent(event)

    def _create_account(self) -> None:
        """React to selecting "create account" option."""
        # self.accept(outcome=CloudOutcome.REQUESTED_SIGNUP)
        url = attribution.POOL.settings.inject_url_parameters(
            'https://anaconda.cloud/sign-up',
            utm_medium='connect-cloud',
            utm_content='signup',
        )
        QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))

    def _finished(self) -> None:
        """Perform actions when dialog is closed."""
        QtWidgets.QApplication.restoreOverrideCursor()

        if self._disable_checkbox.isChecked():
            config.CONF.set('internal', 'welcome_state', preferences.WELCOME_DELAYS.first - 1)

    def _login(self) -> None:
        """React to selecting "login" option."""
        if not self._validate_input():
            return

        self._error_message.setText('')
        self._username.error = ''
        self._password.error = ''

        self._username.input.setReadOnly(True)
        self._password.input.setReadOnly(True)
        self._accept_button.status = CloudButton.BUSY
        self._accept_button.setDisabled(True)
        self._accept_button_movie.start()
        QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)

        self.adjustSize()

        worker: 'workers.TaskWorker' = cloud.CloudAPI().login.worker(  # pylint: disable=no-member
            username=self._username.value.lower(),
            password=self._password.value,
        )
        self.finished.connect(worker.cancel)
        worker.signals.sig_done.connect(lambda: self.finished.disconnect(worker.cancel))
        worker.signals.sig_done.connect(self._login_done)
        worker.start()

    def _login_done(self, result: workers.TaskResult) -> None:
        """Process Cloud API response to login request."""
        if not self.isVisible():
            return

        QtWidgets.QApplication.restoreOverrideCursor()
        self._accept_button_movie.stop()
        self._update_accept_button_icon(-1)
        self._accept_button.setEnabled(True)
        self._accept_button.status = CloudButton.NORMAL
        self._username.input.setReadOnly(False)
        self._password.input.setReadOnly(False)

        if result.status == workers.TaskStatus.SUCCEEDED:
            self.accept(outcome=CloudOutcome.SUCCESS)
            return

        error: typing.Optional[BaseException] = result.exception
        error_message: str = 'Error performing request'
        if isinstance(error, requests.RequestException) and (error.response is not None):
            if error.response.status_code in (401, 403):
                error_message = 'Invalid credentials'
            else:
                with contextlib.suppress(LookupError, TypeError, ValueError):
                    error_message = error.response.json()['message']
        self._error_message.setText(error_message)

        self.adjustSize()

        self._password.input.setFocus()
        self._password.input.selectAll()

    def _restore_password(self) -> None:
        """React to selecting "restore password" option."""
        # self.accept(outcome=CloudOutcome.REQUESTED_PASSWORD_RESTORE)
        url = attribution.POOL.settings.inject_url_parameters(
            'https://anaconda.cloud/forgot-password',
            utm_medium='connect-cloud',
            utm_content='password-reset',
        )
        QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))

    def _update_accept_button_icon(self, frame: int) -> None:
        """Update icon displayed on accept button."""
        icon: QtGui.QIcon
        if frame < 0:
            icon = QtGui.QIcon()
        else:
            icon = QtGui.QIcon(self._accept_button_movie.currentPixmap())
        self._accept_button.setIcon(icon)

    def _validate_input(self) -> bool:
        """Check if values in form are valid to proceed with login."""
        result: bool = bool(self._password.input.text()) and self._username.input.hasAcceptableInput()
        self._accept_button.setEnabled(result)
        return result
