using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Configuration;

namespace Codex.Dal.Services;

/// <summary>
/// AES-256 encryption service with random IV generation.
/// Thread-safe implementation for encrypting sensitive data like API keys.
/// </summary>
public class AesEncryptionService : IEncryptionService
{
    private readonly byte[] _key;

    /// <summary>
    /// Initializes the encryption service with a key from configuration.
    /// </summary>
    /// <param name="configuration">Application configuration</param>
    /// <exception cref="InvalidOperationException">Thrown when encryption key is missing or invalid</exception>
    public AesEncryptionService(IConfiguration configuration)
    {
        var keyBase64 = configuration["Encryption:Key"];

        if (string.IsNullOrWhiteSpace(keyBase64))
        {
            throw new InvalidOperationException(
                "Encryption key is not configured. Add 'Encryption:Key' to appsettings.json. " +
                "Generate a key with: openssl rand -base64 32");
        }

        try
        {
            _key = Convert.FromBase64String(keyBase64);
        }
        catch (FormatException)
        {
            throw new InvalidOperationException(
                "Encryption key is not a valid Base64 string. " +
                "Generate a key with: openssl rand -base64 32");
        }

        if (_key.Length != 32)
        {
            throw new InvalidOperationException(
                $"Encryption key must be 32 bytes (256 bits) for AES-256. Current length: {_key.Length} bytes. " +
                "Generate a key with: openssl rand -base64 32");
        }
    }

    /// <summary>
    /// Encrypts plain text using AES-256-CBC with a random IV.
    /// Format: [16-byte IV][encrypted data]
    /// </summary>
    /// <param name="plainText">The text to encrypt</param>
    /// <returns>Base64-encoded string containing IV + ciphertext</returns>
    /// <exception cref="ArgumentNullException">Thrown when plainText is null</exception>
    public string Encrypt(string plainText)
    {
        if (string.IsNullOrEmpty(plainText))
        {
            throw new ArgumentNullException(nameof(plainText), "Cannot encrypt null or empty text");
        }

        using var aes = Aes.Create();
        aes.Key = _key;
        aes.Mode = CipherMode.CBC;
        aes.Padding = PaddingMode.PKCS7;
        aes.GenerateIV(); // Generate random IV for each encryption

        using var encryptor = aes.CreateEncryptor();
        var plainBytes = Encoding.UTF8.GetBytes(plainText);
        var cipherBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);

        // Prepend IV to ciphertext: [IV][ciphertext]
        var result = new byte[aes.IV.Length + cipherBytes.Length];
        Buffer.BlockCopy(aes.IV, 0, result, 0, aes.IV.Length);
        Buffer.BlockCopy(cipherBytes, 0, result, aes.IV.Length, cipherBytes.Length);

        return Convert.ToBase64String(result);
    }

    /// <summary>
    /// Decrypts text that was encrypted using the Encrypt method.
    /// Extracts IV from the first 16 bytes of the encrypted data.
    /// </summary>
    /// <param name="encryptedText">Base64-encoded string containing IV + ciphertext</param>
    /// <returns>Decrypted plain text</returns>
    /// <exception cref="ArgumentNullException">Thrown when encryptedText is null</exception>
    /// <exception cref="CryptographicException">Thrown when decryption fails (wrong key or corrupted data)</exception>
    public string Decrypt(string encryptedText)
    {
        if (string.IsNullOrEmpty(encryptedText))
        {
            throw new ArgumentNullException(nameof(encryptedText), "Cannot decrypt null or empty text");
        }

        byte[] fullData;
        try
        {
            fullData = Convert.FromBase64String(encryptedText);
        }
        catch (FormatException ex)
        {
            throw new CryptographicException("Encrypted text is not a valid Base64 string", ex);
        }

        const int ivLength = 16; // AES IV is always 16 bytes
        if (fullData.Length < ivLength)
        {
            throw new CryptographicException(
                $"Encrypted data is too short. Expected at least {ivLength} bytes for IV, got {fullData.Length} bytes");
        }

        using var aes = Aes.Create();
        aes.Key = _key;
        aes.Mode = CipherMode.CBC;
        aes.Padding = PaddingMode.PKCS7;

        // Extract IV from first 16 bytes
        var iv = new byte[ivLength];
        Buffer.BlockCopy(fullData, 0, iv, 0, ivLength);
        aes.IV = iv;

        // Extract ciphertext (everything after IV)
        var cipherBytes = new byte[fullData.Length - ivLength];
        Buffer.BlockCopy(fullData, ivLength, cipherBytes, 0, cipherBytes.Length);

        using var decryptor = aes.CreateDecryptor();
        var plainBytes = decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length);

        return Encoding.UTF8.GetString(plainBytes);
    }
}
