using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Newtonsoft.Json;
using Serilog;

namespace ZB.MOM.WW.LmxProxy.Host.Security
{
    /// <summary>
    ///     Manages API keys loaded from a JSON file with hot-reload via FileSystemWatcher.
    /// </summary>
    public sealed class ApiKeyService : IDisposable
    {
        private static readonly ILogger Log = Serilog.Log.ForContext<ApiKeyService>();

        private readonly string _configFilePath;
        private readonly FileSystemWatcher? _watcher;
        private readonly SemaphoreSlim _reloadLock = new SemaphoreSlim(1, 1);
        private volatile Dictionary<string, ApiKey> _keys = new Dictionary<string, ApiKey>(StringComparer.Ordinal);
        private DateTime _lastReloadTime = DateTime.MinValue;
        private static readonly TimeSpan DebounceInterval = TimeSpan.FromSeconds(1);

        public ApiKeyService(string configFilePath)
        {
            _configFilePath = Path.GetFullPath(configFilePath);

            // Auto-generate default file if missing
            if (!File.Exists(_configFilePath))
            {
                GenerateDefaultKeyFile();
            }

            // Initial load
            LoadKeys();

            // Set up FileSystemWatcher for hot-reload
            var directory = Path.GetDirectoryName(_configFilePath);
            var fileName = Path.GetFileName(_configFilePath);
            if (directory != null)
            {
                _watcher = new FileSystemWatcher(directory, fileName)
                {
                    NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size,
                    EnableRaisingEvents = true
                };
                _watcher.Changed += OnFileChanged;
            }
        }

        /// <summary>
        ///     Validates an API key. Returns the ApiKey if valid and enabled, null otherwise.
        /// </summary>
        public ApiKey? ValidateApiKey(string apiKey)
        {
            if (string.IsNullOrEmpty(apiKey)) return null;
            return _keys.TryGetValue(apiKey, out var key) && key.Enabled ? key : null;
        }

        /// <summary>
        ///     Checks if a key has the required role.
        ///     ReadWrite implies ReadOnly.
        /// </summary>
        public bool HasRole(string apiKey, ApiKeyRole requiredRole)
        {
            var key = ValidateApiKey(apiKey);
            if (key == null) return false;

            switch (requiredRole)
            {
                case ApiKeyRole.ReadOnly:
                    return true; // Both roles have ReadOnly
                case ApiKeyRole.ReadWrite:
                    return key.Role == ApiKeyRole.ReadWrite;
                default:
                    return false;
            }
        }

        /// <summary>Gets the count of loaded API keys.</summary>
        public int KeyCount => _keys.Count;

        private void GenerateDefaultKeyFile()
        {
            Log.Information("API key file not found at {Path}, generating defaults", _configFilePath);

            var config = new ApiKeyConfiguration
            {
                ApiKeys = new List<ApiKey>
                {
                    new ApiKey
                    {
                        Key = GenerateRandomKey(),
                        Description = "Default ReadOnly key (auto-generated)",
                        Role = ApiKeyRole.ReadOnly,
                        Enabled = true
                    },
                    new ApiKey
                    {
                        Key = GenerateRandomKey(),
                        Description = "Default ReadWrite key (auto-generated)",
                        Role = ApiKeyRole.ReadWrite,
                        Enabled = true
                    }
                }
            };

            var directory = Path.GetDirectoryName(_configFilePath);
            if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
                Directory.CreateDirectory(directory);

            var json = JsonConvert.SerializeObject(config, Formatting.Indented);
            File.WriteAllText(_configFilePath, json);
            Log.Information("Default API key file generated at {Path}", _configFilePath);
        }

        private static string GenerateRandomKey()
        {
            // 32 random bytes -> 64-char hex string
            var bytes = new byte[32];
            using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create())
            {
                rng.GetBytes(bytes);
            }
            return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
        }

        private void LoadKeys()
        {
            try
            {
                var json = File.ReadAllText(_configFilePath);
                var config = JsonConvert.DeserializeObject<ApiKeyConfiguration>(json);
                if (config?.ApiKeys != null)
                {
                    _keys = config.ApiKeys
                        .Where(k => !string.IsNullOrEmpty(k.Key))
                        .ToDictionary(k => k.Key, k => k, StringComparer.Ordinal);
                    Log.Information("Loaded {Count} API keys from {Path}", _keys.Count, _configFilePath);
                }
                else
                {
                    Log.Warning("API key file at {Path} contained no keys", _configFilePath);
                    _keys = new Dictionary<string, ApiKey>(StringComparer.Ordinal);
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex, "Failed to load API keys from {Path}", _configFilePath);
            }
        }

        private void OnFileChanged(object sender, FileSystemEventArgs e)
        {
            // Debounce: ignore rapid changes within 1 second
            if (DateTime.UtcNow - _lastReloadTime < DebounceInterval) return;

            if (_reloadLock.Wait(0))
            {
                try
                {
                    _lastReloadTime = DateTime.UtcNow;
                    Log.Information("API key file changed, reloading");

                    // Small delay to let the file system finish writing
                    Thread.Sleep(100);
                    LoadKeys();
                }
                finally
                {
                    _reloadLock.Release();
                }
            }
        }

        public void Dispose()
        {
            _watcher?.Dispose();
            _reloadLock.Dispose();
        }
    }
}
