import base64

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

from django.conf import settings
from django.utils.encoding import force_bytes, force_str


class BaseCipher:
    def __init__(self, key, iv) -> None:
        self.key = key
        self.iv = iv

    def encrypt(self, text) -> str:
        return text

    def decrypt(self, encrypted) -> str:
        return encrypted


class ECBCipher(BaseCipher):

    def encrypt(self, text):
        encryptor = Cipher(algorithms.AES(self.key), modes.ECB(), default_backend()).encryptor()
        padder = padding.PKCS7(algorithms.AES(self.key).block_size).padder()
        padded_data = padder.update(force_bytes(text)) + padder.finalize()
        encrypted_text = encryptor.update(padded_data) + encryptor.finalize()
        return force_str(base64.urlsafe_b64encode(encrypted_text))

    def decrypt(self, encrypted):
        decryptor = Cipher(algorithms.AES(self.key), modes.ECB(), default_backend()).decryptor()
        padder = padding.PKCS7(algorithms.AES(self.key).block_size).unpadder()
        decrypted_text = decryptor.update(base64.urlsafe_b64decode(encrypted))
        unpadded_text = padder.update(decrypted_text) + padder.finalize()
        return force_str(unpadded_text)

class CBCCipher(BaseCipher):

    def encrypt(self, text) -> str:
        encryptor = Cipher(algorithms.AES(self.key), modes.CBC(self.iv), default_backend()).encryptor()
        padder = padding.PKCS7(algorithms.AES(self.key).block_size).padder()
        padded_data = padder.update(force_bytes(text)) + padder.finalize()
        encrypted_text = encryptor.update(padded_data) + encryptor.finalize()
        return force_str(base64.urlsafe_b64encode(encrypted_text))

    def decrypt(self, encrypted) -> str:
        decryptor = Cipher(algorithms.AES(self.key), modes.CBC(self.iv), default_backend()).decryptor()
        padder = padding.PKCS7(algorithms.AES(self.key).block_size).unpadder()
        decrypted_text = decryptor.update(base64.urlsafe_b64decode(encrypted))
        unpadded_text = padder.update(decrypted_text) + padder.finalize()
        return force_str(unpadded_text)

class Crypto:

    def __init__(self, key=None, mode=None, iv=None):
        if key is None:
            key = getattr(settings, "MIRAGE_SECRET_KEY", None) or getattr(settings, "SECRET_KEY")
        assert len(key) >= 32, "mirage key length must more than 32!"
        key = base64.urlsafe_b64encode(force_bytes(key))[:32]
        if mode is None:
            mode = getattr(settings, "MIRAGE_CIPHER_MODE", "ECB")
        if iv is None:
            iv=getattr(settings, "MIRAGE_CIPHER_IV", "1234567890abcdef")
        self.cipher = eval(f"{mode}Cipher")(key=key, iv=force_bytes(iv))

    def encrypt(self, text):
        if text is None:
            return None
        try:
            self.cipher.decrypt(text)
            return text
        except Exception:
            return self.cipher.encrypt(text)

    def decrypt(self, encrypted):
        if encrypted is None:
            return None
        try:
            return self.cipher.decrypt(encrypted)
        except Exception:
            return encrypted
