using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Serilog;

namespace ZB.MOM.WW.LmxProxy.Host.Security
{
    /// <summary>
    ///     Service for managing API keys with file-based storage.
    ///     Handles validation, role checking, and automatic reload on file changes.
    /// </summary>
    public class ApiKeyService : IDisposable
    {
        private static readonly ILogger Logger = Log.ForContext<ApiKeyService>();
        private readonly ConcurrentDictionary<string, ApiKey> _apiKeys;
        private readonly string _configFilePath;
        private readonly SemaphoreSlim _reloadLock = new(1, 1);
        private bool _disposed;
        private FileSystemWatcher? _fileWatcher;
        private DateTime _lastReloadTime = DateTime.MinValue;

        /// <summary>
        ///     Initializes a new instance of the <see cref="ApiKeyService" /> class.
        /// </summary>
        /// <param name="configFilePath">The path to the API key configuration file.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="configFilePath" /> is null.</exception>
        public ApiKeyService(string configFilePath)
        {
            _configFilePath = configFilePath ?? throw new ArgumentNullException(nameof(configFilePath));
            _apiKeys = new ConcurrentDictionary<string, ApiKey>();

            InitializeFileWatcher();
            LoadConfiguration();
        }

        /// <summary>
        ///     Disposes the <see cref="ApiKeyService" /> and releases resources.
        /// </summary>
        public void Dispose()
        {
            if (_disposed)
            {
                return;
            }

            _disposed = true;

            _fileWatcher?.Dispose();
            _reloadLock?.Dispose();

            Logger.Information("API key service disposed");
        }

        /// <summary>
        ///     Validates an API key and returns its details if valid.
        /// </summary>
        /// <param name="apiKey">The API key value to validate.</param>
        /// <returns>The <see cref="ApiKey" /> if valid; otherwise, <c>null</c>.</returns>
        public ApiKey? ValidateApiKey(string apiKey)
        {
            if (string.IsNullOrWhiteSpace(apiKey))
            {
                return null;
            }

            if (_apiKeys.TryGetValue(apiKey, out ApiKey? key) && key.IsValid())
            {
                Logger.Debug("API key validated successfully for {Description}", key.Description);
                return key;
            }

            Logger.Warning("Invalid or expired API key attempted");
            return null;
        }

        /// <summary>
        ///     Checks if an API key has the specified role.
        /// </summary>
        /// <param name="apiKey">The API key value.</param>
        /// <param name="requiredRole">The required <see cref="ApiKeyRole" />.</param>
        /// <returns><c>true</c> if the API key has the required role; otherwise, <c>false</c>.</returns>
        public bool HasRole(string apiKey, ApiKeyRole requiredRole)
        {
            ApiKey? key = ValidateApiKey(apiKey);
            if (key == null)
            {
                return false;
            }

            // ReadWrite role has access to everything
            if (key.Role == ApiKeyRole.ReadWrite)
            {
                return true;
            }

            // ReadOnly role only has access to ReadOnly operations
            return requiredRole == ApiKeyRole.ReadOnly;
        }

        /// <summary>
        ///     Initializes the file system watcher for the API key configuration file.
        /// </summary>
        private void InitializeFileWatcher()
        {
            string? directory = Path.GetDirectoryName(_configFilePath);
            string? fileName = Path.GetFileName(_configFilePath);

            if (string.IsNullOrEmpty(directory) || string.IsNullOrEmpty(fileName))
            {
                Logger.Warning("Invalid config file path, file watching disabled");
                return;
            }

            try
            {
                _fileWatcher = new FileSystemWatcher(directory, fileName)
                {
                    NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.CreationTime,
                    EnableRaisingEvents = true
                };

                _fileWatcher.Changed += OnFileChanged;
                _fileWatcher.Created += OnFileChanged;
                _fileWatcher.Renamed += OnFileRenamed;

                Logger.Information("File watcher initialized for {FilePath}", _configFilePath);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Failed to initialize file watcher for {FilePath}", _configFilePath);
            }
        }

        /// <summary>
        ///     Handles file change events for the configuration file.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The <see cref="FileSystemEventArgs" /> instance containing event data.</param>
        private void OnFileChanged(object sender, FileSystemEventArgs e)
        {
            if (e.ChangeType == WatcherChangeTypes.Changed || e.ChangeType == WatcherChangeTypes.Created)
            {
                Logger.Information("API key configuration file changed, reloading");
                Task.Run(() => ReloadConfigurationAsync());
            }
        }

