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

# -----------------------------------------------------------------------------
# Copyright (c) 2016-2017 Anaconda, Inc.
#
# May be copied and distributed freely only as part of an Anaconda or
# Miniconda installation.
# -----------------------------------------------------------------------------
"""Base classes of login dialogs."""

__all__ = ['BasePage', 'BaseLoginPage', 'BaseSettingPage']

import ast
import typing
from contextlib import suppress

from qtpy.QtCore import Qt, QUrl
from qtpy.QtGui import QDesktopServices, QRegExpValidator, QPixmap
from qtpy.QtWidgets import QApplication, QHBoxLayout, QLabel, QLineEdit, QVBoxLayout, QWidget

from anaconda_navigator.config import CONF
from anaconda_navigator.utils.analytics import GATracker
from anaconda_navigator.utils.styles import load_style_sheet
from anaconda_navigator.widgets import ButtonPrimary
from anaconda_navigator.widgets.dialogs import StaticDialogBase, MessageBoxInformation
from . import styling
from .utils import USER_RE, TextContainer


class BasePage(StaticDialogBase):

    @property
    def username(self) -> str:
        """Return the logged username."""
        with suppress(Exception):
            return self.text_login.text().lower()

    def setup(self) -> None:
        """Setup signals and call all common init helpers"""

    def _get_forgot_links_widget(self, text_container: TextContainer) -> typing.Optional[QWidget]:
        if not text_container.form_forgot_links_msg:
            return None

        self.forgot_login_url = None
        self.forgot_password_url = None
        self.forgot_links = QLabel(text_container.form_forgot_links_msg)

        forgot_layout = QHBoxLayout()
        forgot_layout.addWidget(self.forgot_links, 0, Qt.AlignLeft)
        forgot_layout.addStretch(100000000)

        forgot_links_widget = QWidget()
        forgot_links_widget.setLayout(forgot_layout)

        return forgot_links_widget

    def _get_info_frame(self, text_container: TextContainer) -> styling.WidgetLoginInfoFrame:
        info_frame_logo: typing.Optional[styling.LabelLoginLogo] = None
        if text_container.info_frame_logo_path:
            info_frame_logo: styling.LabelLoginLogo = self._get_logo_icon(text_container.info_frame_logo_path)

        self.label_information = QLabel(text_container.info_frame_text)
        self.label_information.setWordWrap(True)

        info_layout = QVBoxLayout()

        if info_frame_logo is not None:
            info_layout.addWidget(info_frame_logo)

        info_layout.addWidget(self.label_information)

        info_widget: styling.WidgetLoginInfoFrame = styling.WidgetLoginInfoFrame()
        info_widget.setLayout(info_layout)

        return info_widget

    def _get_form_frame(self, text_container: TextContainer) -> styling.WidgetLoginFormFrame:
        self.label_login = QLabel('Username:')
        self.label_password = QLabel('Password:')

        self.text_login = QLineEdit()
        self.text_login.setValidator(QRegExpValidator(USER_RE))

        self.text_password = QLineEdit()
        self.text_password.setEchoMode(QLineEdit.Password)

        self.label_message = styling.LabelMainLoginText('')
        self.label_message.setWordWrap(True)
        self.label_message.setVisible(False)

        self.button_login = ButtonPrimary('Sign In')
        self.button_login.setDefault(True)

        login_form_layout = QVBoxLayout()

        for widget in (self.label_login, self.text_login, self.label_password, self.text_password, self.label_message):
            login_form_layout.addWidget(widget)

        forgot_links_widget = self._get_forgot_links_widget(text_container)
        if forgot_links_widget:
            login_form_layout.addWidget(forgot_links_widget)

        login_form_layout.addWidget(self.button_login, 0, Qt.AlignHCenter)

        login_form_widget = styling.WidgetLoginFormFrame()
        login_form_widget.setLayout(login_form_layout)

        return login_form_widget

    def _get_header_frame(self, text_container: TextContainer) -> typing.Optional[styling.WidgetHeaderFrame]:
        if not text_container.header_frame_logo_path:
            return None

        label_icon: styling.LabelLoginLogo = styling.LabelLoginLogo()
        label_icon.setPixmap(QPixmap(self.text_container.header_frame_logo_path))
        label_icon.setScaledContents(True)  # important on High DPI!
        label_icon.setAlignment(Qt.AlignLeft)

        header_layout = QVBoxLayout()
        header_layout.addWidget(label_icon)

        header_widget = styling.WidgetHeaderFrame()
        header_widget.setLayout(header_layout)

        return header_widget

    def _show_message_box(self, title: str, text: str) -> None:
        msg_box = MessageBoxInformation(title=title, text=text)
        msg_box.exec_()

        self.button_login.setDisabled(False)
        self.check_text()
        QApplication.restoreOverrideCursor()

    def _get_logo_icon(self, logo_path) -> styling.LabelLoginLogo:
        label_icon = styling.LabelLoginLogo()
        label_icon.setPixmap(QPixmap(logo_path))
        label_icon.setScaledContents(True)  # important on High DPI!
        label_icon.setAlignment(Qt.AlignLeft)
        return label_icon

    def open_url(self, url: str) -> None:
        """Open given url in the default browser and log the action."""
        self.tracker.track_event('content', 'click', url)
        QDesktopServices.openUrl(QUrl(url))

    def update_style_sheet(self, style_sheet=None) -> None:
        """Update custom css style sheet."""
        if style_sheet is None:
            style_sheet = load_style_sheet()

        self.setStyleSheet(style_sheet)


