from __future__ import annotations
from typing import Any, Dict, Mapping, Tuple
from common.orm.fields.PrimaryKeyField import PrimaryKeyField
from common.type_checkers.Checkers import chk_none
from ..backend._Backend import _Backend
from ..fields.Field import Field
from .Tools import NoDupOrderedDict

class MetaModel(type):
    @classmethod
    def __prepare__(metacls, name : str, bases : Tuple[type, ...], **attrs : Any) -> Mapping[str, Any]:
        return NoDupOrderedDict()

    def __new__(metacls, name : str, bases : Tuple[type, ...], attrs : NoDupOrderedDict[str, Any]):
        field_names = list()
        primary_key_field = None
        for key, value in attrs.items():
            if not isinstance(value, Field): continue
            attrs[key].name = key
            field_names.append(key)
            if isinstance(value, PrimaryKeyField):
                if primary_key_field is None:
                    primary_key_field = value
                    continue
                raise AttributeError('PrimaryKey for Model({:s}) already set to attribute({:s})'.format(
                    str(name), str(primary_key_field.name)))
        cls_obj = super().__new__(metacls, name, bases, dict(attrs))
        setattr(cls_obj, '_primary_key_field', primary_key_field)
        setattr(cls_obj, '_field_names_list', field_names)
        setattr(cls_obj, '_field_names_set', set(field_names))
        return cls_obj

class Model(metaclass=MetaModel):
    def __init__(self, parent : 'Model', primary_key : Any = None) -> None:
        if not isinstance(parent, Model):
            str_class_path = '{}.{}'.format(Model.__module__, Model.__name__)
            raise AttributeError('parent must inherit from {}'.format(str_class_path))
        self._parent = parent
        self._backend = self._parent.backend
        self._class_name = type(self).__name__
        self._backend_key = '{:s}/{:s}'.format(self.parent.backend_key, self._class_name)
        if self._primary_key_field is not None: # pylint: disable=no-member
            primary_key_field_name = self._primary_key_field.name # pylint: disable=no-member
            print('primary_key_field_name', primary_key_field_name)
            setattr(self, primary_key_field_name, primary_key) 
            self._backend_key += '[{:s}]'.format(getattr(self, primary_key_field_name))
        else:
            try:
                chk_none('primary_key', primary_key)
            except:
                msg = 'Unable to set primary_key({:s}) since no PrimaryKeyField is defined in the model'
                raise AttributeError(msg.format(str(primary_key)))

    @property
    def parent(self) -> 'Model': return self._parent

    @property
    def backend(self) -> _Backend: return self._parent.backend

    @property
    def backend_key(self) -> str: return self._backend_key

    def load(self) -> None:
        attributes = self._backend.dict_get(self._backend_key).items()
        for name in self._field_names_list: # pylint: disable=no-member
            if name not in attributes: continue
            setattr(self, name, attributes[name])

    def save(self) -> None:
        attributes : Dict[str, Any] = {
            name:repr(getattr(self, name))
            for name in self._field_names_list # pylint: disable=no-member
        }
        self._backend.dict_update(self._backend_key, attributes)

    def delete(self) -> None:
        self._backend.dict_delete(self._backend_key)

    def dump_id(self) -> Dict:
        raise NotImplementedError()

    def dump(self) -> Dict:
        raise NotImplementedError()

    def __repr__(self) -> str:
        pk_field = self._primary_key_field # pylint: disable=no-member
        pk_field_name = None if pk_field is None else pk_field.name # pylint: disable=no-member
        arguments = ', '.join(
            '{:s}={:s}{:s}'.format(
                name, repr(getattr(self, name)), '(PK)' if name == pk_field_name else '')
            for name in self._field_names_list # pylint: disable=no-member
        )
        return '{:s}({:s})'.format(self._class_name, arguments)
