# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright 2016 Continuum Analytics, Inc.
#
# May be copied and distributed freely only as part of an Anaconda or
# Miniconda installation.
# -----------------------------------------------------------------------------
"""License Manager Dialog."""

# Standard library imports
import datetime
import json
import os

# Third party imports
from qtpy.compat import getopenfilename, to_qvariant
from qtpy.QtCore import (QAbstractTableModel, QModelIndex,
                         QSortFilterProxyModel, Qt, Signal)
from qtpy.QtGui import QColor
from qtpy.QtWidgets import (QAbstractItemView, QHBoxLayout, QStyle,
                            QStyledItemDelegate, QTableView, QVBoxLayout)
import _license

# Local imports
from anaconda_navigator.config import get_home_dir
from anaconda_navigator.utils.qthelpers import qapplication
from anaconda_navigator.widgets import (ButtonLink, ButtonNormal,
                                        ButtonPrimary, LabelBase,
                                        SpacerHorizontal, SpacerVertical)
from anaconda_navigator.widgets.dialogs import (DialogBase,
                                                MessageBoxInformation,
                                                MessageBoxQuestion,
                                                MessageBoxRemove)

REMOVED_LICENSE_PATH = '.removed_licenses'
# Extra data added to the license dicts to track the file it comes from
# Defined as a constant as it is used in several places so this avoidd hard
# coding a string
LICENSE_PATH = '_file_path'
COL_MAP = {
    0: 'type',
    1: 'product',
    2: 'end_date',
    3: 'status',
    4: 'sig',
    5: LICENSE_PATH,
}
# Other license like cluster dont really have a meaning for a dekstop user
# using navigator
VALID_PRODUCT_LICENSES = [
    'accelerate',
    'Anaconda Enterprise Repository',
    'iopro',
    'mkl-optimizations',
]


HIDDEN_COLUMNS = [LICENSE_PATH, 'sig']


class LicenseModel(QAbstractTableModel):
    """Table model for the license view."""

    def __init__(self, parent=None, licenses=None):
        """Table model for the license view."""
        super(LicenseModel, self).__init__(parent=parent)
        self._parent = parent
        self._rows = licenses if licenses else []

    @staticmethod
    def flags(index):
        """Override Qt method."""
        if index.isValid():
            return Qt.ItemFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)

    def data(self, index, role=Qt.DisplayRole):
        """Override Qt method."""
        if not index.isValid() or not 0 <= index.row() < len(self._rows):
            return None

        row = index.row()
        column = index.column()
        license_data = self._rows[row]

        if role == Qt.DisplayRole:
            data_key = COL_MAP.get(column)
            if data_key:
                return license_data.get(data_key)
        elif role == Qt.TextAlignmentRole:
            return Qt.AlignCenter

        return to_qvariant()

    @staticmethod
    def headerData(section, orientation, role=Qt.DisplayRole):
        """Override Qt method."""
        title = COL_MAP.get(section)
        title = title.replace('_', ' ').capitalize()
        if role == Qt.TextAlignmentRole:
            if orientation == Qt.Horizontal:
                return to_qvariant(int(Qt.AlignHCenter | Qt.AlignVCenter))
            return to_qvariant(int(Qt.AlignRight | Qt.AlignVCenter))
        elif role == Qt.ToolTipRole:
            return to_qvariant()
        elif role == Qt.DisplayRole and orientation == Qt.Horizontal:
            return to_qvariant(title)

    def rowCount(self, index=QModelIndex()):
        """Override Qt method."""
        return len(self._rows)

    @staticmethod
    def columnCount(index=QModelIndex()):
        """Override Qt method."""
        return len(COL_MAP)

    # --- Helpers
    # -------------------------------------------------------------------------
    def row(self, rownum):
        """Return the row data."""
        return self._rows[rownum] if rownum < len(self._rows) else None

    def load_licenses(self, licenses=None):
        """(Re)Load license data."""
        self._rows = licenses if licenses else []


class BackgroundDelegate(QStyledItemDelegate):
    """
    Delegate for handling background color in table.

    QTableView CSS styling rules are too limited so in order to get an even
    styling that matches the overall look, this delegate is needed.
    """

    def __init__(self, parent=None):
        """Delegate for handling background color in table."""
        super(BackgroundDelegate, self).__init__(parent=parent)
        self._parent = parent

    def paint(self, painter, option, index):
        """Override Qt method."""
        # To draw a border on selected cells
        if option.state & QStyle.State_Selected:
            if self._parent.hasFocus():
                color = QColor('#43B02A')  # TODO: Get this from the scss
            else:
                color = QColor('#cecece')  # TODO: Get this from the scss

            painter.save()
            painter.fillRect(option.rect, color)
            painter.restore()

            # Disable the state for the super() painter method
            option.state ^= QStyle.State_Selected

        super(BackgroundDelegate, self).paint(painter, option, index)