class CommonPage(BasePage):

    def __init__(self, api, text_container, parent: typing.Optional[QWidget] = None) -> None:
        """Initialize new :class:`~CommonPage` instance."""
        super().__init__(parent)

        self.api = api
        self.tracker = GATracker()
        self.text_container = text_container

        header_widget = self._get_header_frame(text_container)
        form_widget = self._get_form_frame(text_container)
        info_widget = self._get_info_frame(text_container)

        title = styling.LabelMainLoginTitle(text_container.title or "Sign in to access your repository")
        title.setWordWrap(True)

        body_layout = QHBoxLayout()
        body_layout.addWidget(info_widget)
        body_layout.addWidget(form_widget)

        body_page_widget = styling.WidgetLoginPageContent()
        body_page_widget.setLayout(body_layout)

        main_layout = QVBoxLayout()
        main_layout.addWidget(title)

        if header_widget:
            main_layout.addWidget(header_widget)
        main_layout.addWidget(body_page_widget)

        self.setLayout(main_layout)
        self.setup()


class BaseLoginPage(CommonPage):

    def __init__(self, api, text_container, parent: typing.Optional[QWidget] = None) -> None:
        """Initialize new :class:`~BaseLoginPage` instance."""
        super().__init__(api, text_container, parent=parent)

    def setup(self):
        self.update_links()

        self.text_login.setFocus()
        self.text_login.textEdited.connect(self.check_text)
        self.text_password.textEdited.connect(self.check_text)
        self.button_login.clicked.connect(self.login)

        self.check_text()
        self.update_style_sheet()

    def check_text(self):
        """Check that `login` and `password` are not empty strings.

        If not empty and disable/enable buttons accordingly.
        """
        login = self.text_login.text()
        password = self.text_password.text()

        self.button_login.setDisabled(not (login and password))

    def login(self):
        api_url = CONF.get('main', self.api_url_config_option)
        if not api_url:
            self._show_message_box(
                title='Login Error',
                text=self.text_container.domain_not_found_msg,
            )
            return

        username_text = self.text_login.text().lower()
        self.button_login.setEnabled(False)
        self.label_message.setText('')
        self.text_login.setText(username_text)

        QApplication.setOverrideCursor(Qt.WaitCursor)

        # Reload the client to the other one, if needed.
        CONF.set('main', 'logged_api_url', api_url)
        CONF.set('main', 'logged_brand', self.brand)
        self.api.client_reload()
        self.create_login_data()

        worker = self.api.login(username_text, self.text_password.text())
        worker.sig_finished.connect(self._finished)

    def _finished(self, worker, output, error):
        """
        Callback for the login procedure after worker has finished.

        If success, sets the token, 'username' attribute to parent widget
        and sends the accept signal.

        Otherwise, outputs error messages.
        """
        token = output
        username = self.text_login.text().lower()

        if token:
            self.accept()

        elif error:
            CONF.set('main', 'logged_api_url', None)
            CONF.set('main', 'logged_brand', None)

            bold_username = '<b>{0}</b>'.format(username)

            # The error might come in (error_message, http_error) format
            try:
                error_message = ast.literal_eval(str(error))[0]
            except Exception:  # pragma: no cover
                error_message = str(error)

            error_message = error_message.lower().capitalize()
            error_message = error_message.split(', ')[0]
            error_text = '<i>{0}</i>'.format(error_message)
            error_text = error_text.replace(username, bold_username)
            self.label_message.setText(error_text)
            self.label_message.setVisible(True)

            if error_message:
                domain = self.api.client_domain()
                label = '{0}/{1}: {2}'.format(domain, username, error_message.lower())
                self.tracker.track_event('authenticate', 'login failed', label=label)
                self.text_password.setFocus()
                self.text_password.selectAll()

        self.button_login.setDisabled(False)
        self.check_text()
        QApplication.restoreOverrideCursor()

    def update_links(self):
        """Fill with urls placeholders within text on page."""

    def create_login_data(self):
        """Post login configurations"""


