// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <QByteArray>
#include <QString>

class QDataStream;

#ifdef WITH_QCA_ENCRYPTION
#include <QtCrypto>
#endif

namespace QCA {
    class SecureArray;
}

namespace Encryption {

#ifdef WITH_QCA_ENCRYPTION
using SecureArray = QCA::SecureArray;
using Salt = QCA::InitializationVector;
#else
using SecureArray = QByteArray;
using Salt = QByteArray;
#endif

/// Wrap a value and clear it on destruction.
template <typename T>
class Cleared {
public:
    Cleared(T &&ba): m_value(std::move(ba)) {}
    ~Cleared() { m_value.fill('\0'); }
    bool isEmpty() const { return m_value.isEmpty(); }
    qsizetype size() const { return m_value.size(); }
    const T &value() const { return m_value; }
private:
    T m_value;
};

/**
 * Wrapper class for encryption key using envelope encryption (key wrapping).
 *
 * Architecture:
 * - DEK (Data Encryption Key): Random 256-bit key that encrypts all tab data
 * - KEK (Key Encryption Key): Derived from user password using PBKDF2 with random salt
 * - The DEK is encrypted by the KEK and stored on disk (envelope encryption)
 *
 * Benefits:
 * - Password changes only require re-encrypting the small DEK, not all data
 * - Each user gets a unique random salt for password derivation
 * - DEK is cryptographically random, stronger than password-derived keys
 *
 * When QCA encryption is not available, this class acts as a stub
 * that always returns invalid/empty results.
 */
class EncryptionKey {
public:
    EncryptionKey() = default;
    EncryptionKey(const EncryptionKey &) = default;
    EncryptionKey &operator=(const EncryptionKey &) = default;
    EncryptionKey(EncryptionKey &&) = default;
    EncryptionKey &operator=(EncryptionKey &&) = default;

    /// Derive from password.
    explicit EncryptionKey(const SecureArray &password);

    /// Unwrap from DEK.
    EncryptionKey(
        const SecureArray &password,
        const SecureArray &wrappedDEK,
        const Salt &kekSalt);

    ~EncryptionKey();

    /**
     * Generate a new random Data Encryption Key (DEK).
     * Should be called once during initial setup.
     * @return true if DEK generation succeeded (always false without QCA)
     */
    bool generateRandomDEK();

    /**
     * Derive KEK from password and unwrap (decrypt) the DEK.
     * @param password Password to derive KEK from
     * @param wrappedDEK Encrypted DEK (previously encrypted with a KEK)
     * @param kekSalt Salt for KEK derivation
     * @return true if KEK derivation and DEK unwrapping succeeded
     */
    bool unwrapDEK(const SecureArray &password, const SecureArray &wrappedDEK, const Salt &kekSalt);

    /**
     * Wrap (encrypt) the DEK using a KEK derived from password.
     * Used when setting or changing passwords.
     * @param password Password to derive KEK from
     * @param kekSalt Salt for KEK derivation
     * @return Encrypted DEK, or empty array on failure
     */
    SecureArray wrapDEK(const SecureArray &password, const Salt &kekSalt) const;

    /**
     * Check if DEK is valid and ready to use.
     * @return true if valid (always false without QCA)
     */
    bool isValid() const;

    /**
     * Clear the keys from memory.
     */
    void clear();

#ifdef WITH_QCA_ENCRYPTION
    // Internal access for encryption/decryption operations
    // Returns the DEK for data encryption/decryption
    const QCA::SymmetricKey &symmetricKey() const { return m_dek; }

    /**
     * Export DEK as raw bytes for serialization.
     * WARNING: This exposes the raw encryption key - use only for secure serialization.
     * @return DEK as byte array, or empty if invalid
     */
    QByteArray exportDEK() const { return m_dek.toByteArray(); }

    /**
     * Import DEK from raw bytes (for deserialization).
     * @param dekBytes Raw DEK bytes
     * @return true if import succeeded
     */
    bool importDEK(const QByteArray &dekBytes)
    {
        m_dek = QCA::SymmetricKey(dekBytes);
        return !m_dek.isEmpty();
    }

private:
    QCA::SymmetricKey m_dek;  // Data Encryption Key (used for actual data encryption)
#endif
};

/**
 * Initialize encryption system.
 * Returns true if encryption is available and initialized successfully.
 * Always returns false when QCA is not available.
 */
bool initialize();

/**
 * Encrypt data using AES-256.
 * @param data Data to encrypt
 * @param key Encryption key to use
 * @return Encrypted data, or empty array on failure (always empty without QCA)
 */
QByteArray encrypt(const SecureArray &data, const EncryptionKey &key);

/**
 * Decrypt data that was encrypted with encrypt().
 * @param encryptedData Data to decrypt
 * @param key Encryption key to use
 * @return Decrypted data, or empty array on failure (always empty without QCA)
 */
QByteArray decrypt(const QByteArray &encryptedData, const EncryptionKey &key);

/** Save encryption key safely. */
EncryptionKey saveKey(const EncryptionKey &key, const SecureArray &newPassword);

/**
 * Load wrapped DEK from secure storage.
 * @return Wrapped DEK, or empty array if not found or on error
 */
SecureArray loadWrappedDEK();

/**
 * Load KEK salt from secure storage.
 * @return KEK salt, or empty array if not found or on error
 */
Salt loadKEKSalt();

/**
 * Remove all encryption key files from disk.
 * This should only be called after successfully disabling encryption for all tabs.
 *
 * Removes:
 * - wrapped_dek.dat (wrapped Data Encryption Key)
 * - kek_salt.dat (Key Encryption Key salt)
 */
void removeEncryptionKeys();

} // namespace Encryption
