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
    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
    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.");
    }

    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)
        };
    }

    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;
    }

    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);
    }

    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);
    }

    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);
    }

    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);
    }

    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;
    }

    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;
    }

    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;
            }
    }
}