using System;
using System.Reflection;
using System.Threading.Tasks;
using System.Xml.Serialization;
using ImageCatalog;
using ImageCatalog_2.Models;
using Microsoft.Extensions.Logging;

namespace ImageCatalog_2.Services
{
    /// <summary>
    /// Modern settings service that uses DTO with attributes for serialization
    /// </summary>
    public class SettingsService : ISettingsService
    {
        private readonly ParametriSetup _parametriSetup;
        private readonly ILogger<SettingsService> _logger;

        public SettingsService(ParametriSetup parametriSetup, ILogger<SettingsService> logger)
        {
            _parametriSetup = parametriSetup;
            _logger = logger;
        }

        public Task SaveSettingsAsync(string filePath, object settings)
        {
            // Use a dedicated ParametriSetup instance for the target settings file so the injected
            // user preferences store is not modified. This keeps user prefs (singleton) intact.
            return Task.Run(() =>
            {
                var fileParams = new ParametriSetup(filePath);

                // Convert ViewModel to DTO
                var dto = ViewModelToDto(settings);

                // Use reflection on DTO properties with XmlElement attribute
                var properties = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance);

                foreach (var prop in properties)
                {
                    var xmlAttr = prop.GetCustomAttribute<XmlElementAttribute>();
                    if (xmlAttr == null)
                        continue;

                    var xmlName = xmlAttr.ElementName;
                    var value = prop.GetValue(dto);

                    fileParams.AggiornaParametro(xmlName, value);
                }

                fileParams.SalvaParametriSetup();
            });
        }

        public async Task LoadSettingsAsync(string filePath, object settings)
        {
            // Step 1: Load XML into a temporary ParametriSetup so we don't mutate the injected
            // user-preferences instance (singleton).
            var dto = await Task.Run(() =>
            {
                var fileParams = new ParametriSetup(filePath);

                var loadedDto = new SettingsDto();
                var properties = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance);

                foreach (var prop in properties)
                {
                    // Get the XmlElement attribute to determine the XML property name
                    var xmlAttr = prop.GetCustomAttribute<XmlElementAttribute>();
                    if (xmlAttr == null)
                        continue; // Skip properties without XmlElement attribute

                    var xmlName = xmlAttr.ElementName;

                    try
                    {
                        object value;
                    if (prop.PropertyType == typeof(string))
                        {
                            value = fileParams.LeggiParametroString(xmlName);
                        }
                        else if (prop.PropertyType == typeof(bool))
                        {
                            value = fileParams.LeggiParametroBoolean(xmlName);
                        }
                    else if (prop.PropertyType.IsEnum)
                    {
                        // Read enum as string from XML and try to parse to the enum type. Fall back to default.
                        var str = fileParams.LeggiParametroString(xmlName);
                        try
                        {
                            var enumValue = Enum.Parse(prop.PropertyType, str ?? string.Empty, true);
                            value = enumValue;
                        }
                        catch
                        {
                            // try numeric
                            var intVal = fileParams.LeggiParametro<int>(xmlName, (int)(prop.GetValue(loadedDto) ?? 0));
                            value = Enum.ToObject(prop.PropertyType, intVal);
                        }
                    }
                        else if (prop.PropertyType == typeof(int))
                        {
                            value = fileParams.LeggiParametro<int>(xmlName, (int)(prop.GetValue(loadedDto) ?? 0));
                        }
                        else if (prop.PropertyType == typeof(double))
                        {
                            value = fileParams.LeggiParametro<double>(xmlName, (double)(prop.GetValue(loadedDto) ?? 0.0));
                        }
                        else if (prop.PropertyType == typeof(DateTime))
                        {
                            var strValue = fileParams.LeggiParametroString(xmlName);
                            if (DateTime.TryParse(strValue, out var dateValue))
                            {
                                value = dateValue;
                            }
                            else
                            {
                                value = (DateTime)(prop.GetValue(loadedDto) ?? DateTime.Now);
                            }
                        }
                        else
                        {
                            continue;
                        }

                        prop.SetValue(loadedDto, value);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Failed to read property {XmlName} -> {PropertyName}", xmlName, prop.Name);
                    }
                }

                return loadedDto;
                // End of LoadSettingsAsync method
            });

            // Step 2: Apply DTO values to ViewModel on UI thread
            DtoToViewModel(dto, settings);
        }

        /// <summary>
        /// Converts DataModel (ViewModel) to SettingsDto
        /// </summary>
        private SettingsDto ViewModelToDto(object viewModel)
        {
            var vmType = viewModel.GetType();
            var dto = new SettingsDto();
            var dtoProps = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance);

            foreach (var dtoProp in dtoProps)
            {
                // Find matching property in ViewModel by name
                var vmProp = vmType.GetProperty(dtoProp.Name, BindingFlags.Public | BindingFlags.Instance);
                if (vmProp != null && vmProp.CanRead)
                {
                    var value = vmProp.GetValue(viewModel);
                    dtoProp.SetValue(dto, value);
                }
            }

            return dto;
        }

        /// <summary>
        /// Copies values from SettingsDto to DataModel (ViewModel)
        /// </summary>
        private void DtoToViewModel(SettingsDto dto, object viewModel)
        {
            var vmType = viewModel.GetType();
            var dtoProps = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance);

            foreach (var dtoProp in dtoProps)
            {
                // Find matching property in ViewModel by name
                var vmProp = vmType.GetProperty(dtoProp.Name, BindingFlags.Public | BindingFlags.Instance);
                if (vmProp != null && vmProp.CanWrite)
                {
                    var value = dtoProp.GetValue(dto);
                    
                    // Don't update if empty string and current value is not empty
                    if (dtoProp.PropertyType == typeof(string) && string.IsNullOrEmpty((string)value))
                    {
                        var currentValue = vmProp.GetValue(viewModel) as string;
                        if (!string.IsNullOrEmpty(currentValue))
                        {
                            continue;
                        }
                    }

                    vmProp.SetValue(viewModel, value);
                    _logger.LogDebug("Set {PropertyName} = {Value}", vmProp.Name, value);
                }
            }
        }
    }
}