class LicenseTableView(QTableView):
    """License table manager view."""

    sig_dropped = Signal(object)
    sig_entered = Signal()
    sig_left = Signal()

    def __init__(self, parent=None):
        """License table manager view."""
        super(LicenseTableView, self).__init__(parent=parent)
        self.setMinimumWidth(500)
        self.setMinimumHeight(200)
        self.setAcceptDrops(True)
        self.setShowGrid(False)
        self.setSortingEnabled(True)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setAlternatingRowColors(True)
        self.setSelectionMode(QAbstractItemView.SingleSelection)
        self.verticalHeader().hide()
        self.horizontalHeader().setStretchLastSection(True)

    def focusInEvent(self, event):
        """Override Qt Method."""
        super(LicenseTableView, self).focusInEvent(event)
        self.sig_entered.emit()

    def focusOutEvent(self, event):
        """Override Qt Method."""
        super(LicenseTableView, self).focusInEvent(event)
        self.sig_left.emit()

    def dragEnterEvent(self, event):
        """Override Qt Method."""
        self.setProperty('dragin', True)
        if event.mimeData().hasUrls:
            event.accept()
        else:
            event.ignore()

    def dragLeaveEvent(self, event):
        """Override Qt Method."""
        self.setProperty('dragin', False)

    def dragMoveEvent(self, event):
        """Override Qt Method."""
        if event.mimeData().hasUrls:
            event.setDropAction(Qt.CopyAction)
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        """Override Qt Method."""
        self.setProperty('dragin', False)
        if event.mimeData().hasUrls:
            event.setDropAction(Qt.CopyAction)
            event.accept()
            links = []
            for url in event.mimeData().urls():
                links.append(str(url.toLocalFile()))
            self.sig_dropped.emit(tuple(links))
        else:
            event.ignore()

    def setProperty(self, name, value):
        """Override Qt method."""
        QTableView.setProperty(self, name, value)
        self.style().unpolish(self)
        self.style().polish(self)
        self.update()


