using System;
using System.Security.Cryptography;
using System.Text;

namespace PolyTrader.Core.Security
{
    /// <summary>
    /// Verschlüsselung sensibler Felder (Wallet-Private-Keys, API-Secrets) at-rest mit einem
    /// portablen Master-Key (AES-256-GCM, authenticated). Der Master-Key liegt AUSSERHALB der DB
    /// (Umgebungsvariable <c>POLYTRADER_MASTER_KEY</c> bzw. eine gitignorierte Key-Datei) — ein
    /// DB-Leak/Backup ist damit ohne den Master-Key wertlos (Befund F1, siehe docs/sicherheit).
    ///
    /// Speicherformat: <c>enc:v1:base64(nonce(12) || tag(16) || ciphertext)</c>. Werte OHNE dieses
    /// Präfix gelten als Alt-Klartext und werden beim nächsten Speichern automatisch verschlüsselt
    /// (selbstheilende Migration). Ist kein Master-Key konfiguriert, arbeitet die App wie bisher mit
    /// Klartext — mit deutlicher Startwarnung (kein stiller Sicherheitsverlust).
    ///
    /// Statischer Zugriff, damit der EF-<see cref="EncryptedStringConverter"/> ihn nutzen kann;
    /// <see cref="Configure"/> wird einmalig beim Start aufgerufen.
    /// </summary>
    public static class SecretProtection
    {
        public const string Prefix = "enc:v1:";
        private static byte[]? _key;   // 32 Byte, null = nicht konfiguriert

        /// <summary>True, wenn ein Master-Key gesetzt ist (Verschlüsselung aktiv).</summary>
        public static bool IsConfigured => _key != null;

        /// <summary>
        /// Konfiguriert den Master-Key aus dem Rohwert (Env/Datei). Akzeptiert 32-Byte-Schlüssel als
        /// Base64 oder Hex; jeder andere String wird per SHA-256 zu 32 Byte abgeleitet (Passphrase-Komfort –
        /// empfohlen ist ein zufälliger 32-Byte-Base64-Key). Leerer/nuller Wert = nicht konfiguriert.
        /// </summary>
        public static void Configure(string? rawKey)
        {
            _key = string.IsNullOrWhiteSpace(rawKey) ? null : DeriveKey(rawKey.Trim());
        }

        /// <summary>Nur für Tests: Zustand zurücksetzen.</summary>
        internal static void Reset() => _key = null;

        /// <summary>
        /// Verschlüsselt einen Klartext → <c>enc:v1:…</c>. Leerstring bleibt leer. Ohne Master-Key wird
        /// der Klartext unverändert zurückgegeben (Passthrough; die App hat beim Start gewarnt).
        /// </summary>
        public static string Protect(string? plaintext)
        {
            if (string.IsNullOrEmpty(plaintext)) return plaintext ?? string.Empty;
            if (plaintext.StartsWith(Prefix, StringComparison.Ordinal)) return plaintext; // schon verschlüsselt
            if (_key == null) return plaintext;                                            // nicht konfiguriert → Klartext

            byte[] pt = Encoding.UTF8.GetBytes(plaintext);
            byte[] nonce = RandomNumberGenerator.GetBytes(AesGcm.NonceByteSizes.MaxSize);   // 12
            byte[] tag = new byte[AesGcm.TagByteSizes.MaxSize];                             // 16
            byte[] ct = new byte[pt.Length];
            using (var aes = new AesGcm(_key, tag.Length))
                aes.Encrypt(nonce, pt, ct, tag);

            byte[] packed = new byte[nonce.Length + tag.Length + ct.Length];
            Buffer.BlockCopy(nonce, 0, packed, 0, nonce.Length);
            Buffer.BlockCopy(tag, 0, packed, nonce.Length, tag.Length);
            Buffer.BlockCopy(ct, 0, packed, nonce.Length + tag.Length, ct.Length);
            return Prefix + Convert.ToBase64String(packed);
        }

        /// <summary>
        /// Entschlüsselt <c>enc:v1:…</c>-Werte. Werte ohne Präfix (Alt-Klartext) werden unverändert
        /// zurückgegeben. Fehlt für einen verschlüsselten Wert der Master-Key oder ist er falsch/manipuliert,
        /// wird eine <see cref="InvalidOperationException"/> geworfen (kein stilles Fehlverhalten).
        /// </summary>
        public static string Unprotect(string? stored)
        {
            if (string.IsNullOrEmpty(stored)) return stored ?? string.Empty;
            if (!stored.StartsWith(Prefix, StringComparison.Ordinal)) return stored; // Alt-Klartext
            if (_key == null)
                throw new InvalidOperationException(
                    "Verschlüsselte Account-Credentials, aber kein Master-Key gesetzt (POLYTRADER_MASTER_KEY). Entschlüsselung nicht möglich.");

            byte[] packed;
            try { packed = Convert.FromBase64String(stored.Substring(Prefix.Length)); }
            catch (FormatException ex) { throw new InvalidOperationException("Beschädigter verschlüsselter Wert (Base64).", ex); }

            int nonceLen = AesGcm.NonceByteSizes.MaxSize; // 12
            int tagLen = AesGcm.TagByteSizes.MaxSize;     // 16
            if (packed.Length < nonceLen + tagLen)
                throw new InvalidOperationException("Beschädigter verschlüsselter Wert (zu kurz).");

            byte[] nonce = new byte[nonceLen];
            byte[] tag = new byte[tagLen];
            byte[] ct = new byte[packed.Length - nonceLen - tagLen];
            Buffer.BlockCopy(packed, 0, nonce, 0, nonceLen);
            Buffer.BlockCopy(packed, nonceLen, tag, 0, tagLen);
            Buffer.BlockCopy(packed, nonceLen + tagLen, ct, 0, ct.Length);

            byte[] pt = new byte[ct.Length];
            try
            {
                using var aes = new AesGcm(_key, tag.Length);
                aes.Decrypt(nonce, ct, tag, pt);   // wirft CryptographicException bei falschem Key/Manipulation
            }
            catch (CryptographicException ex)
            {
                throw new InvalidOperationException("Entschlüsselung fehlgeschlagen (falscher Master-Key oder manipulierte Daten).", ex);
            }
            return Encoding.UTF8.GetString(pt);
        }

        /// <summary>True, wenn der Wert bereits im verschlüsselten Format vorliegt.</summary>
        public static bool IsEncrypted(string? value) =>
            !string.IsNullOrEmpty(value) && value!.StartsWith(Prefix, StringComparison.Ordinal);

        private static byte[] DeriveKey(string raw)
        {
            // 32-Byte-Key als Base64?
            try { var b = Convert.FromBase64String(raw); if (b.Length == 32) return b; } catch { /* kein Base64 */ }
            // 32-Byte-Key als Hex (64 Zeichen)?
            if (raw.Length == 64 && IsHex(raw))
            {
                var b = new byte[32];
                for (int i = 0; i < 32; i++) b[i] = Convert.ToByte(raw.Substring(i * 2, 2), 16);
                return b;
            }
            // sonst: aus beliebiger Passphrase 32 Byte ableiten (Komfort; besser echten 32-Byte-Key nutzen)
            return SHA256.HashData(Encoding.UTF8.GetBytes(raw));
        }

        private static bool IsHex(string s)
        {
            foreach (char c in s)
                if (!Uri.IsHexDigit(c)) return false;
            return true;
        }
    }
}
