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

namespace Darkmatter.Libs.PlayerPrefs
{
    /// <summary>
    /// Wrapper around Unity's PlayerPrefs that obscures keys and encrypts values
    /// to discourage casual tampering via plist/registry editors.
    /// </summary>
    public static class ProtectedPlayerPrefs
    {
        private static byte[] s_key;
        private static byte[] s_iv;
        private static bool s_initialized;
        private static bool s_missingSettingsWarningLogged;

        private const string DefaultPassphrase = "DM_BusGame_2026";
        private const string SettingsResourcePath = "ProtectedPlayerPrefsSettings";

        public static bool IsUsingConfiguredHash { get; private set; }
        public static string InitializationSource { get; private set; } = "Uninitialized";

        /// <summary>
        /// Initialize with a custom passphrase. Call once at startup.
        /// If never called, the configured settings asset is used when present.
        /// </summary>
        public static void Init(string passphrase)
        {
            if (string.IsNullOrWhiteSpace(passphrase))
            {
                throw new ArgumentException("Passphrase cannot be null or whitespace.", nameof(passphrase));
            }

            ApplyHash(HashPassphraseToBytes(passphrase), "ManualPassphrase", false);
        }

        /// <summary>
        /// Initialize from a precomputed SHA-256 hash generated by <see cref="ComputePassphraseHash"/>.
        /// </summary>
        public static void InitWithHash(string hashedPassphrase)
        {
            if (!TryParseHash(hashedPassphrase, out var hashBytes))
            {
                throw new ArgumentException(
                    "Hashed passphrase must be a 64-character hexadecimal SHA-256 value.",
                    nameof(hashedPassphrase));
            }

            ApplyHash(hashBytes, "ManualHash", true);
        }

        /// <summary>
        /// Converts a user-facing passphrase into the SHA-256 hash stored in the settings asset.
        /// </summary>
        public static string ComputePassphraseHash(string passphrase)
        {
            if (string.IsNullOrWhiteSpace(passphrase))
            {
                return string.Empty;
            }

            return BytesToHex(HashPassphraseToBytes(passphrase));
        }

        private static void EnsureInitialized()
        {
            if (s_initialized)
            {
                return;
            }

            if (TryInitializeFromSettings())
            {
                return;
            }

            ApplyHash(HashPassphraseToBytes(DefaultPassphrase), "DefaultFallback", false);
            LogMissingSettingsWarning();
        }

        // ── String ──────────────────────────────────────────────

        public static void SetString(string key, string value)
        {
            EnsureInitialized();
            var encKey = HashKey(key);
            var encVal = Encrypt(value ?? string.Empty);
            UnityEngine.PlayerPrefs.SetString(encKey, encVal);
        }

        public static string GetString(string key, string defaultValue = "")
        {
            EnsureInitialized();
            var encKey = HashKey(key);
            if (!UnityEngine.PlayerPrefs.HasKey(encKey)) return defaultValue;
            try
            {
                return Decrypt(UnityEngine.PlayerPrefs.GetString(encKey));
            }
            catch
            {
                return defaultValue;
            }
        }

        // ── Int ─────────────────────────────────────────────────

        public static void SetInt(string key, int value)
        {
            SetString(key, value.ToString());
        }

        public static int GetInt(string key, int defaultValue = 0)
        {
            EnsureInitialized();
            var encKey = HashKey(key);
            if (!UnityEngine.PlayerPrefs.HasKey(encKey)) return defaultValue;
            try
            {
                var decrypted = Decrypt(UnityEngine.PlayerPrefs.GetString(encKey));
                return int.TryParse(decrypted, out var result) ? result : defaultValue;
            }
            catch
            {
                return defaultValue;
            }
        }

        // ── Float ───────────────────────────────────────────────

        public static void SetFloat(string key, float value)
        {
            SetString(key, value.ToString("R"));
        }

        public static float GetFloat(string key, float defaultValue = 0f)
        {
            EnsureInitialized();
            var encKey = HashKey(key);
            if (!UnityEngine.PlayerPrefs.HasKey(encKey)) return defaultValue;
            try
            {
                var decrypted = Decrypt(UnityEngine.PlayerPrefs.GetString(encKey));
                return float.TryParse(decrypted, out var result) ? result : defaultValue;
            }
            catch
            {
                return defaultValue;
            }
        }

        // ── Bool (convenience) ──────────────────────────────────

        public static void SetBool(string key, bool value)
        {
            SetInt(key, value ? 1 : 0);
        }

        public static bool GetBool(string key, bool defaultValue = false)
        {
            EnsureInitialized();
            var encKey = HashKey(key);
            if (!UnityEngine.PlayerPrefs.HasKey(encKey)) return defaultValue;
            return GetInt(key, defaultValue ? 1 : 0) == 1;
        }

