import atexit
import os
import shutil
import sys
import tempfile
from pathlib import Path
from typing import ClassVar

import PyQt6Ads as QtAds
from PyQt6.QtCore import QObject, QSettings, pyqtSignal

sys.path.append(str(Path(__file__).resolve().parent))
from dockindock import DockInDockWidget
from dockindockmanager import DockInDockManager, MoveDockWidgetAction

GROUP_PREFIX = "Group"


def findWidget(name, managers: "list[DockInDockManager]") -> QtAds.CDockWidget:
    for mgr in managers:
        widget = mgr.findDockWidget(name)
        if widget:
            return widget


class PerspectiveInfo:
    # Partially bypass ADS perspective management, store list here
    # and then ADS will only have one perspective loaded
    # this is because all docking widgets must exist when a perspective is loaded
    # we will guarantee that!

    settings = QSettings()
    groups: ClassVar[dict[str, list[str]]] = {}


class PerspectivesManager(QObject):
    perspectivesListChanged = pyqtSignal()
    openingPerspective = pyqtSignal()
    openedPerspective = pyqtSignal()

    def __init__(self, perspectives_folder):
        super().__init__()
        self.__perspectives_folder = perspectives_folder
        self.__perspectives = {}
        atexit.register(self.cleanup)

    def cleanup(self):
        for perspective in self.__perspectives.values():
            filename = perspective.settings.fileName()
            try:
                os.remove(filename)
            except FileNotFoundError:
                pass

    def perspectiveNames(self) -> "list[str]":
        return self.__perspectives.keys()

    def addPerspective(self, name: str, widget: DockInDockWidget) -> None:
        if self.__perspectives_folder:
            self.__perspectives[name] = perspective = PerspectiveInfo()
            perspective.settings = self.getSettingsObject(
                self.getSettingsFileName(name, True)
            )
            perspective.groups = widget.getManager().getGroupContents()

            # save perspective internally
            widget.getManager().addPerspectiveRec(name)
            # store it in QSettings object
            widget.getManager().savePerspectivesRec(perspective.settings)
            # remove internal perspectives
            widget.getManager().removePerspectives(
                widget.getManager().perspectiveNames()
            )

        self.perspectivesListChanged.emit()

    def openPerspective(self, name: str, widget: DockInDockWidget) -> None:
        assert widget.getTopLevelDockWidget() == widget

        if self.__perspectives_folder:
            if name in self.__perspectives:
                self.openingPerspective.emit()

                if widget.canCreateNewGroups():
                    cur_groups = widget.getManager().allManagers(True, True)
                    for group in self.__perspectives[name].groups.keys():
                        found = False
                        for curgroup in cur_groups:
                            if curgroup.getPerspectiveGroupName() == group:
                                found = True
                                break
                        if not found:
                            group = DockInDockManager.getGroupNameFromPersistGroupName(
                                group
                            )

                        # restore group in file but not in GUI yet
                        widget.createGroup(group, None)

                    cur_groups = widget.getManager().allManagers(False, True)
                    for curgroup in cur_groups:
                        if (
                            curgroup.getPersistGroupName()
                            not in self.__perspectives[name].groups.keys()
                        ):
                            widget.destroyGroup(curgroup.parent())

                managers = widget.getManager().allManagers(True, True)
                for group in self.__perspectives[name].groups().keys():
                    for mgr in managers:
                        if mgr.getPersistGroupName() == group:
                            for widget_name in self.__perspectives[name].groups[group]:
                                widget = findWidget(widget_name, [mgr])
                                if widget:
                                    pass  # OK, widget is already in the good manager!
                                else:
                                    widget = findWidget(widget_name, managers)
                                    if widget:
                                        # move dock widget in the same group as it used
                                        # to be when perspective was saved this
                                        # guarantee load/open perspectives will work
                                        # smartly
                                        MoveDockWidgetAction.move(widget, mgr)

                # internally load perspectives from QSettings
                widget.getManager().loadPerspectivesRec(
                    self.__perspectives[name].settings
                )
                # load perspective (update GUI)
                widget.getManager().openPerspectiveRec(name)
                # remove internal perspectives
                widget.getManager().removePerspectives(
                    widget.getManager().perspectiveNames()
                )

                self.openedPerspective().emit()
        else:
            raise AssertionError()

    def removePerspectives(self) -> None:
        self.__perspectives.clear()
        self.perspectivesListChanged.emit()

    def removePerspective(self, name: str) -> None:
        del self.__perspectives[name]
        self.perspectivesListChanged.emit()

    def getSettingsFileName(self, perspective: str, temp: bool) -> str:
        ext = perspective + ".tmp" if temp else perspective + ".ini"
        name = "perspectives.ini" if not perspective else f"perspectives_{ext}"

        return os.path.join(self.__perspectives_folder, name)

    def getSettingsObject(self, file_path: str) -> QSettings:
        return QSettings(file_path, QSettings.Format.IniFormat)

    def loadPerspectives(self) -> None:
        if self.__perspectives_folder:
            tempfile.mktemp(dir=self.__perspectives_folder)

            self.__perspectives.clear()

            main_settings = self.getSettingsObject(self.getSettingsFileName("", False))
            main_settings.fileName()

            size = main_settings.beginReadArray("Perspectives")

            for i in range(0, size):
                main_settings.setArrayIndex(i)
                perspective = main_settings.value("Name")

                if perspective:
                    to_load = self.getSettingsFileName(perspective, False)
                    loaded = self.getSettingsFileName(perspective, True)

                    try:
                        os.remove(loaded)
                    except FileNotFoundError:
                        pass
                    if not shutil.copy(to_load, loaded):
                        raise AssertionError()

                    self.__perspectives[perspective] = PerspectiveInfo()
                    self.__perspectives[perspective].settings = self.getSettingsObject(
                        loaded
                    )

                    # load group info:
                    main_settings.beginGroup(GROUP_PREFIX)
                    for key in main_settings.allKeys():
                        self.__perspectives[perspective].groups[key] = (
                            main_settings.value(key)
                        )
                    main_settings.endGroup()
                else:
                    raise AssertionError()

            main_settings.endArray()

        self.perspectivesListChanged.emit()

    def savePerspectives(self) -> None:
        if self.__perspectives_folder:
            main_settings = self.getSettingsObject(self.getSettingsFileName("", False))

            # Save list of perspective and group organization
            main_settings.beginWriteArray("Perspectives", len(self.__perspectives))
            for i, perspective in enumerate(self.__perspectives.keys()):
                main_settings.setArrayIndex(i)
                main_settings.setValue("Name", perspective)
                main_settings.beginGroup(GROUP_PREFIX)
                for group in self.__perspectives[perspective].groups.keys():
                    main_settings.setValue(
                        group, list(self.__perspectives[perspective].groups[group])
                    )
                main_settings.endGroup()
            main_settings.endArray()

            # Save perspectives themselves
            for perspective_name in self.__perspectives.keys():
                to_save = self.getSettingsFileName(perspective_name, False)
                settings = self.__perspectives[perspective_name].settings
                settings.sync()

                try:
                    os.remove(to_save)
                except FileNotFoundError:
                    pass
                if not shutil.copy(settings.fileName(), to_save):
                    raise AssertionError()
