# -*- coding: utf-8 -*-

"""
Implement Client Side Encryption Attribute for Unicode, Binary, Numbers and
Json data.
"""

import json
import typing
from pynamodb.attributes import BinaryAttribute

from ..cipher import str_to_key, BaseCipher, AesEcbCipher, AesCtrCipher


class SymmetricEncryptedAttribute(object):
    """
    Symmetric Encrypted Attribute
    """
    encryption_key = None  # type: str
    determinative = None  # type: str

    _key = None  # type: bytes

    @property
    def key(self) -> bytes:
        if self._key is None:
            self._key = str_to_key(self.encryption_key)
        return self._key

    _cipher = None  # type: BaseCipher

    @property
    def cipher(self) -> BaseCipher:
        """
        :rtype: SymmtricCipher
        """
        if self._cipher is None:
            if self.determinative is True:
                self._cipher = AesEcbCipher(key=self.key)
            elif self.determinative is False:
                self._cipher = AesCtrCipher(key=self.key)
            else:  # pragma: no cover
                raise ValueError
        return self._cipher


class EncryptedUnicodeAttribute(BinaryAttribute, SymmetricEncryptedAttribute):
    """
    Encrypted Unicode Attribute.
    """
    def serialize(self, value: str) -> bytes:
        return self.cipher.encrypt(value.encode("utf-8"))

    def deserialize(self, value: bytes) -> str:
        return self.cipher.decrypt(value).decode("utf-8")


class EncryptedBinaryAttribute(BinaryAttribute, SymmetricEncryptedAttribute):
    def serialize(self, value: bytes) -> bytes:
        return self.cipher.encrypt(value)

    def deserialize(self, value: bytes) -> bytes:
        return self.cipher.decrypt(value)


class EncryptedNumberAttribute(BinaryAttribute, SymmetricEncryptedAttribute):
    """
    Encrypted Number Attribute.
    """
    def serialize(self, value: typing.Union[int, float]) -> bytes:
        return self.cipher.encrypt(json.dumps(value).encode("ascii"))

    def deserialize(self, value: bytes) -> typing.Union[int, float]:
        return json.loads(self.cipher.decrypt(value).decode("ascii"))


class EncryptedJsonAttribute(EncryptedNumberAttribute):
    """
    Encrypted JSON data Attribute.
    """
    pass
