# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# *                                                                         *
# *   Copyright (c) 2022-2023 FreeCAD Project Association                   *
# *                                                                         *
# *   This file is part of FreeCAD.                                         *
# *                                                                         *
# *   FreeCAD is free software: you can redistribute it and/or modify it    *
# *   under the terms of the GNU Lesser General Public License as           *
# *   published by the Free Software Foundation, either version 2.1 of the  *
# *   License, or (at your option) any later version.                       *
# *                                                                         *
# *   FreeCAD is distributed in the hope that it will be useful, but        *
# *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
# *   Lesser General Public License for more details.                       *
# *                                                                         *
# *   You should have received a copy of the GNU Lesser General Public      *
# *   License along with FreeCAD. If not, see                               *
# *   <https://www.gnu.org/licenses/>.                                      *
# *                                                                         *
# ***************************************************************************

import os

import FreeCAD
import FreeCADGui
from addonmanager_git import initialize_git

from PySide import QtWidgets, QtCore

translate = FreeCAD.Qt.translate


class ChangeBranchDialog(QtWidgets.QWidget):

    branch_changed = QtCore.Signal(str)

    def __init__(self, path: str, parent=None):
        super().__init__(parent)

        self.ui = FreeCADGui.PySideUic.loadUi(
            os.path.join(os.path.dirname(__file__), "change_branch.ui")
        )

        self.item_filter = ChangeBranchDialogFilter()
        self.ui.tableView.setModel(self.item_filter)

        self.item_model = ChangeBranchDialogModel(path, self)
        self.item_filter.setSourceModel(self.item_model)
        self.ui.tableView.sortByColumn(
            2, QtCore.Qt.DescendingOrder
        )  # Default to sorting by remote last-changed date

        # Figure out what row gets selected:
        git_manager = initialize_git()
        row = 0
        current_ref = git_manager.current_branch(path)
        selection_model = self.ui.tableView.selectionModel()
        for ref in self.item_model.branches:
            if ref["ref_name"] == current_ref:
                index = self.item_filter.mapFromSource(self.item_model.index(row, 0))
                selection_model.select(index, QtCore.QItemSelectionModel.ClearAndSelect)
                selection_model.select(index.siblingAtColumn(1), QtCore.QItemSelectionModel.Select)
                selection_model.select(index.siblingAtColumn(2), QtCore.QItemSelectionModel.Select)
                break
            row += 1

        # Make sure the column widths are OK:
        header = self.ui.tableView.horizontalHeader()
        header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)

    def exec(self):
        if self.ui.exec() == QtWidgets.QDialog.Accepted:

            selection = self.ui.tableView.selectedIndexes()
            index = self.item_filter.mapToSource(selection[0])
            ref = self.item_model.data(index, ChangeBranchDialogModel.RefAccessRole)

            if ref["ref_name"] == self.item_model.current_branch:
                # This is the one we are already on... just return
                return

            result = QtWidgets.QMessageBox.critical(
                self,
                translate("AddonsInstaller", "DANGER: Developer feature"),
                translate(
                    "AddonsInstaller",
                    "DANGER: Switching branches is intended for developers and beta testers, "
                    "and may result in broken, non-backwards compatible documents, instability, "
                    "crashes, and/or the premature heat death of the universe. Are you sure you "
                    "want to continue?",
                ),
                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel,
                QtWidgets.QMessageBox.Cancel,
            )
            if result == QtWidgets.QMessageBox.Cancel:
                return
            if self.item_model.dirty:
                result = QtWidgets.QMessageBox.critical(
                    self,
                    translate("AddonsInstaller", "There are local changes"),
                    translate(
                        "AddonsInstaller",
                        "WARNING: This repo has uncommitted local changes. Are you sure you want "
                        "to change branches (bringing the changes with you)?",
                    ),
                    QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel,
                    QtWidgets.QMessageBox.Cancel,
                )
                if result == QtWidgets.QMessageBox.Cancel:
                    return

            gm = initialize_git()
            remote_name = ref["ref_name"]
            _, _, local_name = ref["ref_name"].rpartition("/")
            if ref["upstream"]:
                gm.checkout(self.item_model.path, remote_name)
            else:
                gm.checkout(self.item_model.path, remote_name, args=["-b", local_name])
            self.branch_changed.emit(local_name)


