#!/usr/bin/python3

from datetime import datetime

from PyQt5.QtWidgets import QTableWidgetItem, QTableWidget, QAbstractItemView, QMenu, QMessageBox
from PyQt5.QtGui import QCursor
from PyQt5.QtCore import Qt, pyqtSignal, QObject

from portfolio.db.fdbhandler import results, strategies, balances


def updatingdata(func):
    """
    Decorator to flag self.updatingdata_flag whenever a function
    that edits data without user intervention is being run
    """

    def wrapper(self, *args, **kwargs):
        self.updatingdata_flag = True
        func(self, *args, **kwargs)
        self.updatingdata_flag = False
    return wrapper


class RightTable(QTableWidget):
    """
    Table dynamically showing results
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Custom Menu
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.showMenu)
        # A signal that will be emited whenever a line is removed
        self.lineremoved = LineRemoved()

        # UI Tweaks
        self.verticalHeader().hide()
        self.setSortingEnabled(True)

        self.setHorizontalHeaderLabels(
            ["id", self.tr("Date"), self.tr("Account"), self.tr("Strategy"), self.tr("Amount")])

        # When edited, change the data on the database too
        self.cellChanged.connect(self.changeCellOnDatabase)

        # A flag to prevent changeCellOnDatabase execution when needed
        self.updatingdata_flag = True

        # Initialization: show all transactions
        self.setData(datetime(1980, 1, 1), datetime.today(), "All", "All")

    @updatingdata
    def setData(self, startdate, enddate, strategy, account):
        """
        Asks the database for results data within certain parameters,
        then shows that data on the table
        """
        # Clear table
        self.clear()
        self.setHorizontalHeaderLabels(
            ["id", self.tr("Date"), self.tr("Account"), self.tr("Strategy"), self.tr("Amount"), self.tr("Description")])

        # Get desired data from db
        results_to_show = results.get_results_from_query(
            start_date=startdate, end_date=enddate, strategy=strategy, account=account)

        # If the data is empty, we are done
        if len(results_to_show) == 0:
            self.setRowCount(0)
            return

        # Resize table
        self.setRowCount(len(results_to_show))
        self.setColumnCount(len(results_to_show[0]))

        # Change content
        for rownum, row in enumerate(results_to_show):
            for colnum, data in enumerate(row):
                item = QTableWidgetItem()  # Item that will be inserted

                if colnum == 0:
                    # Ids can't be editable
                    item.setFlags(Qt.ItemIsSelectable)
                elif colnum == 1:
                    # Change format to display date better
                    data = datetime.fromtimestamp(data).strftime("%d-%m-%Y")

                # Data is now formatted, we can write it on table
                item.setData(0, data)
                self.setItem(rownum, colnum, item)

    def showMenu(self, event):
        """
        Custom Menu to show when an item is right-clicked

        Options:
        - Remove Line: removes line from table and database
        """
        menu = QMenu()

        # Actions
        remove_action = menu.addAction(self.tr("Remove Line"))

        # Getting action selected by user
        action = menu.exec_(QCursor.pos())

        # Act accordingly
        if action == remove_action:
            self.removeSelection()
            self.lineremoved.lineRemoved.emit()

    @updatingdata
    def removeSelection(self):
        """
        Removes the entire row of every selected item,
        and then does the same on the databse
        """

        # Getting selected indexes, and their corresponding ids
        # from the database
        selected_indexes_table, selected_ids = [], []
        for index in self.selectedIndexes():
            index = index.row()  # Row number
            if index not in selected_indexes_table:  # Avoid duplicates
                selected_indexes_table.append(index)
                selected_ids.append(int(self.item(index, 0).text()))

        # Removing the rows from the table and the database
        for index, id_db in zip(selected_indexes_table, selected_ids):
            results.delete_result(id_db)
            self.removeRow(index)

        print("Removed rows with ids on db : ", selected_ids,
              "\n & ids on table: ", selected_indexes_table)

    def changeCellOnDatabase(self, row, column):
        """
        When a Table Item is edited by the user,
        we want to check if it fits the type
        and edit it on the database too
        """

        if self.updatingdata_flag is True:
            return
            # The data is being modified internally (not by the user)
            # so no errors assumed

        new_item = self.item(row, column)
        new_item_data = new_item.text()
        database_entry_id = self.item(row, 0).text()

        previous_amount = results.getResultAmountById(
            database_entry_id)  # Useful for balance adjustments later
        columnselected_name = self.horizontalHeaderItem(column).text()
        # Depending on from which column the item is, we check the data
        # proposed differently

        # Check which part of the transaction has been edited, and accting accordingly
        # -------------- id --------------------
        if columnselected_name == self.tr("Id"):
            # Ids can't be edited
            error_mssg = QMessageBox()
            error_mssg.setIcon(QMessageBox.Warning)
            error_mssg.setText(self.tr("Ids can't be edited"))
            error_mssg.exec_()

        # -------------- Date --------------------
        elif columnselected_name == self.tr("Date"):
            # The new text has to be a date
            try:
                new_date = datetime.strptime(new_item_data, "%d-%m-%Y")
                results.update_result(
                    database_entry_id, new_date=new_date.timestamp())

            except ValueError:
                error_mssg = QMessageBox()
                error_mssg.setIcon(QMessageBox.Warning)
                error_mssg.setText(
                    self.tr("Has to be a date in format dd-mm-yyyy"))
                error_mssg.exec_()

                # Reset date to previous one
                previous_date_timestamp = results.get_result_date_by_id(
                    database_entry_id)
                previous_date_text = datetime.fromtimestamp(
                    previous_date_timestamp).strftime("%d-%m-%Y")
                self.updatingdata_flag = True
                new_item.setData(0, previous_date_text)
                self.updatingdata_flag = False

        # -------------- Account --------------------
        elif columnselected_name == self.tr("Account"):
            # The account has to be an existing one
            all_accounts = [a[0] for a in balances.get_all_accounts()]
            previous_account = results.get_result_account_by_id(
                database_entry_id)

            if new_item_data not in all_accounts:
                error_mssg = QMessageBox()
                error_mssg.setIcon(QMessageBox.Warning)
                error_mssg.setText(
                    self.tr("The account has to be an existing one. \nAdd it first manually"))
                error_mssg.exec_()

                # Reset strategy to previous one
                self.updatingdata_flag = True
                new_item.setData(0, previous_account)
                self.updatingdata_flag = False

            else:
                # The data is good
                # Change the result on the results table on the db
                results.update_result(
                    database_entry_id, new_account=new_item_data)
                # Update the balance of the two accounts involved,
                # according to the result amount
                balances.update_balances_with_new_result(
                    previous_account, - previous_amount)
                balances.update_balances_with_new_result(
                    new_item_data, previous_amount)

        # -------------- Strategy --------------------
        elif columnselected_name == self.tr("Strategy"):
            # The strategy has to be an existing one
            previous_strategy = results.get_result_strategy_by_id(
                database_entry_id)
            all_strategies = [s[0] for s in strategies.get_all_strategies()]

            if new_item_data not in all_strategies:
                error_mssg = QMessageBox()
                error_mssg.setIcon(QMessageBox.Warning)
                error_mssg.setText(
                    self.tr("The strategy has to be an existing one. \nAdd it first manually"))
                error_mssg.exec_()

                # Reset strategy to previous one
                self.updatingdata_flag = True
                new_item.setData(0, previous_strategy)
                self.updatingdata_flag = False
            else:
                # The data is good
                # Change the result on the results table of the db
                results.updateResult(
                    database_entry_id, newstrategy=new_item_data)
                # Update the pnl of the two strategies involved,
                # according to the result amount
                strategies.update_strategies_with_new_result(
                    previous_strategy, - previous_amount)
                strategies.update_strategies_with_new_result(
                    new_item_data, previous_amount)

        # -------------- Amount --------------------
        elif columnselected_name == self.tr("Amount"):
            # The amount has to be an integer
            try:
                new_item_data = int(new_item_data)
                # Change the result on the results table of the db
                results.update_result(
                    database_entry_id, new_amount=new_item_data)
                # Update the balances and strategies with the difference
                # between the old and the new result
                diff_betweeen_results = new_item_data - previous_amount
                account_involved = results.get_result_account_by_id(
                    database_entry_id)
                strategy_involved = results.get_result_strategy_by_id(
                    database_entry_id)

                balances.update_balances_with_new_result(
                    account_involved, diff_betweeen_results)
                strategies.update_strategies_with_new_result(
                    strategy_involved, diff_betweeen_results)

            except Exception:
                error_mssg = QMessageBox()
                error_mssg.setIcon(QMessageBox.Warning)
                error_mssg.setText(
                    self.tr("Has to be an integer"))
                error_mssg.exec_()

                # Reset to previous amount
                previous_amount = results.get_result_amount_by_id(
                    database_entry_id)
                self.updatingdata_flag = True
                new_item.setData(0, previous_amount)
                self.updatingdata_flag = False

        # -------------- Description --------------------
        elif columnselected_name == self.tr("Description"):
            # A description can be any data. So no checks
            results.update_result(
                database_entry_id, new_description=new_item_data)


class LineRemoved(QObject):
    lineRemoved = pyqtSignal()

[(298, 'PyQt5.QtCore.pyqtSignal', 'pyqtSignal', False, 'from PyQt5.QtCore import Qt, pyqtSignal, QObject\n'), (67, 'portfolio.db.fdbhandler.results.get_results_from_query', 'results.get_results_from_query', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (102, 'PyQt5.QtWidgets.QMenu', 'QMenu', False, 'from PyQt5.QtWidgets import QTableWidgetItem, QTableWidget, QAbstractItemView, QMenu, QMessageBox\n'), (155, 'portfolio.db.fdbhandler.results.getResultAmountById', 'results.getResultAmountById', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (53, 'datetime.datetime', 'datetime', False, 'from datetime import datetime\n'), (53, 'datetime.datetime.today', 'datetime.today', False, 'from datetime import datetime\n'), (108, 'PyQt5.QtGui.QCursor.pos', 'QCursor.pos', False, 'from PyQt5.QtGui import QCursor\n'), (133, 'portfolio.db.fdbhandler.results.delete_result', 'results.delete_result', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (165, 'PyQt5.QtWidgets.QMessageBox', 'QMessageBox', False, 'from PyQt5.QtWidgets import QTableWidgetItem, QTableWidget, QAbstractItemView, QMenu, QMessageBox\n'), (82, 'PyQt5.QtWidgets.QTableWidgetItem', 'QTableWidgetItem', False, 'from PyQt5.QtWidgets import QTableWidgetItem, QTableWidget, QAbstractItemView, QMenu, QMessageBox\n'), (174, 'datetime.datetime.strptime', 'datetime.strptime', False, 'from datetime import datetime\n'), (198, 'portfolio.db.fdbhandler.results.get_result_account_by_id', 'results.get_result_account_by_id', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (179, 'PyQt5.QtWidgets.QMessageBox', 'QMessageBox', False, 'from PyQt5.QtWidgets import QTableWidgetItem, QTableWidget, QAbstractItemView, QMenu, QMessageBox\n'), (186, 'portfolio.db.fdbhandler.results.get_result_date_by_id', 'results.get_result_date_by_id', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (202, 'PyQt5.QtWidgets.QMessageBox', 'QMessageBox', False, 'from PyQt5.QtWidgets import QTableWidgetItem, QTableWidget, QAbstractItemView, QMenu, QMessageBox\n'), (216, 'portfolio.db.fdbhandler.results.update_result', 'results.update_result', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (220, 'portfolio.db.fdbhandler.balances.update_balances_with_new_result', 'balances.update_balances_with_new_result', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (222, 'portfolio.db.fdbhandler.balances.update_balances_with_new_result', 'balances.update_balances_with_new_result', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (228, 'portfolio.db.fdbhandler.results.get_result_strategy_by_id', 'results.get_result_strategy_by_id', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (197, 'portfolio.db.fdbhandler.balances.get_all_accounts', 'balances.get_all_accounts', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (233, 'PyQt5.QtWidgets.QMessageBox', 'QMessageBox', False, 'from PyQt5.QtWidgets import QTableWidgetItem, QTableWidget, QAbstractItemView, QMenu, QMessageBox\n'), (246, 'portfolio.db.fdbhandler.results.updateResult', 'results.updateResult', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (250, 'portfolio.db.fdbhandler.strategies.update_strategies_with_new_result', 'strategies.update_strategies_with_new_result', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (252, 'portfolio.db.fdbhandler.strategies.update_strategies_with_new_result', 'strategies.update_strategies_with_new_result', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (89, 'datetime.datetime.fromtimestamp', 'datetime.fromtimestamp', False, 'from datetime import datetime\n'), (188, 'datetime.datetime.fromtimestamp', 'datetime.fromtimestamp', False, 'from datetime import datetime\n'), (230, 'portfolio.db.fdbhandler.strategies.get_all_strategies', 'strategies.get_all_strategies', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (261, 'portfolio.db.fdbhandler.results.update_result', 'results.update_result', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (266, 'portfolio.db.fdbhandler.results.get_result_account_by_id', 'results.get_result_account_by_id', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (268, 'portfolio.db.fdbhandler.results.get_result_strategy_by_id', 'results.get_result_strategy_by_id', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (271, 'portfolio.db.fdbhandler.balances.update_balances_with_new_result', 'balances.update_balances_with_new_result', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (273, 'portfolio.db.fdbhandler.strategies.update_strategies_with_new_result', 'strategies.update_strategies_with_new_result', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (293, 'portfolio.db.fdbhandler.results.update_result', 'results.update_result', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n'), (277, 'PyQt5.QtWidgets.QMessageBox', 'QMessageBox', False, 'from PyQt5.QtWidgets import QTableWidgetItem, QTableWidget, QAbstractItemView, QMenu, QMessageBox\n'), (284, 'portfolio.db.fdbhandler.results.get_result_amount_by_id', 'results.get_result_amount_by_id', False, 'from portfolio.db.fdbhandler import results, strategies, balances\n')]