        // ── Long (convenience) ──────────────────────────────────
        public static void SetLong(string key, long value)
        {
            SetString(key, value.ToString());
        }

        public static long GetLong(string key, long defaultValue = 0L)
        {
            EnsureInitialized();
            var encKey = HashKey(key);
            if (!UnityEngine.PlayerPrefs.HasKey(encKey)) return defaultValue;
            try
            {
                var decrypted = Decrypt(UnityEngine.PlayerPrefs.GetString(encKey));
                return long.TryParse(decrypted, out var result) ? result : defaultValue;
            }
            catch
            {
                return defaultValue;
            }
        }

        // ── Queries ─────────────────────────────────────────────

        public static bool HasKey(string key)
        {
            EnsureInitialized();
            return UnityEngine.PlayerPrefs.HasKey(HashKey(key));
        }

        public static void DeleteKey(string key)
        {
            EnsureInitialized();
            UnityEngine.PlayerPrefs.DeleteKey(HashKey(key));
        }

        public static void DeleteAll()
        {
            UnityEngine.PlayerPrefs.DeleteAll();
        }

        public static void Save()
        {
            UnityEngine.PlayerPrefs.Save();
        }

        // ── Internal crypto ─────────────────────────────────────

        internal static string HashKey(string key)
        {
            EnsureInitialized();
            using var hmac = new HMACSHA256(s_key);
            var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(key));
            return "pp_" + BitConverter.ToString(hash).Replace("-", "").Substring(0, 32).ToLowerInvariant();
        }

        private static bool TryInitializeFromSettings()
        {
            var settings = Resources.Load<ProtectedPlayerPrefsSettings>(SettingsResourcePath);
            if (settings == null || !settings.HasConfiguredHash)
            {
                return false;
            }

            if (!TryParseHash(settings.HashedPassphrase, out var hashBytes))
            {
                Debug.LogWarning(
                    "[ProtectedPlayerPrefs] Settings asset contains an invalid hash. Falling back to the default passphrase.");
                return false;
            }

            ApplyHash(hashBytes, "SettingsAsset", true);
            return true;
        }

        private static void ApplyHash(byte[] hashBytes, string source, bool usingConfiguredHash)
        {
            s_key = new byte[16];
            s_iv = new byte[16];
            Array.Copy(hashBytes, 0, s_key, 0, 16);
            Array.Copy(hashBytes, 16, s_iv, 0, 16);
            s_initialized = true;
            IsUsingConfiguredHash = usingConfiguredHash;
            InitializationSource = source;
        }

        private static byte[] HashPassphraseToBytes(string passphrase)
        {
            using var sha = SHA256.Create();
            return sha.ComputeHash(Encoding.UTF8.GetBytes(passphrase));
        }

        private static bool TryParseHash(string hashedPassphrase, out byte[] hashBytes)
        {
            hashBytes = null;
            if (string.IsNullOrWhiteSpace(hashedPassphrase))
            {
                return false;
            }

            var normalized = hashedPassphrase.Trim();
            if (normalized.Length != 64)
            {
                return false;
            }

            try
            {
                hashBytes = new byte[32];
                for (int index = 0; index < hashBytes.Length; index++)
                {
                    var hexIndex = index * 2;
                    hashBytes[index] = Convert.ToByte(normalized.Substring(hexIndex, 2), 16);
                }

                return hashBytes.Length == 32;
            }
            catch (Exception)
            {
                return false;
            }
        }

        private static string BytesToHex(byte[] bytes)
        {
            var builder = new StringBuilder(bytes.Length * 2);
            foreach (var value in bytes)
            {
                builder.Append(value.ToString("x2"));
            }

            return builder.ToString();
        }

        private static void LogMissingSettingsWarning()
        {
            if (s_missingSettingsWarningLogged)
            {
                return;
            }

            s_missingSettingsWarningLogged = true;
            Debug.LogWarning(
                "[ProtectedPlayerPrefs] No configured hash password was found. Using the default fallback passphrase. Open Tools/Darkmatter/Protected PlayerPrefs/Getting Started to configure a project-specific hash.");
        }

        [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
        static void ResetDomain()
        {
            s_key = null;
            s_iv = null;
            s_initialized = false;
            s_missingSettingsWarningLogged = false;
            IsUsingConfiguredHash = false;
            InitializationSource = "Uninitialized";
        }

        private static string Encrypt(string plainText)
        {
            using var aes = Aes.Create();
            aes.Key = s_key;
            aes.IV = s_iv;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;

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

        internal static string Decrypt(string cipherText)
        {
            using var aes = Aes.Create();
            aes.Key = s_key;
            aes.IV = s_iv;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;

            using var decryptor = aes.CreateDecryptor();
            var cipherBytes = Convert.FromBase64String(cipherText);
            var plainBytes = decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length);
            return Encoding.UTF8.GetString(plainBytes);
        }
    }
}