using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;

/// <summary>
/// EF Core value converter that encrypts a string column at rest using ASP.NET
/// Data Protection. Plaintext is protected when written to the database and
/// transparently unprotected when read back, so secret-bearing columns
/// (SMTP credentials, external-system auth config, database connection strings)
/// are never persisted verbatim.
/// </summary>
/// <remarks>
/// The protector is purpose-scoped so ciphertext from one column cannot be
/// unprotected as another. Data Protection keys are persisted to the
/// configuration database itself (see <see cref="ScadaBridgeDbContext"/> implementing
/// <c>IDataProtectionKeyContext</c>), so all central nodes share the same key ring
/// and can decrypt each other's writes.
/// </remarks>
public sealed class EncryptedStringConverter : ValueConverter<string?, string?>
{
    /// <summary>The Data Protection purpose string shared by all encrypted configuration columns.</summary>
    public const string ProtectorPurpose = "ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.EncryptedColumn";

    /// <summary>
    /// Initializes the converter with the data protector used to encrypt and decrypt column values.
    /// </summary>
    /// <param name="protector">The data protector scoped to the encrypted column purpose.</param>
    public EncryptedStringConverter(IDataProtector protector)
        : base(
            plaintext => plaintext == null ? null : protector.Protect(plaintext),
            ciphertext => ciphertext == null ? null : Unprotect(protector, ciphertext))
    {
    }

    private static string Unprotect(IDataProtector protector, string ciphertext)
    {
        // A row that predates encryption (or test fixtures inserting raw text) is not valid
        // protected payload. Unprotect throws CryptographicException in that case; surface a
        // clearer message rather than a bare crypto failure.
        try
        {
            return protector.Unprotect(ciphertext);
        }
        catch (System.Security.Cryptography.CryptographicException ex)
        {
            throw new InvalidOperationException(
                "Failed to decrypt an encrypted configuration column. The Data Protection key " +
                "ring may be unavailable, or the stored value was not written by this system.",
                ex);
        }
    }
}
