"""
Do not move or rename this module! It's name and position used in settings saving mechanism!
"""
import copy
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, List, Tuple, TYPE_CHECKING, overload, Union

import PySide2.QtCore as QtC
import axipy as axp

from .utils import StrEnumMeta

if TYPE_CHECKING:
    from .__init__ import StyleCatalogs


class StyleKey(StrEnumMeta):  # Do not move or rename this class!
    PEN = "pen"
    BRUSH = "brush"
    SYMBOL = "symbol"


@dataclass
class StyleSet:  # Do not move or rename this class! It's name and position used in settings saving mechanism!
    """
    Набор стилей.
    """
    name: str
    items: Dict[StyleKey, Path]
    is_active: bool = False


class ListModel(QtC.QAbstractListModel):

    def __init__(self, plugin: 'StyleCatalogs') -> None:
        super().__init__()
        self.plugin = plugin
        self.settings_manager = plugin.settings_manager

        if axp.CurrentSettings.Language == axp.AxiomaLanguage.ru:
            self.__default_style_set_name: str = "По умолчанию"
        else:
            self.__default_style_set_name: str = "Default"

        self.default_style_set: StyleSet = StyleSet(self.__default_style_set_name, {
            StyleKey.PEN: axp.DefaultSettings.PenCatalog,
            StyleKey.BRUSH: axp.DefaultSettings.BrushCatalog,
            StyleKey.SYMBOL: axp.DefaultSettings.SymbolCatalog,
        })

        # Модель
        self.__model: List[StyleSet] = [self.default_style_set]
        # Инициализация модели из настроек.
        for style_set in self.settings_manager.get_style_sets():
            result = self.is_unique_set_name(style_set)
            if result:
                self.__model.append(style_set)
            else:
                self.__model[self.__model.index(style_set)] = style_set

        result = self.check_if_active_exists_and_single(self.__model)
        if not result:
            self.set_active_style_set(0)

        self._check_if_model_is_valid()

    def _check_if_model_is_valid(self) -> None:
        sets_names = self.get_sets_names()
        if len(sets_names) != len(set(sets_names)):
            raise RuntimeError("Model has duplicate set names.")

        for style_set in self.__model:
            if not isinstance(style_set, StyleSet):
                raise TypeError(style_set)
            if not isinstance(style_set.name, str):
                raise TypeError(style_set.name)

            if not isinstance(style_set.items, dict):
                raise TypeError(style_set.items)
            for k, v in style_set.items.items():
                if not isinstance(k, str):
                    raise TypeError(k)
                if not isinstance(v, Path):
                    raise TypeError(v)

            if not isinstance(style_set.is_active, bool):
                raise TypeError(style_set.is_active)

        result = self.check_if_active_exists_and_single(self.__model)
        if not result:
            raise RuntimeError("Model has none or multiple active style sets.")

    def get_inner_model_copy(self) -> List[StyleSet]:
        return copy.deepcopy(self.__model)

    def get_inner_model_copy_without_default(self) -> List[StyleSet]:
        inner_model = copy.deepcopy(self.__model)
        if inner_model[0].name != self.default_style_set_name:
            raise RuntimeError("First element is not default.")
        inner_model.pop(0)
        return inner_model

    def reset_inner_model(self, model: List[StyleSet]) -> None:
        self.__model = model
        self._check_if_model_is_valid()

    def get_style_set(self, row: int) -> StyleSet:
        return self.__model[row]

    def get_style_set_copy(self, row: int) -> StyleSet:
        return copy.deepcopy(self.__model[row])

    @property
    def default_style_set_name(self) -> str:
        return self.__default_style_set_name

    @overload
    def set_active_style_set(self, arg: int) -> None:
        ...

    @overload
    def set_active_style_set(self, arg: str) -> None:
        ...

    def set_active_style_set(self, arg):
        if isinstance(arg, int):
            i = arg
        elif isinstance(arg, str):
            i = self.get_sets_names().index(arg)
        else:
            raise TypeError(arg)

        for index, style_set in enumerate(self.__model):
            if index == i:
                style_set.is_active = True
            else:
                style_set.is_active = False

    def get_active_style_set_index(self) -> int:
        index = 0
        for i, style_set in enumerate(self.__model):
            if style_set.is_active:
                index = i
                break
        return index

    @staticmethod
    def check_if_active_exists_and_single(style_sets: List[StyleSet]) -> bool:
        active_style_set = None
        count = 0
        for style_set in style_sets:
            if style_set.is_active:
                active_style_set = style_set
                count += 1

        if active_style_set is None or count != 1:
            return False
        else:
            return True

    def get_sets_names(self) -> Tuple[str, ...]:
        return tuple((elem.name for elem in self.__model))

    def is_unique_set_name(self, style_set: Union[StyleSet, str]) -> bool:
        if isinstance(style_set, StyleSet):
            name = style_set.name
        elif isinstance(style_set, str):
            name = style_set
        else:
            raise TypeError(style_set)

        if name in self.get_sets_names():
            return False
        else:
            return True

    def rowCount(self, parent: QtC.QModelIndex = None) -> int:
        if parent is None:
            parent = QtC.QModelIndex()

        if parent.isValid():
            return 0

        return len(self.__model)

    def insertRow(self, row: int, parent: QtC.QModelIndex = None, style_set: StyleSet = None) -> bool:
        if parent is None:
            parent = QtC.QModelIndex()

        return self.insertRows(row, 1, parent, [style_set])

    def insertRows(self, row: int, count: int, parent: QtC.QModelIndex = None,
                   style_sets: List[StyleSet] = None) -> bool:
        if row == 0:
            raise RuntimeError("Can't insert at 0 index.")
        # if count != 1:
        #     raise RuntimeError("Can only insert 1 row at a time.")
        if parent is None:
            parent = QtC.QModelIndex()
        if style_sets is None:
            raise RuntimeError("Must provide a style set on insert.")

        last = row + count - 1
        self.beginInsertRows(parent, row, last)
        if any((self.is_unique_set_name(style_set) is False for style_set in style_sets)):
            raise RuntimeError("New sets names must be unique.")
        self.__model[row:row] = style_sets
        self.endInsertRows()
        return True

    def removeRows(self, row: int, count: int, parent: QtC.QModelIndex = None) -> bool:
        if row == 0:
            raise RuntimeError("Can't remove at 0 index.")
        if count != 1:
            raise RuntimeError("Can only remove 1 row at a time.")
        if parent is None:
            parent = QtC.QModelIndex()

        last = row + count - 1
        self.beginRemoveRows(parent, row, last)
        style_set = self.__model.pop(row)
        if style_set.is_active:
            self.set_active_style_set(0)
        self.endRemoveRows()
        return True

    # noinspection PyPep8Naming
    def moveRows(
            self,
            sourceParent: QtC.QModelIndex,
            sourceRow: int,
            count: int,
            destinationParent: QtC.QModelIndex,
            destinationChild: int
    ) -> bool:
        if count != 1:
            raise RuntimeError("Can only move 1 row at a time.")
        if sourceRow == 0 or destinationChild == 0:
            raise RuntimeError("Can't move at or to 0 index.")

        if sourceRow < destinationChild:
            if (destinationChild - sourceRow) <= 1:
                raise RuntimeError("Invalid move.")
            destination_child_fixed = destinationChild - 1
        else:
            destination_child_fixed = destinationChild

        source_first: int = sourceRow
        source_last: int = sourceRow + count - 1
        # logging.debug(f"{sourceParent, source_first, source_last, destinationParent, destinationChild}")
        # logging.debug(f"{self.rowCount()=}")
        self.beginMoveRows(sourceParent, source_first, source_last, destinationParent, destinationChild)
        self.__model[sourceRow], self.__model[destination_child_fixed] = (
            self.__model[destination_child_fixed], self.__model[sourceRow])
        self.endMoveRows()
        return True

    def data(self, index: QtC.QModelIndex, role: QtC.Qt.ItemDataRole = QtC.Qt.DisplayRole) -> Any:
        row, col = index.row(), index.column()

        # if role == QtC.Qt.DisplayRole or role == QtC.Qt.EditRole:
        if role == QtC.Qt.DisplayRole:
            style_set: StyleSet = self.__model[row]
            return style_set.name

    # def flags(self, index: QtC.QModelIndex) -> Union[QtC.Qt.ItemFlag, QtC.Qt.ItemFlags]:
    #     if not index.isValid():
    #         return QtC.Qt.ItemIsEnabled
    #
    #     row, col = index.row(), index.column()
    #     if row == 0:
    #         return QtC.QAbstractListModel.flags(self, index)
    #     else:
    #         return QtC.QAbstractListModel.flags(self, index) | QtC.Qt.ItemIsEditable

    # def setData(self, index: QtC.QModelIndex, value: Any, role: QtC.Qt.ItemDataRole = QtC.Qt.EditRole) -> bool:
    #     if not self.checkIndex(index):
    #         return False
    #
    #     row, col = index.row(), index.column()
    #     if row == 0:
    #         raise RuntimeError("Can't set data on 0 index.")
    #
    #     if role == QtC.Qt.EditRole:
    #         result = self.is_unique_set_name(value)
    #         if result:
    #             self.__model[row].name = value
    #         else:
    #             raise RuntimeError("Can't rename, name already exists.")
    #         return True
    #
    #     return False
