using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Extensions.Logging;

namespace ImageCatalog;

/// <summary>
/// Modern parameter storage service using XML for persistence.
/// Thread-safe key-value store for application settings.
/// </summary>
public class ParametriSetup
{
    private readonly object _lock = new();
    private readonly ILogger<ParametriSetup> _logger;
    private Dictionary<string, string> _parameters = new(StringComparer.OrdinalIgnoreCase);

    /// <summary>
    /// Gets or sets the XML file path for settings storage.
    /// </summary>
    public string? NomeFileSetup { get; set; }

    /// <summary>
    /// Initializes a new instance with the specified settings file.
    /// </summary>
    /// <param name="fileSetup">Path to the XML settings file.</param>
    /// <param name="logger">Optional logger for diagnostics.</param>
    public ParametriSetup(string? fileSetup = null, ILogger<ParametriSetup>? logger = null)
    {
        _logger = logger ?? Microsoft.Extensions.Logging.Abstractions.NullLogger<ParametriSetup>.Instance;
        NomeFileSetup = fileSetup;
        if (!string.IsNullOrWhiteSpace(fileSetup))
        {
            CaricaParametriSetup();
        }
    }

    /// <summary>
    /// Loads settings from the XML file.
    /// </summary>
    public void CaricaParametriSetup()
    {
        lock (_lock)
        {
            if (string.IsNullOrEmpty(NomeFileSetup) || !File.Exists(NomeFileSetup))
            {
                _parameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                return;
            }

            try
            {
                var doc = XDocument.Load(NomeFileSetup);
                var setupElements = doc.Descendants("Setup");

                _parameters = setupElements
                    .Where(e => e.Element("Nome") != null)
                    .ToDictionary(
                        e => e.Element("Nome")!.Value,
                        e => e.Element("Valore")?.Value ?? string.Empty,
                        StringComparer.OrdinalIgnoreCase
                    );

                _logger.LogInformation("Loaded {Count} parameters from {FilePath}", _parameters.Count, NomeFileSetup);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to load settings from {FilePath}", NomeFileSetup);
                _parameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            }
        }
    }

    /// <summary>
    /// Saves settings to the XML file.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown when NomeFileSetup is not set.</exception>
    public void SalvaParametriSetup()
    {
        if (string.IsNullOrWhiteSpace(NomeFileSetup))
            throw new InvalidOperationException("NomeFileSetup is not set.");

        lock (_lock)
        {
            try
            {
                // Ensure directory exists
                var directory = Path.GetDirectoryName(NomeFileSetup);
                if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }

                // Create XML document with DataSet-compatible structure
                var doc = new XDocument(
                    new XDeclaration("1.0", "utf-8", "yes"),
                    new XElement("NewDataSet",
                        _parameters.Select(kvp =>
                            new XElement("Setup",
                                new XElement("Nome", kvp.Key),
                                new XElement("Valore", kvp.Value)
                            )
                        )
                    )
                );

                doc.Save(NomeFileSetup);
                _logger.LogInformation("Saved {Count} parameters to {FilePath}", _parameters.Count, NomeFileSetup);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to save settings to {FilePath}", NomeFileSetup);
                throw;
            }
        }
    }

    /// <summary>
    /// Reads a parameter as a string.
    /// </summary>
    /// <param name="nomeParametro">Parameter name.</param>
    /// <returns>Parameter value or empty string if not found.</returns>
    public string LeggiParametroString(string nomeParametro)
    {
        lock (_lock)
        {
            return _parameters.TryGetValue(nomeParametro, out var value) ? value : string.Empty;
        }
    }

    /// <summary>
    /// Reads a parameter as a boolean.
    /// </summary>
    /// <param name="nomeParametro">Parameter name.</param>
    /// <returns>True if value is truthy (TRUE, OK, SI, 1, YES, VERO), false otherwise.</returns>
    public bool LeggiParametroBoolean(string nomeParametro)
    {
        var raw = LeggiParametroString(nomeParametro);
        return raw.ToUpperInvariant() switch
        {
            "TRUE" or "OK" or "SI" or "1" or "YES" or "VERO" => true,
            _ => false
        };
    }

    /// <summary>
    /// Updates or creates a parameter.
    /// </summary>
    /// <param name="nomeParametro">Parameter name.</param>
    /// <param name="valoreParametro">Parameter value.</param>
    public void AggiornaParametro(string nomeParametro, object? valoreParametro)
    {
        lock (_lock)
        {
            _parameters[nomeParametro] = valoreParametro?.ToString() ?? string.Empty;
        }
    }

    /// <summary>
    /// Reads a parameter and converts it to the specified type.
    /// </summary>
    /// <typeparam name="T">Target type.</typeparam>
    /// <param name="nomeParametro">Parameter name.</param>
    /// <param name="defaultValue">Default value if conversion fails.</param>
    /// <returns>Converted value or default value.</returns>
    public T LeggiParametro<T>(string nomeParametro, T defaultValue = default!)
    {
        var raw = LeggiParametroString(nomeParametro);
        if (string.IsNullOrEmpty(raw))
            return defaultValue;

        try
        {
            // Handle nullable types
            var targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
            
            // Special handling for common types
            if (targetType == typeof(bool))
            {
                return (T)(object)LeggiParametroBoolean(nomeParametro);
            }

            return (T)Convert.ChangeType(raw, targetType, CultureInfo.InvariantCulture);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to convert parameter {ParameterName} to {TypeName}, using default value", 
                nomeParametro, typeof(T).Name);
            return defaultValue;
        }
    }

    /// <summary>
    /// Checks if a parameter exists.
    /// </summary>
    /// <param name="nomeParametro">Parameter name.</param>
    /// <returns>True if parameter exists, false otherwise.</returns>
    public bool ParametroExists(string nomeParametro)
    {
        lock (_lock)
        {
            return _parameters.ContainsKey(nomeParametro);
        }
    }

    /// <summary>
    /// Removes a parameter.
    /// </summary>
    /// <param name="nomeParametro">Parameter name.</param>
    /// <returns>True if parameter was removed, false if it didn't exist.</returns>
    public bool RimuoviParametro(string nomeParametro)
    {
        lock (_lock)
        {
            return _parameters.Remove(nomeParametro);
        }
    }

    /// <summary>
    /// Gets all parameter names.
    /// </summary>
    /// <returns>Collection of parameter names.</returns>
    public IReadOnlyCollection<string> GetParameterNames()
    {
        lock (_lock)
        {
            return _parameters.Keys.ToList();
        }
    }

    /// <summary>
    /// Clears all parameters.
    /// </summary>
    public void ClearAll()
    {
        lock (_lock)
        {
            _parameters.Clear();
        }
    }
}