from typing import TYPE_CHECKING, List, NamedTuple, Optional

from axipy._internal._shadow_instance_factory import _shadow_manager
from axipy._internal._utils import _AxiRepr, _AxiReprMeta, _Singleton
from axipy.cpp_core_core import ShadowForeignKeyManager as _ShadowForeignKeyManager

if TYPE_CHECKING:
    from axipy.da import Source

__all__: List[str] = [
    "ForeignKeyManager",
    "ForeignRelationFields",
    "foreignkey_manager",
]


class ForeignRelationFields(NamedTuple):
    """
    Описание полей соответствия таблицы и справочника.

    .. seealso::
        Менеджер связей :class:`ForeignKeyManager`
    """

    attributeKey: str
    """Наименование атрибута связи основной таблицы."""
    attributeKeyForeign: str
    """Наименование атрибута связи таблицы справочника."""
    attributeNameForeign: str
    """Наименование атрибута таблицы справочника, которое будет использоваться для
    отображения."""


class ForeignKeyManager(_Singleton, _AxiRepr, metaclass=_AxiReprMeta):
    """
    Менеджер связей, который позволяет организовывать связи между таблицами по принципу
    справочников.

    Вместо кода при настройке такого взаимодействия будет отображаться наименование из соответствующего справочника.
    Принцип работы основан на предварительной регистрации взаимосвязи между открываемыми позднее таблицами.
    Т.е. регистрация должна производится до момента открытия основной таблицы.
    На справочники это условие не распространяется.
    Если регистрация корректна, то в момент открытия основной таблицы будет произведена проверка на предмет наличия уже
    открытых таблиц со справочниками. Если таковые отсутствуют, производится их открытие.
    Правильность регистрации проверяется на этапе открытия основной таблицы.

    Результатом будет подмена в таблице просмотра и редакторе свойств значения поля с кодом на значение описания из
    справочника. Так-же при редактировании будет использован выпадающий список со значениями из справочника.
    Внешних ключей для одной таблицы может быть несколько. В этом случае регистрация ключей производится последовательно
    до регистрации взаимосвязи в системе.

    Note:
        Создание :class:`axipy.ForeignKeyManager` не требуется,
        используйте объект :attr:`axipy.foreign_key_manager`.

    Note:
        На данный момент реализовано только для локальных данных.

    .. seealso::
        :ref:`ref-dictionary`


    .. literalinclude:: /../../tests/doc_examples/da/test_example_foreign.py
        :caption: Пример использования
        :pyobject: test_run_example_foreign
        :lines: 2,3,6-32
        :dedent: 4

    .. literalinclude:: /../../tests/doc_examples/da/test_example_foreign.py
        :caption: Альтернативное описание источников через :class:`dict`
        :pyobject: test_run_example_foreign
        :lines: 33-
        :dedent: 4
    """

    def __init__(self) -> None:
        self._inner_shadow: Optional[_ShadowForeignKeyManager] = None

    @property
    def _shadow(self) -> _ShadowForeignKeyManager:
        if self._inner_shadow is None:
            self._inner_shadow = _ShadowForeignKeyManager(_shadow_manager.core)
        return self._inner_shadow

    def register(self, base: "Source", foreign: "Source", relation: ForeignRelationFields) -> None:
        """
        Производит регистрацию взаимосвязи между двумя таблицами.

        Args:
            base: Описание основной таблицы.
            foreign: Описание таблицы справочника.
            relation: Атрибуты, участвующие в формировании внешнего ключа.
        """
        r = {
            "key": relation.attributeKey,
            "fkey": relation.attributeKeyForeign,
            "fname": relation.attributeNameForeign,
        }
        self._shadow.registerRelation(base, foreign, r)

    def unregister(self, base: "Source", foreign: "Source") -> None:
        """
        Убирает регистрацию взаимосвязи между двумя таблицами.

        Args:
            base: Описание основной таблицы.
            foreign: Описание таблицы справочника.
        """
        self._shadow.unregisterRelation(base, foreign)

    def list_registered(self) -> List[dict]:
        """Список в удобочитаемом виде существующих регистраций."""
        return self._shadow.descriptRegistered()

    def __len__(self) -> int:
        return len(self.list_registered())


foreignkey_manager = ForeignKeyManager()
