"""
Кастомные поля Django с шифрованием для безопасного хранения credentials.

Использует Fernet (AES-128-CBC) из библиотеки cryptography.
"""
from django.db import models
from django.conf import settings
from cryptography.fernet import Fernet, InvalidToken


class EncryptedCharField(models.CharField):
    """
    CharField с прозрачным шифрованием/дешифрованием.

    Данные шифруются при сохранении в БД и дешифруются при чтении.
    В БД хранится зашифрованная строка (base64).

    Требует ENCRYPTION_KEY в settings.py:
        from cryptography.fernet import Fernet
        ENCRYPTION_KEY = Fernet.generate_key()  # сгенерировать один раз!

    Пример использования:
        api_token = EncryptedCharField(max_length=500, blank=True)
    """

    description = "Encrypted CharField using Fernet"

    def __init__(self, *args, **kwargs):
        # Сохраняем оригинальный max_length для deconstruct()
        self._original_max_length = kwargs.get('max_length')
        # Зашифрованные данные длиннее исходных, увеличиваем max_length
        if 'max_length' in kwargs:
            # Fernet добавляет ~100 байт overhead
            kwargs['max_length'] = max(kwargs['max_length'] * 2, 500)
        super().__init__(*args, **kwargs)

    def deconstruct(self):
        """Возвращаем оригинальные параметры для миграций"""
        name, path, args, kwargs = super().deconstruct()
        # Восстанавливаем оригинальный max_length
        if self._original_max_length is not None:
            kwargs['max_length'] = self._original_max_length
        return name, path, args, kwargs

    def _get_fernet(self):
        """Получить инстанс Fernet с ключом из settings"""
        key = getattr(settings, 'ENCRYPTION_KEY', None)
        if not key:
            raise ValueError(
                "ENCRYPTION_KEY не найден в settings. "
                "Сгенерируйте ключ: from cryptography.fernet import Fernet; Fernet.generate_key()"
            )
        # Ключ может быть строкой или bytes
        if isinstance(key, str):
            key = key.encode()
        return Fernet(key)

    def get_prep_value(self, value):
        """Шифрование перед сохранением в БД"""
        value = super().get_prep_value(value)
        if value is None or value == '':
            return value
        try:
            f = self._get_fernet()
            encrypted = f.encrypt(value.encode('utf-8'))
            return encrypted.decode('utf-8')
        except Exception as e:
            raise ValueError(f"Ошибка шифрования: {e}")

    def from_db_value(self, value, expression, connection):
        """Дешифрование при чтении из БД"""
        if value is None or value == '':
            return value
        try:
            f = self._get_fernet()
            decrypted = f.decrypt(value.encode('utf-8'))
            return decrypted.decode('utf-8')
        except InvalidToken:
            # Данные не зашифрованы или ключ изменился
            # Возвращаем как есть (для миграции старых данных)
            return value
        except Exception:
            return value

    def to_python(self, value):
        """Преобразование в Python-объект (не дешифруем, т.к. это для форм)"""
        return super().to_python(value)


class EncryptedTextField(models.TextField):
    """
    TextField с шифрованием для больших данных (например JSON credentials).
    """

    description = "Encrypted TextField using Fernet"

    def _get_fernet(self):
        key = getattr(settings, 'ENCRYPTION_KEY', None)
        if not key:
            raise ValueError("ENCRYPTION_KEY не найден в settings.")
        if isinstance(key, str):
            key = key.encode()
        return Fernet(key)

    def get_prep_value(self, value):
        value = super().get_prep_value(value)
        if value is None or value == '':
            return value
        try:
            f = self._get_fernet()
            encrypted = f.encrypt(value.encode('utf-8'))
            return encrypted.decode('utf-8')
        except Exception as e:
            raise ValueError(f"Ошибка шифрования: {e}")

    def from_db_value(self, value, expression, connection):
        if value is None or value == '':
            return value
        try:
            f = self._get_fernet()
            decrypted = f.decrypt(value.encode('utf-8'))
            return decrypted.decode('utf-8')
        except InvalidToken:
            return value
        except Exception:
            return value