        /// <summary>
        ///     Handles file rename events for the configuration file.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The <see cref="RenamedEventArgs" /> instance containing event data.</param>
        private void OnFileRenamed(object sender, RenamedEventArgs e)
        {
            if (e.FullPath.Equals(_configFilePath, StringComparison.OrdinalIgnoreCase))
            {
                Logger.Information("API key configuration file renamed, reloading");
                Task.Run(() => ReloadConfigurationAsync());
            }
        }

        /// <summary>
        ///     Asynchronously reloads the API key configuration from file.
        ///     Debounces rapid file changes to avoid excessive reloads.
        /// </summary>
        private async Task ReloadConfigurationAsync()
        {
            // Debounce rapid file changes
            TimeSpan timeSinceLastReload = DateTime.UtcNow - _lastReloadTime;
            if (timeSinceLastReload < TimeSpan.FromSeconds(1))
            {
                await Task.Delay(TimeSpan.FromSeconds(1) - timeSinceLastReload);
            }

            await _reloadLock.WaitAsync();
            try
            {
                LoadConfiguration();
                _lastReloadTime = DateTime.UtcNow;
            }
            finally
            {
                _reloadLock.Release();
            }
        }

        /// <summary>
        ///     Loads the API key configuration from file.
        ///     If the file does not exist, creates a default configuration.
        /// </summary>
        private void LoadConfiguration()
        {
            try
            {
                if (!File.Exists(_configFilePath))
                {
                    Logger.Warning("API key configuration file not found at {FilePath}, creating default",
                        _configFilePath);
                    CreateDefaultConfiguration();
                    return;
                }

                string json = File.ReadAllText(_configFilePath);
                var options = new JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true,
                    ReadCommentHandling = JsonCommentHandling.Skip
                };
                options.Converters.Add(new JsonStringEnumConverter());
                ApiKeyConfiguration? config = JsonSerializer.Deserialize<ApiKeyConfiguration>(json, options);

                if (config?.ApiKeys == null || !config.ApiKeys.Any())
                {
                    Logger.Warning("No API keys found in configuration file");
                    return;
                }

                // Clear existing keys and load new ones
                _apiKeys.Clear();

                foreach (ApiKey? apiKey in config.ApiKeys)
                {
                    if (string.IsNullOrWhiteSpace(apiKey.Key))
                    {
                        Logger.Warning("Skipping API key with empty key value");
                        continue;
                    }

                    if (_apiKeys.TryAdd(apiKey.Key, apiKey))
                    {
                        Logger.Information("Loaded API key: {Description} with role {Role}",
                            apiKey.Description, apiKey.Role);
                    }
                    else
                    {
                        Logger.Warning("Duplicate API key found: {Description}", apiKey.Description);
                    }
                }

                Logger.Information("Loaded {Count} API keys from configuration", _apiKeys.Count);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Failed to load API key configuration from {FilePath}", _configFilePath);
            }
        }

        /// <summary>
        ///     Creates a default API key configuration file with sample keys.
        /// </summary>
        private void CreateDefaultConfiguration()
        {
            try
            {
                var defaultConfig = new ApiKeyConfiguration
                {
                    ApiKeys = new List<ApiKey>
                    {
                        new()
                        {
                            Key = Guid.NewGuid().ToString("N"),
                            Description = "Default read-only API key",
                            Role = ApiKeyRole.ReadOnly,
                            Enabled = true
                        },
                        new()
                        {
                            Key = Guid.NewGuid().ToString("N"),
                            Description = "Default read-write API key",
                            Role = ApiKeyRole.ReadWrite,
                            Enabled = true
                        }
                    }
                };

                string? json = JsonSerializer.Serialize(defaultConfig, new JsonSerializerOptions
                {
                    WriteIndented = true
                });

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

                File.WriteAllText(_configFilePath, json);
                Logger.Information("Created default API key configuration at {FilePath}", _configFilePath);

                // Load the created configuration
                LoadConfiguration();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Failed to create default API key configuration");
            }
        }
    }
}