class BaseSettingPage(CommonPage):

    def __init__(self, api, text_container, parent: typing.Optional[QWidget] = None) -> None:
        """Initialize new :class:`~BaseSettingPage` instance."""
        super().__init__(api, text_container, parent=parent)

    def setup(self):
        self.label_information.linkActivated.connect(lambda activated_link: self.open_url(activated_link))
        self.update_style_sheet()
        self.input_line.setFocus()

    def _get_form_frame(self, text_container):
        self.label_text = styling.LabelMainLoginText(text_container.form_primary_text)
        self.label_note = styling.LabelMainLoginSubTitle(text_container.form_secondary_text)
        self.input_label = QLabel(text_container.form_input_label_text)
        self.label_text.setWordWrap(True)
        self.label_note.setWordWrap(True)

        self.input_line = QLineEdit()

        self.label_message = styling.LabelMainLoginText('')
        self.label_message.setWordWrap(True)
        self.label_message.setVisible(False)

        self.button_apply = ButtonPrimary(text_container.form_submit_button_text)
        self.button_apply.setEnabled(True)
        self.button_apply.setDefault(True)

        login_form_widget = styling.WidgetLoginFormFrame()
        login_form_layout = QVBoxLayout()
        for widget in (self.label_text, self.label_note, self.input_label, self.input_line, self.label_message):
            login_form_layout.addWidget(widget)

        login_form_layout.addWidget(self.button_apply, 0, Qt.AlignHCenter)
        login_form_widget.setLayout(login_form_layout)

        return login_form_widget

    def set_domain(self):
        self.input_line.setText(self.input_line.text().lower())
        self.label_message.setText('')

        if self.check_text():
            CONF.set('main', self.api_url_config_option, self.input_line.text().strip('/'))
            self.api.client_reload()
            self.accept()

        QApplication.restoreOverrideCursor()

    def check_text(self):
        text = self.input_line.text()
        valid, error = self.is_valid_api(text.lower())

        if not valid:
            self.label_message.setText(error)
            self.label_message.setVisible(bool(self.input_line.text()))
            return False

        self.button_apply.setEnabled(True)
        self.label_message.setVisible(False)

        return True

    def is_valid_api(self, url, verify=True, allow_blank=False):
        """Check if a given URL is a valid anaconda api endpoint."""

        valid = self.api.download_is_valid_api_url(
            url, non_blocking=False, verify=verify, allow_blank=allow_blank
        )
        error = 'Invalid API url. Check the url is valid and corresponds to the api endpoint.' if not valid else ''
        return valid, error
