import configparser
import traceback
from functools import cached_property
from pathlib import Path
from typing import Any, Callable, List, Optional, Union

import axipy
import PySide2.QtCore as QtC
from axipy._internal._decorator import _deprecated_by
from axipy._internal._shadow_instance_factory import _shadow_manager
from axipy.da.observer_manager import Observer
from axipy.gui import MapTool, view_manager
from axipy.menubar import ActionButton, Button, ToolButton
from PySide2.QtCore import QCoreApplication, QSettings
from PySide2.QtGui import QIcon

__all__: List[str] = [
    "Plugin",
]


class Plugin:
    """Вспомогательный класс для создания плагинов."""

    __plugin_dir: Path
    __plugin_id: str
    __settings: QSettings
    __legacy_settings: QSettings

    @classmethod
    def __create(cls, plugin_dir: Path, *args: Any, **kwargs: Any) -> "Plugin":
        inst = super().__new__(cls, *args, **kwargs)
        inst.__plugin_dir = plugin_dir
        inst.__plugin_id = plugin_dir.name

        # plugin settings
        settings_folder = axipy.CurrentSettings.UserPluginsSettingsFolder
        settings_folder.mkdir(parents=True, exist_ok=True)

        ini_file_path = settings_folder / f"{inst.__plugin_id}.ini"

        inst.__settings = QSettings(str(ini_file_path), QtC.QSettings.IniFormat)
        inst.__legacy_settings = QSettings("axipy_plugin", inst.__plugin_id)
        inst.__copy_legacy_settings()

        # Init is used on an instance to ensure that other attributes can be accessed in init of child classes
        inst.__init__()  # type: ignore[misc]
        if axipy.mainwindow.is_valid:
            inst.load()
        else:
            view_manager.mainwindow_activated.connect(inst.__load)
        return inst

    @cached_property
    def __installed_modules_folder(self) -> Path:
        return axipy.CurrentSettings.UserPluginsFolder

    def __copy_legacy_settings(self) -> None:
        legacy_settings_ini_path = axipy.CurrentSettings.UserPluginsSettingsFolder / "__legacy_settings.ini"
        if not legacy_settings_ini_path.exists():
            open(legacy_settings_ini_path, "a").close()
        config = configparser.ConfigParser()
        section = "axipy_plugins_legacy_settings_copied"
        option = self.__plugin_id
        config.read(legacy_settings_ini_path, encoding="UTF-8")
        is_copied = config.getboolean(section, option, fallback=False)
        if is_copied:
            return None

        try:
            self.__copy_legacy_settings_impl()
        except Exception:
            traceback.print_exc()
        finally:
            if section not in config:
                config[section] = {}
            config[section][option] = str(True)
            with open(legacy_settings_ini_path, mode="w", encoding="UTF-8") as io:
                config.write(io)

    def __copy_legacy_settings_impl(self) -> None:
        all_keys = self.__settings.allKeys()
        all_legacy_keys = self.__legacy_settings.allKeys()
        for legacy_key in all_legacy_keys:
            if legacy_key not in all_keys:
                try:
                    legacy_value = self.__legacy_settings.value(legacy_key)
                except (Exception,):
                    continue
                self.__settings.setValue(legacy_key, legacy_value)

    def tr(self, text: str) -> str:
        """
        Ищет перевод строки в загруженных файлах перевода.

        Args:
            text:
                Строка для перевода.

        Returns:
            Перевод стоки, если строка найдена. Иначе — сама переданная строка.

        Пример::

            button_name = self.tr("My button")
        """
        return QCoreApplication.translate(self.__plugin_id, text)

    @property
    def language(self) -> str:
        """
        Значение языка, с которым запущено приложение.

        Например, ``'ru'``.
        """
        return _shadow_manager.core.translationLanguage()

    @property
    def settings(self) -> QSettings:
        """
        Настройки плагина.

        Позволяет сохранять и загружать параметры.

        See also:
            Подробнее в документации на класс :class:`PySide2.QtCore.QSettings`.
        """
        return self.__settings

    def __unload(self) -> None:
        self.unload()

    def __load(self) -> None:
        view_manager.mainwindow_activated.disconnect(self.__load)
        self.load()

    def load(self) -> None:
        """Переопределите этот метод для задания логики загрузки плагина."""
        pass

    def unload(self) -> None:
        """Переопределите этот метод для очистки ресурсов при выгрузке плагина."""
        pass

    def create_tool(
        self,
        title: str,
        on_click: Union[Callable[[], MapTool], MapTool],
        icon: Union[str, QIcon] = "",
        enable_on: Optional[Union[str, Observer]] = None,
        tooltip: Optional[str] = None,
        doc_file: Optional[str] = None,
    ) -> ToolButton:
        """
        Создает кнопку с инструментом.

        Args:
            title: Текст.
            on_click: Класс инструмента или же функция, возвращающая экземпляр инструмента.
            icon: Иконка. Может быть путем к файлу или адресом ресурса.
            enable_on: Идентификатор наблюдателя для определения доступности кнопки.
            tooltip: Строка с дополнительной короткой информацией по данному действию.
            doc_file: Относительная ссылка на файл документации. Расположение рассматривается по отношению к
              каталогу `documentation`.
        Return:
            Кнопка с инструментом.

        Note:
            То же, что и :class:`ToolButton`, но дополнительно
            делает идентификатор кнопки уникальным для данного плагина.

        Пример::

            def create_tool() -> MapTool:
                return MapTool()

            class ExampleTool(Plugin):
                def __init__(self) -> None:
                    tool = self.create_tool(
                        title="Пример инструмента",
                        icon="://icons/share/32px/run3.png",
                        on_click=create_tool
                    )

        """
        result = ToolButton(title, on_click, icon, enable_on, tooltip)
        if doc_file is not None:
            result.action.setWhatsThis(self.__full_doc_file(doc_file))
        self.__disambiguate(result)
        return result

    def __full_doc_file(self, filename: str) -> str:
        doc_path = str(self.plugin_dir / "documentation" / filename)
        return "file:///{}".format(doc_path.replace("\\", "/"))

    def create_action(
        self,
        title: str,
        on_click: Callable[[], Any],
        icon: Union[str, QIcon] = "",
        enable_on: Optional[Observer] = None,
        tooltip: Optional[str] = None,
        doc_file: Optional[str] = None,
    ) -> ActionButton:
        """
        Создает кнопку с действием.

        Args:
            title: Текст.
            on_click: Действие на нажатие.
            icon: Иконка. Может быть путем к файлу или адресом ресурса.
            enable_on: Идентификатор наблюдателя для определения доступности кнопки.
            tooltip: Строка с дополнительной короткой информацией по данному действию.
            doc_file: Относительная ссылка на файл документации. Расположение рассматривается по отношению к
              каталогу `documentation`.
        Return:
            Кнопка с действием.

        Note:
            То же, что и :class:`ActionButton`, но дополнительно
            делает идентификатор кнопки уникальным для данного плагина.
        """
        result = ActionButton(title, on_click, icon, enable_on, tooltip=tooltip)
        if doc_file is not None:
            result.action.setWhatsThis(self.__full_doc_file(doc_file))
        self.__disambiguate(result)
        return result

    def __disambiguate(self, button: Button) -> None:

        def make_action_id(title: str) -> str:
            return self.__plugin_id + "_" + Button._title_to_id(title)

        button.action.setObjectName(make_action_id(button.action.text()))

    @property
    def plugin_dir(self) -> Path:
        """Возвращает путь к папке плагина."""
        return self.__plugin_dir

    def get_plugin_data_dir(self) -> Path:
        """Возвращает каталог, в котором находятся изменяемые данные плагина."""
        path = axipy.CurrentSettings.UserPluginsDataFolder / self.__plugin_id
        path.mkdir(parents=True, exist_ok=True)
        return path


def _apply_deprecated() -> None:
    @property  # type: ignore[misc]
    @_deprecated_by("axipy.CurrentSettings.Language")
    def language(_self: Plugin) -> str:
        """
        Warning:
            .. deprecated:: 6.3.0
        """
        return _shadow_manager.core.translationLanguage()

    setattr(Plugin, "language", language)


_apply_deprecated()