class ChangeBranchDialogModel(QtCore.QAbstractTableModel):

    branches = []
    DataSortRole = QtCore.Qt.UserRole
    RefAccessRole = QtCore.Qt.UserRole + 1

    def __init__(self, path: str, parent=None) -> None:
        super().__init__(parent)

        gm = initialize_git()
        self.path = path
        self.branches = gm.get_branches_with_info(path)
        self.current_branch = gm.current_branch(path)
        self.dirty = gm.dirty(path)
        self._remove_tracking_duplicates()

    def rowCount(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()) -> int:
        if parent.isValid():
            return 0
        return len(self.branches)

    def columnCount(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()) -> int:
        if parent.isValid():
            return 0
        return 3  # Local name, remote name, date

    def data(self, index: QtCore.QModelIndex, role: int = QtCore.Qt.DisplayRole):
        if not index.isValid():
            return None
        row = index.row()
        column = index.column()
        if role == QtCore.Qt.ToolTipRole:
            tooltip = self.branches[row]["author"] + ": " + self.branches[row]["subject"]
            return tooltip
        elif role == QtCore.Qt.DisplayRole:
            dd = self.branches[row]
            if column == 2:
                if dd["date"] is not None:
                    q_date = QtCore.QDateTime.fromString(
                        dd["date"], QtCore.Qt.DateFormat.RFC2822Date
                    )
                    return QtCore.QLocale().toString(q_date, QtCore.QLocale.ShortFormat)
                return None
            elif column == 0:
                return dd["ref_name"]
            elif column == 1:
                return dd["upstream"]
            else:
                return None
        elif role == ChangeBranchDialogModel.DataSortRole:
            if column == 2:
                if self.branches[row]["date"] is not None:
                    q_date = QtCore.QDateTime.fromString(
                        self.branches[row]["date"], QtCore.Qt.DateFormat.RFC2822Date
                    )
                    return q_date
                return None
            elif column == 0:
                return self.branches[row]["ref_name"]
            elif column == 1:
                return self.branches[row]["upstream"]
            else:
                return None
        elif role == ChangeBranchDialogModel.RefAccessRole:
            return self.branches[row]

    def headerData(
        self,
        section: int,
        orientation: QtCore.Qt.Orientation,
        role: int = QtCore.Qt.DisplayRole,
    ):
        if orientation == QtCore.Qt.Vertical:
            return None
        if role != QtCore.Qt.DisplayRole:
            return None
        if section == 0:
            return translate(
                "AddonsInstaller",
                "Local",
                "Table header for local git ref name",
            )
        if section == 1:
            return translate(
                "AddonsInstaller",
                "Remote tracking",
                "Table header for git remote tracking branch name",
            )
        elif section == 2:
            return translate(
                "AddonsInstaller",
                "Last Updated",
                "Table header for git update date",
            )
        else:
            return None

    def _remove_tracking_duplicates(self):
        remote_tracking_branches = []
        branches_to_keep = []
        for branch in self.branches:
            if branch["upstream"]:
                remote_tracking_branches.append(branch["upstream"])
        for branch in self.branches:
            if (
                "HEAD" not in branch["ref_name"]
                and branch["ref_name"] not in remote_tracking_branches
            ):
                branches_to_keep.append(branch)
        self.branches = branches_to_keep


class ChangeBranchDialogFilter(QtCore.QSortFilterProxyModel):
    def lessThan(self, left: QtCore.QModelIndex, right: QtCore.QModelIndex):
        leftData = self.sourceModel().data(left, ChangeBranchDialogModel.DataSortRole)
        rightData = self.sourceModel().data(right, ChangeBranchDialogModel.DataSortRole)
        if leftData is None or rightData is None:
            if rightData is not None:
                return True
            else:
                return False
        return leftData < rightData
