using System.Security.Cryptography;
using System.Text;
using Baya.Application.Contracts.Common;
using Microsoft.Extensions.Options;

namespace Baya.Infrastructure.CrossCutting.Seams;

/// <summary>
/// Local symmetric-key implementation of <see cref="IFieldEncryptor"/> — AES-256-CBC with a random
/// per-value IV (prepended to the ciphertext) for at-rest reversibility, and a keyed HMAC-SHA256 for
/// deterministic lookup hashes. This is the mock seam: the real implementation swaps to a KMS / Key
/// Vault provider behind the same interface. Plaintext is never logged.
/// </summary>
public sealed class SymmetricFieldEncryptor : IFieldEncryptor
{
    private readonly byte[] _key;
    private readonly byte[] _hashKey;

    public SymmetricFieldEncryptor(IOptions<SeamOptions> options)
    {
        var settings = options.Value.FieldEncryption;

        // Derive a stable 32-byte AES key from whatever the operator configured (any length/format),
        // so a human-friendly secret still yields a valid key. SHA-256 of the configured material.
        _key = SHA256.HashData(Encoding.UTF8.GetBytes(Require(settings.Key, nameof(settings.Key))));

        var hashMaterial = string.IsNullOrEmpty(settings.HashKey) ? settings.Key : settings.HashKey;
        _hashKey = SHA256.HashData(Encoding.UTF8.GetBytes(Require(hashMaterial, nameof(settings.HashKey))));
    }

    public string Encrypt(string plaintext)
    {
        if (string.IsNullOrEmpty(plaintext))
            return plaintext;

        using var aes = Aes.Create();
        aes.Key = _key;
        aes.GenerateIV();

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

        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);
    }

    public string Decrypt(string ciphertext)
    {
        if (string.IsNullOrEmpty(ciphertext))
            return ciphertext;

        var cipherWithIv = Convert.FromBase64String(ciphertext);

        using var aes = Aes.Create();
        aes.Key = _key;

        var ivLength = aes.BlockSize / 8;
        var iv = new byte[ivLength];
        Buffer.BlockCopy(cipherWithIv, 0, iv, 0, ivLength);
        aes.IV = iv;

        using var decryptor = aes.CreateDecryptor();
        var cipherBytes = decryptor.TransformFinalBlock(cipherWithIv, ivLength, cipherWithIv.Length - ivLength);

        return Encoding.UTF8.GetString(cipherBytes);
    }

    public string Hash(string value)
    {
        if (string.IsNullOrEmpty(value))
            return value;

        using var hmac = new HMACSHA256(_hashKey);
        var hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(value));
        return Convert.ToHexString(hashBytes);
    }

    private static string Require(string value, string name) =>
        string.IsNullOrWhiteSpace(value)
            ? throw new InvalidOperationException($"Seams:FieldEncryption:{name} must be configured.")
            : value;
}