class LicenseManagerDialog(DialogBase):
    """License Manager main dialog."""

    CONTACT_LINK = 'https://support.continuum.io/'  # TODO: Centralize this?

    # Url, Sender
    sig_url_clicked = Signal(object, object)

    def __init__(self, parent=None):
        """License Manager main dialog."""
        super(LicenseManagerDialog, self).__init__(parent=parent)

        # Widgets
        self.message_box = None  # For testing
        self.button_add = ButtonPrimary('Add license')
        self.button_close = ButtonNormal('Close')
        self.button_remove = ButtonNormal('Remove license')
        self.button_contact = ButtonLink('Please contact us.')
        self.label_info = LabelBase('Manage your Continuum Analytics '
                                    'license keys.')
        self.label_contact = LabelBase('Got a problem with your license? ')
        self.proxy_model = QSortFilterProxyModel(parent=self)
        self.model = LicenseModel(parent=self)
        self.table = LicenseTableView(parent=self)
        self.delegate = BackgroundDelegate(self.table)

        # Widget setup
        self.proxy_model.setSourceModel(self.model)
        self.table.setItemDelegate(self.delegate)
        self.table.setModel(self.proxy_model)
        self.setWindowTitle('License Manager')

        # Layouts
        layout_buttons = QHBoxLayout()
        layout_buttons.addWidget(self.label_info)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_add)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_remove)

        layout_buttons_bottom = QHBoxLayout()
        layout_buttons_bottom.addWidget(self.label_contact)
        layout_buttons_bottom.addWidget(self.button_contact)
        layout_buttons_bottom.addStretch()
        layout_buttons_bottom.addWidget(self.button_close)

        layout = QVBoxLayout()
        layout.addLayout(layout_buttons)
        layout.addWidget(SpacerVertical())
        layout.addWidget(self.table)
        layout.addWidget(SpacerVertical())
        layout.addWidget(SpacerVertical())
        layout.addLayout(layout_buttons_bottom)
        self.setLayout(layout)

        # Signals
        self.button_add.clicked.connect(lambda: self.add_license())
        self.button_remove.clicked.connect(self.remove_license)
        self.button_close.clicked.connect(self.accept)
        self.button_contact.clicked.connect(
            lambda v=None: self.sig_url_clicked.emit(self.CONTACT_LINK,
                                                     'License Manager'))
        self.table.sig_dropped.connect(self._add_license)

        # Setup
        self.button_add.setFocus()
        self.load_licenses()

    def _hide_columns(self):
        """Hide columns."""
        for key, val in COL_MAP.items():
            if val in HIDDEN_COLUMNS:
                self.table.setColumnHidden(key, True)

    def _add_license(self, paths):
        """Add license file callback."""
        valid_licenses = {}
        paths = [p for p in paths if os.path.isfile(p)]
        for path in paths:
            lic = _license.read_licenses(path)
            if lic:
                valid_licenses[path] = lic
            else:
                text = ('File: <b>"{0}"</b>'
                        '<br>is not a valid license file.').format(path)
                self.message_box = MessageBoxInformation(
                    text=text,
                    title="Invalid license file")
                self.message_box.exec_()

        # FIXME: Check  if license name exists in any of the paths
        # And then ask the user a question based on this
        if not os.path.isdir(self.license_location()):
            os.mkdir(self.license_location())

        for path in valid_licenses:
            head, tail = os.path.split(path)
            new_path = os.path.join(self.license_location(), tail)
            if not os.path.isfile(new_path):
                with open(new_path, 'w') as f:
                    json.dump(valid_licenses[path], f)
            else:
                text = ('File: <b>"{0}"</b>'
                        '<br>already exists.<br>'
                        'Do you want to overwrite it?').format(path)
                self.message_box = MessageBoxQuestion(
                    text=text,
                    title="License file exists")
                if self.message_box.exec_():
                    with open(new_path, 'w') as f:
                        json.dump(valid_licenses[path], f)

        if valid_licenses:
            self.load_licenses()

    def add_license(self, v=None, path=None):
        """Add license file."""
        if path is None:
            filename, selected_filter = getopenfilename(
                self,
                'Select license file',
                filters='License files (*.txt)',
                basedir=get_home_dir(),
            )

            if filename:
                paths = [filename]
            else:
                paths = []
        else:
            paths = [path]

        self._add_license(paths)

    def _remove_license(self, lic):
        """Remove license callback."""
        path = lic.get(LICENSE_PATH)
        sig = lic.get('sig')

        with open(path) as f:
            licenses = json.load(f)

        for i, lic in enumerate(licenses):
            if lic.get('sig') == sig:
                break

        removed_license = licenses.pop(i)
        with open(path, 'w') as f:
            json.dump(licenses, f)

        head, tail = os.path.split(os.path.abspath(path))
        removed_folder = os.path.join(head, REMOVED_LICENSE_PATH)
        removed_path = os.path.join(removed_folder, tail)

        if not os.path.isdir(removed_folder):
            os.mkdir(removed_folder)

        removed_licenses = [removed_license]
        if os.path.isfile(removed_path):
            # Merge removed files
            with open(removed_path) as f:
                existing_removed_licenses = json.load(f)
                removed_licenses.extend(existing_removed_licenses)

        with open(removed_path, 'w') as f:
            json.dump(removed_licenses, f)

    def remove_license(self, row=None):
        """Remove license from file."""
        if row is None:
            index = self.table.currentIndex()
        else:
            index = self.proxy_model.index(row, 0)

        model_index = self.proxy_model.mapToSource(index)
        row_data = self.model.row(model_index.row())

        if row_data:
            text = ('Do you want to remove license for product:<br><br>'
                    '<b>{product}</b> ({issued} - {end_date})')
            text = text.format(product=row_data.get('product'),
                               end_date=row_data.get('end_date'),
                               issued=row_data.get('issued'))
            self.message_box = MessageBoxRemove(
                title='Remove license',
                text=text)
            if self.message_box.exec_():
                self._remove_license(row_data)
                self.load_licenses()

    def load_licenses(self):
        """Load license files."""
        res = []
        # This is used instead of _license.find_licenses to have the path
        # for each file
        for license_path in self.license_paths():
            licenses = _license.read_licenses(license_path)
            for lic in licenses:
                if lic.get('product') in VALID_PRODUCT_LICENSES:
                    valid = self.is_license_valid(lic)
                    status = 'Valid' if valid else 'Invalid'
                    lic['status'] = status
                    lic['type'] = lic.get('type', 'Enterprise')
                    lic[LICENSE_PATH] = license_path
                    res.append(lic)

        self.model.load_licenses(res)
        self.proxy_model.setSourceModel(self.model)
        self.table.resizeColumnsToContents()
        self._hide_columns()
        self.update_status()

    def count(self):
        """Return the number of items in the table."""
        return self.table.model().rowCount()

    def update_status(self):
        """Update visible and enabled status for widgets based on actions."""
        self.button_remove.setEnabled(bool(self.count()))

    # -- License helpers, useful in testing
    # -------------------------------------------------------------------------
    @staticmethod
    def license_paths():
        """Return licenses paths founds on main location."""
        return _license.get_license_paths()

    @staticmethod
    def license_location():
        """Return license main location."""
        return _license.get_license_dirs()[0]

    @staticmethod
    def is_license_valid(lic):
        """Return wether a license dictionary is valid."""
        licenses = [lic]
        licenses = _license.filter_signatures(licenses)
        licenses = _license.filter_vendor(licenses)

        end_date = lic.get('end_date')
        if end_date:
            end_date_components = [int(i) for i in end_date.split('-')]
            end_date = datetime.datetime(*end_date_components)
            now = datetime.datetime.now()
            valid = end_date > now

        return bool(len(licenses)) and valid


def test():  # pragma: no cover
    """Run local test."""
    app = qapplication()
    w = LicenseManagerDialog()
    w.update_style_sheet()
    w.show()
    app.exec_()


if __name__ == '__main__':
    test()
