from __future__ import annotations
import copy
from typing import Any, Callable, Dict, List, Set
from ..backend._Backend import _Backend
from ._Object import _Object
from .Tools import format_key

class Attributes:
    def __init__(
        self, parent : _Object, key_pattern : str, validators : Dict[str, Callable[[Any], bool]],
        transcoders : Dict[str, Dict[Any, Callable[[Any], Any]]] = {}):

        if not issubclass(parent, _Object):
            str_class_path = '{}.{}'.format(_Object.__module__, _Object.__name__)
            raise AttributeError('parent must inherit from {}'.format(str_class_path))
        if (not isinstance(key_pattern, str)) or (len(key_pattern) == 0):
            raise AttributeError('key_pattern must be a non-empty instance of str')
        if not isinstance(validators, dict):
            raise AttributeError('validators must be an instance of dict')
        # TODO: implement validation of entries in validators
        if not isinstance(transcoders, dict):
            raise AttributeError('transcoders must be an instance of dict')
        # TODO: implement validation of entries in transcoders

        self._parent = parent
        self._backend : _Backend = self._parent.backend
        self._key = format_key(key_pattern, self._parent)
        self._validators = validators
        self._transcoders = transcoders

    def validate(self, update_attributes : Dict[str, Any], remove_attributes : Set[str], attribute_name : str) -> None:
        remove_attributes.discard(attribute_name)
        value = update_attributes.pop(attribute_name, None)
        if value is None: return
        validator = self._validators.get(attribute_name)
        if validator is None: return
        if not validator(value): raise AttributeError('{} is invalid'.format(attribute_name))

    def transcode(self, attribute_name : str, attribute_value : Any) -> Any:
        transcoder_set = self._transcoders.get(attribute_name, {})
        transcoder = transcoder_set.get(type(attribute_value))
        return attribute_value if transcoder is None else transcoder(attribute_value)

    def get(self, attributes : List[str] = []) -> Dict[str, Any]:
        return {
            k:self.transcode(k, v)
            for k,v in self._backend.dict_get(self._key, fields=attributes).items()
        }

    def update(self, update_attributes : Dict[str, Any] = {}, remove_attributes : List[str] = []) -> 'Attributes':
        remove_attributes = set(remove_attributes)
        copy_update_attributes = copy.deepcopy(update_attributes)
        copy_remove_attributes = copy.deepcopy(remove_attributes)

        for attribute_name in self._validators.keys():
            self.validate(copy_update_attributes, copy_remove_attributes, attribute_name)
            attribute_value = update_attributes.get(attribute_name)
            if attribute_value is None: continue
            update_attributes[attribute_name] = self.transcode(attribute_name, attribute_value)

        if len(copy_update_attributes) > 0:
            raise AttributeError('Unexpected update_attributes: {}'.format(str(copy_update_attributes)))

        if len(copy_remove_attributes) > 0:
            raise AttributeError('Unexpected remove_attributes: {}'.format(str(copy_remove_attributes)))

        self._backend.dict_update(self._key, update_attributes, remove_attributes)
        return self

    def delete(self, attributes : List[str] = []) -> None:
        self._backend.dict_delete(self._key, attributes)
