using System.Reflection;
using adas_core.Application.Exceptions;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Masters;
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;

namespace adas_core.Application.Services;

public class MasterListServiceFactory(
    IServiceProvider serviceProvider,
    ILogger<MasterListServiceFactory> logger,
    IOptions<ListSettings> listSettings)
    : IMasterListServiceFactory
{
    private readonly IOptions<ListSettings> _listSettings = listSettings;
    private readonly ILogger<MasterListServiceFactory> _logger = logger;

    // Obtiene el servicio basado en MasterListType
    /// <summary>
    /// Resolves and returns a service instance for the specified master list type by dynamically constructing the generic <see cref="MasterListService{T}"/> type and retrieving the registered service.
    /// </summary>
    /// <param name="serviceName">The master list type used to resolve the corresponding service type from the adas_core.Domain.Models.Masters namespace.</param>
    /// <returns>An object representing the resolved service instance for the requested master list type.</returns>
    /// <exception cref="BadRequestException">Thrown when the type corresponding to <paramref name="serviceName"/> cannot be resolved in the adas_core.Domain.Models.Masters namespace.</exception>
    /// <exception cref="NotFoundException">Thrown when the generic service type cannot be constructed for the resolved type parameter.</exception>
    public object GetService(MasterListType serviceName)
    {
        var typeParameter = Type.GetType($"adas_core.Domain.Models.Masters.{serviceName}, adas-core.Domain") ??
                            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
        var genericTypeDef = typeof(MasterListService<>);
        var specificServiceType = genericTypeDef.MakeGenericType(typeParameter) ??
                                  throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);

        return GetService(specificServiceType);
    }

    // Obtiene el servicio basado en un Type genérico
    /// <summary>
    /// Resolves a generic <see cref="IMasterListService{T}"/> instance when the requested service type is a generic <c>MasterListService&lt;T&gt;</c>. Throws an exception if the service type is not compatible or if the underlying service cannot be resolved.
    /// </summary>
    /// <param name="serviceType">The type of the service to resolve. Must be a generic type based on <c>MasterListService&lt;&gt;</c> to be successfully handled.</param>
    /// <returns>The resolved service instance implementing <see cref="IMasterListService{T}"/> for the corresponding item type.</returns>
    /// <exception cref="System.InvalidOperationException">Thrown when the requested <paramref name="serviceType"/> is not a generic <c>MasterListService&lt;&gt;</c>, or when no implementation of <see cref="IMasterListService{T}"/> can be resolved for the requested item type.</exception>
    public object GetService(Type serviceType)
    {
        // Verifica si el tipo es genérico y está basado en MasterList
        if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(MasterListService<>))
        {
            // Obtiene el tipo genérico base
            var itemType = serviceType.GenericTypeArguments[0];

            // Construye el tipo del servicio genérico con base en el tipo T
            var specificServiceType = typeof(IMasterListService<>).MakeGenericType(itemType);

            // Obtiene el servicio utilizando el tipo construido
            var service = serviceProvider.GetService(specificServiceType);

            return service ??
                   throw new InvalidOperationException(
                       $"No se pudo resolver el servicio de tipo {serviceType.FullName}");
        }

        _logger.LogError("El tipo de servicio {serviceType} no es compatible.", serviceType.FullName);
        throw new InvalidOperationException($"El tipo de servicio {serviceType.FullName} no es compatible.");
    }

    /// <summary>
    /// Returns the specific <see cref="Type"/> associated with the given <paramref name="masterListType"/>, mapping each enumeration value to its corresponding concrete list type (e.g., <see cref="AllergyList"/>, <see cref="DiagnosisList"/>, <see cref="DoctorList"/>, etc.). If no specific mapping is found, the base <see cref="MasterList"/> type is returned as the fallback.
    /// </summary>
    /// <param name="masterListType">The enumeration value identifying which master list category to resolve to its concrete type.</param>
    /// <returns>The <see cref="Type"/> that implements the requested master list category, or <see cref="MasterList"/> when no specific type applies.</returns>
    public Type GetMasterListSpecificType(MasterListType masterListType)
    {
        // mapear cada valor de MasterListType a su respectivo tipo específico
        return masterListType switch
        {
            MasterListType.AltableOptionList => typeof(AltableOptionList),
            MasterListType.AllergyList => typeof(AllergyList),
            MasterListType.DestinationList => typeof(DestinationList),
            MasterListType.DiagnosisList => typeof(DiagnosisList),
            MasterListType.DischargeStatusList => typeof(DischargeStatusList),
            MasterListType.DoctorList => typeof(DoctorList),
            MasterListType.DoctorTypeList => typeof(DoctorTypeList),
            MasterListType.InternalDestinationList => typeof(InternalDestinationList),
            MasterListType.InsulationList => typeof(InsulationList),
            MasterListType.MobilityOptionList => typeof(MobilityOptionList),
            MasterListType.OriginList => typeof(OriginList),
            MasterListType.PatientStatusList => typeof(PatientStatusList),
            MasterListType.ProcedureList => typeof(ProcedureList),
            MasterListType.TestList => typeof(TestList),
            MasterListType.ServiceList => typeof(ServiceList),
            MasterListType.TherapeuticCeilingList => typeof(TherapeuticCeilingList),
            MasterListType.PassiveSittingList => typeof(PassiveSittingList),
            MasterListType.GenericList => typeof(GenericList),
            MasterListType.TreatmentList => typeof(TreatmentList),
            MasterListType.VisitOptionList => typeof(VisitOptionList),
            MasterListType.AccessControlList => typeof(AccessControlList),
            MasterListType.LanguageBarrierList => typeof(LanguageBarrierList),
            _ => typeof(MasterList)
        };
    }

    /// <summary>
    /// Returns a specific <see cref="MasterList"/> subtype instance resolved from <paramref name="masterListType"/>, with properties mapped from the provided <paramref name="masterList"/>. Returns null when the specific type cannot be instantiated via reflection.
    /// </summary>
    /// <param name="masterListType">The discriminator used to resolve the concrete <see cref="MasterList"/> subtype to create.</param>
    /// <param name="masterList">The source <see cref="MasterList"/> whose properties are mapped onto the created specific instance.</param>
    /// <returns>The created specific <see cref="MasterList"/> instance with mapped properties, or null if reflection failed to create the instance.</returns>
    public object? GetTypedMasterList(MasterListType masterListType, MasterList masterList)
    {
        // Obtiene el tipo específico de MasterList basado en masterListType
        var specificType = GetMasterListSpecificType(masterListType);

        // Utiliza reflexión para crear una instancia del tipo específico
        var specificMasterList = Activator.CreateInstance(specificType);
        if (specificMasterList == null)
            return null;

        // Realiza el mapeo de propiedades de masterList a specificMasterList
        MapMasterListProperties(masterList, specificMasterList);

        return specificMasterList;
    }

    /// <summary>
    /// Inserts a master list by dynamically resolving the appropriate service for the given <paramref name="masterListType"/> and invoking its <c>InsertMasterList</c> method via reflection.
    /// </summary>
    /// <param name="masterListType">The type used to resolve the target service and the specific typed master list instance.</param>
    /// <param name="masterList">The master list entity to be converted and inserted.</param>
    /// <returns>The result produced by the dynamically invoked <c>InsertMasterList</c> method, or <c>null</c> when the awaited task exposes no result value.</returns>
    /// <exception cref="InvalidOperationException">Thrown when no service is registered for the specified <paramref name="masterListType"/>, when the master list cannot be converted to its expected typed instance, or when the <c>InsertMasterList</c> method cannot be found on the resolved service.</exception>
    public async Task<object?> InsertMasterList(MasterListType masterListType, MasterList masterList)
    {
        dynamic service = GetService(masterListType) ??
                          throw new InvalidOperationException("Service not found for type " + masterListType);
        var typedMasterList = GetTypedMasterList(masterListType, masterList) ??
                              throw new InvalidOperationException("Failed to convert MasterList to specific type.");

        // Reflexión para invocar el método InsertMasterList
        var method = service.GetType().GetMethod("InsertMasterList", new[] { typedMasterList.GetType() }) ??
                     throw new InvalidOperationException("Method InsertMasterList not found.");
        var task = (Task)method.Invoke(service, new[] { typedMasterList });
        await task.ConfigureAwait(false);

        var resultProperty = task.GetType().GetProperty("Result");
        return resultProperty?.GetValue(task);
    }

    /// <summary>
    /// Updates a master list entry by dynamically resolving the appropriate service for the given <paramref name="masterListType"/> and invoking its <c>UpdateMasterList</c> method via reflection on the typed master list.
    /// </summary>
    /// <param name="masterListType">The type of master list that determines which service to resolve and which target method to invoke.</param>
    /// <param name="masterList">The master list instance to be updated, which is converted to its specific type before the service method is invoked.</param>
    /// <returns>The <c>Result</c> value of the awaited task returned by the invoked <c>UpdateMasterList</c> method, or <see langword="null"/> if the result property is not available.</returns>
    /// <exception cref="InvalidOperationException">Thrown when no service is registered for the specified <paramref name="masterListType"/>.</exception>
    /// <exception cref="InvalidOperationException">Thrown when the provided <paramref name="masterList"/> cannot be converted to its specific typed master list.</exception>
    /// <exception cref="InvalidOperationException">Thrown when the resolved service does not expose an <c>UpdateMasterList</c> method matching the typed master list argument.</exception>
    public async Task<object?> UpdateMasterList(MasterListType masterListType, MasterList masterList)
    {
        dynamic service = GetService(masterListType) ??
                          throw new InvalidOperationException("Service not found for type " + masterListType);
        var typedMasterList = GetTypedMasterList(masterListType, masterList) ??
                              throw new InvalidOperationException("Failed to convert MasterList to specific type.");

        // Reflexión para invocar el método InsertMasterList
        var method = service.GetType().GetMethod("UpdateMasterList", new[] { typedMasterList.GetType() }) ??
                     throw new InvalidOperationException("Method UpdateMasterList not found.");
        var task = (Task)method.Invoke(service, new[] { typedMasterList });
        await task.ConfigureAwait(false);

        var resultProperty = task.GetType().GetProperty("Result");
        return resultProperty?.GetValue(task);
    }

    /// <summary>
    /// Asynchronously retrieves a master list item by its identifier, using reflection to dispatch the call to the appropriate service based on the specified master list type.
    /// </summary>
    /// <param name="masterListType">The type of master list used to resolve the underlying service that will handle the request.</param>
    /// <param name="masterListId">The unique identifier of the master list item to retrieve.</param>
    /// <param name="dataLocale">An optional locale used to localize the returned data; may be <c>null</c> when localization is not required.</param>
    /// <returns>A task that resolves to the retrieved master list item as an <see cref="object"/>, or <c>null</c> when the awaited result has no value.</returns>
    /// <exception cref="System.InvalidOperationException">Thrown when no service is registered for the supplied <paramref name="masterListType"/>, or when the target service does not expose a compatible <c>GetMasterListById</c> method matching the expected parameters.</exception>
    public async Task<object?> GetMasterListById(MasterListType masterListType, ObjectId masterListId,
            LocaleEnum? dataLocale)
    {
        dynamic service = GetService(masterListType) ??
                          throw new InvalidOperationException("Service not found for type " + masterListType);

        // Reflexión para invocar el método InsertMasterList
        var method =
            service.GetType().GetMethod("GetMasterListById", new[] { typeof(ObjectId), typeof(LocaleEnum?) }) ??
            throw new InvalidOperationException("Method GetMasterListById not found.");
        var task = (Task)method.Invoke(service, new object[] { masterListId, dataLocale! });

        await task.ConfigureAwait(false);

        var resultProperty = task.GetType().GetProperty("Result");
        return resultProperty?.GetValue(task);
    }

    /// <summary>
    /// Retrieves a master list option by its identifier using reflection to dynamically resolve and invoke the
    /// appropriate service method based on the specified master list type.
    /// </summary>
    /// <param name="masterListType">The type of master list whose service implementation should be resolved.</param>
    /// <param name="masterListId">The identifier of the master list containing the option to retrieve.</param>
    /// <param name="masterListOptionId">The identifier of the specific option to look up within the master list.</param>
    /// <param name="dataLocale">Optional locale used to localize the returned data; falls back to <see cref="LocaleEnum.Default"/> when not provided.</param>
    /// <returns>A task that resolves to the located master list option as an <see cref="object"/>, or <see langword="null"/> if the underlying task produced no result.</returns>
    /// <exception cref="InvalidOperationException">Thrown when no service implementation is registered for the given <paramref name="masterListType"/>.</exception>
    /// <exception cref="InvalidOperationException">Thrown when the resolved service does not expose the expected <c>FindOptionItemById</c> method.</exception>
    public async Task<object?> GetMasterListOptionById(MasterListType masterListType, ObjectId masterListId,
            ObjectId masterListOptionId,
            LocaleEnum? dataLocale)
    {
        dynamic service = GetService(masterListType) ??
                          throw new InvalidOperationException("Service not found for type " + masterListType);

        // Reflexión para invocar el método InsertMasterList
        var method =
            service.GetType().GetMethod("FindOptionItemById",
                new[] { typeof(ObjectId), typeof(ObjectId), typeof(LocaleEnum) }) ??
            throw new InvalidOperationException("Method GetMasterListById not found.");
        var task = (Task)method.Invoke(service,
            new object[] { masterListId, masterListOptionId, dataLocale ?? LocaleEnum.Default });

        await task.ConfigureAwait(false);

        var resultProperty = task.GetType().GetProperty("Result");
        return resultProperty?.GetValue(task);
    }

    /// <summary>
    /// Retrieves the list of nurse observation names by reflecting over the configured <see cref="ListSettings"/> instance,
    /// collecting the <c>ManualObservationName</c> of every <see cref="ListSettingItem"/> property whose value is not null or empty,
    /// and appending the fixed observation "PatientIncomingData".
    /// </summary>
    /// <returns>A list of nurse observation names, including all configured manual observations and the fixed "PatientIncomingData" entry.</returns>
    public List<string> StringNurseObs()
    {
        var stringNurseObs = new List<string>();

        // 1. Obtener la instancia configurada de ListSettings
        var listSettingsInstance = _listSettings.Value;
        var listSettingsType = typeof(ListSettings);

        // 2. Obtener todas las propiedades públicas de la instancia de ListSettings
        // Estas propiedades son las que están inicializadas con ListSettingItem
        var properties = listSettingsType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (var prop in properties)
        {
            // 3. Leer el valor de la propiedad (que es un ListSettingItem) de la instancia configurada
            // prop.GetValue(listSettingsInstance) devuelve el ListSettingItem que contiene el ManualObservationName

            // 4. Extraer el ManualObservationName
            if (prop.GetValue(listSettingsInstance) is ListSettingItem listSettingItem && !string.IsNullOrEmpty(listSettingItem.ManualObservationName))
                stringNurseObs.Add(listSettingItem.ManualObservationName);
        }

        // 5. Añadir otras observaciones fijas
        // (Añadir observaciones referentes a las acciones de NursePlan)
        stringNurseObs.Add("PatientIncomingData");

        return stringNurseObs;
    }

    /// <summary>
    /// Returns a patient with its option list fields translated to the specified locale, using the master list identifiers from the provided unit.
    /// If the patient is null, returns null; if the locale is Default or the unit is null, the patient is returned unchanged.
    /// Translations are applied to single or collection <see cref="OptionList"/> properties (origin, diagnosis, visits, allergies, procedures, etc.) by resolving each option's id against the matching master list and updating its <c>Name</c>.
    /// </summary>
    /// <param name="unit">The unit whose master list identifiers are used to resolve translations for each patient field.</param>
    /// <param name="locale">The target locale for translation; when set to <c>Default</c>, no translation is performed.</param>
    /// <param name="patient">The patient whose option list values will be translated in place.</param>
    /// <returns>A task that resolves to the same <see cref="Patient"/> instance with translated option names, or null if the input patient is null.</returns>
    public async Task<Patient?> GetPatientTraslated(Unit? unit, LocaleEnum? locale, Patient? patient)
    {
        if (patient == null)
            return null;
        // Si es Default, devolvemos tal cual
        if (locale == LocaleEnum.Default || unit == null)
            return patient;

        var listMap = new List<(string field, ObjectId? listId, MasterListType type)>
            {
                ("origin", unit.OriginListId, MasterListType.OriginList),
                ("diagnosis", unit.DiagnosisListId, MasterListType.DiagnosisList),
                ("visits", unit.VisitOptionListId, MasterListType.VisitOptionList),
                ("insulation", unit.InsulationListId, MasterListType.InsulationList),
                ("languageBarrier", unit.LanguageBarrierListId, MasterListType.LanguageBarrierList),
                ("allergies", unit.AllergyListId, MasterListType.AllergyList),
                ("therapeuticCeiling", unit.TherapeuticCeilingListId, MasterListType.TherapeuticCeilingList),
                ("tests", unit.TestListId, MasterListType.TestList),
                ("procedures", unit.ProcedureListId, MasterListType.ProcedureList),
                ("treatment", unit.TreatmentListId, MasterListType.TreatmentList)
            };

        foreach (var (field, listId, masterListType) in listMap)
        {
            if (listId == null)
                continue;

            // Obtener propiedad del paciente (origin, diagnosis, procedures…)
            var prop = typeof(Patient).GetProperty(
                field,
                BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);

            if (prop == null)
                continue;

            var propValue = prop.GetValue(patient);
            if (propValue == null)
                continue;

            // var master = listObj as dynamic;
            // IEnumerable<OptionList> masterOptions = master.Options;

            // ---------------------------------------------------------
            // CASO 1 → OptionList simple
            // ---------------------------------------------------------
            if (propValue is OptionList single)
            {
                if (single.Id != null)
                {
                    var translated =
                        await GetMasterListOptionById(masterListType, listId.Value, single.Id.Value, locale);
                    if (translated is OptionList translatedSingle)
                        single.Name = translatedSingle.Name;
                }

                continue;
            }

            // ---------------------------------------------------------
            // CASO 2 → Lista de OptionList (procedures, tests, treatment…)
            // ---------------------------------------------------------
            if (propValue is IEnumerable<OptionList> list)
                foreach (var item in list)
                {
                    if (item.Id == null)
                        continue;

                    var translated = await GetMasterListOptionById(masterListType, listId.Value, item.Id.Value, locale);
                    if (translated is OptionList translatedSingle)
                        item.Name = translatedSingle.Name;
                }
        }

        return patient;
    }

    /// <summary>
    /// Maps properties from a <see cref="MasterList"/> source instance to a destination object using reflection,
    /// copying only those properties whose name and type match between both objects.
    /// </summary>
    /// <param name="source">The <see cref="MasterList"/> instance whose property values are read.</param>
    /// <param name="destination">The target object that receives the matching property values.</param>
    private void MapMasterListProperties(MasterList source, object destination)
    {
        // Obtiene las propiedades del tipo de origen y destino
        var sourceProps = source.GetType().GetProperties();
        var destProps = destination.GetType().GetProperties();

        foreach (var sourceProp in sourceProps)
            foreach (var destProp in destProps)
                if (destProp.Name == sourceProp.Name && destProp.PropertyType == sourceProp.PropertyType)
                {
                    // Asigna el valor de la propiedad de origen a la propiedad de destino
                    destProp.SetValue(destination, sourceProp.GetValue(source));
                    break;
                }
    }
}