using adas_core.Application.Exceptions;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Application.Subscriptions;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.DTO.Display;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using adas_core.Domain.Utils;
using Microsoft.AspNetCore.Http;
using MongoDB.Bson;
using MongoDB.Driver;
using Newtonsoft.Json;
using Serilog;
using DisplayConfig = adas_core.Domain.Models.MongoModels.DisplayConfig;

namespace adas_core.Application.Services;

public class DisplayConfigService(
    IDisplayConfigRepository displayConfigRepository,
    Lazy<IDisplayService> displayService,
    ISubscribersService subscribersService,
    IClientMessageService clientMessageService,
    IHttpContextAccessor httpContextAccessor,
    ILocalAuditService auditService,
    IMasterListServiceFactory masterListServiceFactory,
    IDisplayCardConfigRepository displayCardConfigRepository,
    IDisplayDetailConfigRepository displayDetailConfigRepository,
    IDisplayChartConfigRepository displayChartRepository)
    : IDisplayConfigService
{
    public async Task<List<DisplayConfig>> GetAll()
    {
        var result = await displayConfigRepository.GetAll();
        var resultToReturn = new List<DisplayConfig>();
        foreach (var config in result)
        {
            var c = await AddDisplaySectionMinimal(config);
            if (c != null) resultToReturn.Add(c);
        }

        return resultToReturn;
    }

    public async Task<PaginationResponse<DisplayConfigMinimalResponse>> GetAllCompactPaginated(PaginationFilter filter)
    {
        var result = displayConfigRepository.GetAllPaginated(filter);
        var count = await result.CountDocumentsAsync();
        var resultToReturn = new List<DisplayConfigMinimalResponse>();

        var data = await result.Skip((filter.PageNumber - 1) * filter.PageSize)
            .Limit(filter.PageSize)
            .ToCursorAsync();

        var dataList = await data.ToListAsync();
        foreach (var config in dataList)
        {
            var isInUse = await displayService.Value.IsDisplayConfigInUse(config.Id);
            resultToReturn.Add(new DisplayConfigMinimalResponse(config, isInUse));
        }


        return new PaginationResponse<DisplayConfigMinimalResponse>(resultToReturn, filter.PageNumber, filter.PageSize,
            count);
    }

    public async Task<List<DisplayConfig>> GetByType(DisplayConfigEnums.DisplayType type)
    {
        var result = await displayConfigRepository.GetByType(type);
        var resultToReturn = new List<DisplayConfig>();
        foreach (var config in result)
        {
            var c = await AddDisplaySectionMinimal(config);
            if (c != null) resultToReturn.Add(c);
        }

        return resultToReturn;
    }

    public async Task<DisplayConfig> GetById(ObjectId id)
    {
        return await AddDisplaySectionMinimal(await displayConfigRepository.GetById(id)) ??
               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
    }

    public async Task<DisplayConfig?> GetById(ObjectId? configId, ObjectId unitId,
        DisplayConfigEnums.DisplayType displayType)
    {
        if (configId.HasValue)
        {
            DisplayConfig? currentConfig;
            DisplayConfig? defaultConfig;
            try
            {
                currentConfig = await GetById(configId.Value);
            }
            catch (NotFoundException)
            {
                currentConfig = null;
            }

            try
            {
                defaultConfig = await GetDefaultByUnitIdAndType(unitId, displayType);
            }
            catch (NotFoundException)
            {
                defaultConfig = null;
            }

            if (defaultConfig != null && currentConfig != null) return currentConfig.MergeConfig(defaultConfig);

            return defaultConfig ?? currentConfig;
        }

        return await GetDefaultByUnitIdAndType(unitId, displayType);
    }

    public async Task<DisplayConfig?> InsertOne(DisplayConfig config)
    {
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, config);
        return await displayConfigRepository.InsertOneAsyncAndReturn(config);
    }

    public async Task<DisplayConfig?> InsertOneMinimal(CreateDisplayConfigDto config)
    {
        DisplayConfig newDisplayConfig;

        if (config.Type == DisplayConfigEnums.DisplayType.DisplayNurse)
            newDisplayConfig = new DisplayNurse
            {
                Hospital = config.Hospital,
                Type = config.Type
            };
        else
            newDisplayConfig = new DisplayConfig
            {
                Hospital = config.Hospital,
                Type = config.Type
            };
        newDisplayConfig.Id = ObjectId.GenerateNewId();

        var result = await displayConfigRepository.InsertOneAsyncAndReturn(newDisplayConfig);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, newDisplayConfig);
        return result;
    }

    public async Task<DisplayConfig> InsertOneTest()
    {
        var d = new DisplayNurse
        {
            Type = DisplayConfigEnums.DisplayType.DisplayNurse
        };
        await displayConfigRepository.InsertOneAsyncAndReturn(d);
        var e = new SmartDisplay
        {
            Type = DisplayConfigEnums.DisplayType.SmartDisplay
        };
        await displayConfigRepository.InsertOneAsyncAndReturn(e);
        return d;
    }

    public async Task<DisplayConfig?> UpdateConfig(ObjectId displayConfigId, object newDisplayConfig)
    {
        var baseType = JsonConvert.DeserializeObject<DisplayConfigDto>(newDisplayConfig.ToString()!);
        // var cardConfigUpdate = await UpdateDisplayCardConfig(displayConfigId, baseType);
        var oldDisplayConfig = await displayConfigRepository.GetById(displayConfigId);
        switch (baseType!.Type)
        {
            case DisplayConfigEnums.DisplayType.SmartDisplay:

                var smartConfigToReturn = await displayConfigRepository.UpdateSmartDisplay(displayConfigId,
                    JsonConvert.DeserializeObject<SmartDisplay>(newDisplayConfig.ToString()!));
                if (smartConfigToReturn != null)
                {
                    // smartConfigToReturn.CardConfig = cardConfigUpdate;
                    SendSmartDisplayConfigBroadcast(displayConfigId, smartConfigToReturn,
                        oldDisplayConfig as SmartDisplay);
                    await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
                        newDisplayConfig);
                    return smartConfigToReturn;
                }

                break;

            case DisplayConfigEnums.DisplayType.DisplayNurse:
                var obsNurseList = masterListServiceFactory.StringNurseObs();
                var displayConfigUpdate =
                    JsonConvert.DeserializeObject<DisplayNurseDto>(newDisplayConfig.ToString()!);
                var nurseConfigToReturn =
                    await displayConfigRepository.UpdateDisplayNurse(displayConfigId, displayConfigUpdate,
                        obsNurseList);
                if (nurseConfigToReturn != null)
                {
                    // nurseConfigToReturn.CardConfig = cardConfigUpdate;
                    SendDisplayConfigBroadcast(displayConfigId, OperationType.UpdateDisplayConfig, nurseConfigToReturn);
                    await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
                        nurseConfigToReturn);
                    return nurseConfigToReturn;
                }

                break;
            default:
                throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundNoMatches);
        }

        throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
    }

    public Task<bool> UpdateFieldList(ObjectId objectIdConfigDisplay, List<Field> fields)
    {
        return displayConfigRepository.UpdateFieldList(objectIdConfigDisplay, fields);
    }

    public async Task<bool> UpdateConfigColor(ObjectId objectIdConfigDisplay, ColorConfig colorConfig)
    {
        var oldDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
                               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        if (oldDisplayConfig.ColorConfig == null)
        {
            oldDisplayConfig.ColorConfig = new ColorConfig();
            await displayConfigRepository.UpdateOneAsync(objectIdConfigDisplay, oldDisplayConfig);
        }

        var result = await displayConfigRepository.UpdateConfigColor(objectIdConfigDisplay, colorConfig);
        var newDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
                               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
            newDisplayConfig);
        return result;
    }

    public async Task<bool> UpdateHeaderConfig(ObjectId objectIdConfigDisplay, HeaderConfig headerConfig)
    {
        var oldDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
                               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        var result = await displayConfigRepository.UpdateHeaderConfig(objectIdConfigDisplay, headerConfig);
        var newDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
                               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
            newDisplayConfig);
        return result;
    }

    public async Task<bool> UpdateSetHomeBanner(ObjectId objectIdConfigDisplay, List<BannerItem> bannerItems)
    {
        var oldDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
                               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        var result = await displayConfigRepository.UpdateSetHomeBanner(objectIdConfigDisplay, bannerItems);
        var newDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
                               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
            newDisplayConfig);
        return result;
    }

    public async Task<bool> UpdateBaseConfig(DisplayConfig baseConfig)
    {
        var oldDisplayConfig = await displayConfigRepository.GetById(baseConfig.Id) ??
                               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        var result = await displayConfigRepository.UpdateBaseConfig(baseConfig.Id, baseConfig);
        var newDisplayConfig = await displayConfigRepository.GetById(baseConfig.Id) ??
                               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
            newDisplayConfig);
        return result;
    }

    public async Task<bool> UpdateDisplayConfigHospitalName(ObjectId objectIdConfigDisplay, string name)
    {
        var oldDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
                               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        var result = await displayConfigRepository.UpdateDisplayConfigHospitalName(objectIdConfigDisplay, name);
        var newDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
                               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
            newDisplayConfig);
        return result;
    }

    public async Task<bool> DeleteDisplayConfig(ObjectId objectIdConfigDisplay)
    {
        var displays = await displayService.Value.GetByConfigId(objectIdConfigDisplay);
        if (displays.Count > 0)
        {
            var type = displays.First().Type;
            var defaultConfig = await GetDefaultConfig(type) ??
                                throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
            foreach (var display in displays)
                _ = await displayService.Value.UpdateConfigId(display, defaultConfig.Id) ??
                    throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
        }

        var result = await displayConfigRepository.DeleteDisplayConfig(objectIdConfigDisplay);
        if (result != null)
        {
            await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, result, null);
            return true;
        }

        return false;
    }

    public async Task<List<DisplayConfigLocationDto>> GetDisplayConfigLocations(ObjectId objectIdConfigDisplay)
    {
        return await displayService.Value.GetDisplayConfigLocations(objectIdConfigDisplay);
    }

    public async Task<List<DisplayConfigMinimalResponse>> GetAllCompact()
    {
        return await displayConfigRepository.GetAllCompact();
    }

    public async Task<DisplayConfig?> InsertOneWithTemplate(string objectId,
        DisplayConfigEnums.DisplayType configType, string? configHospital)
    {
        if (ObjectId.TryParse(objectId, out var objectIdConfigDisplay))
        {
            var template = await GetById(objectIdConfigDisplay);
            switch (configType)
            {
                case DisplayConfigEnums.DisplayType.StandarDisplay:
                    if (template is StandarDisplay standardTemplate)
                    {
                        var standarConfigg = new StandarDisplay
                            { Hospital = configHospital, Type = DisplayConfigEnums.DisplayType.StandarDisplay };
                        standarConfigg.MergeConfig(standardTemplate);
                        await InsertOne(standarConfigg);
                        return standarConfigg;
                    }

                    break;

                case DisplayConfigEnums.DisplayType.DisplayNurse:
                    if (template is DisplayNurse nurseTemplate)
                    {
                        var nurseConfig = new DisplayNurse
                            { Hospital = configHospital, Type = DisplayConfigEnums.DisplayType.DisplayNurse };
                        nurseConfig.MergeConfig(nurseTemplate);
                        await InsertOne(nurseConfig);
                        return nurseConfig;
                    }

                    break;

                case DisplayConfigEnums.DisplayType.SmartDisplay:
                    if (template is SmartDisplay smartTemplate)
                    {
                        var smartConfig = new SmartDisplay
                            { Hospital = configHospital, Type = DisplayConfigEnums.DisplayType.DisplayNurse };
                        smartConfig.MergeConfig(smartTemplate);
                        await InsertOne(smartConfig);
                        return smartConfig;
                    }

                    break;

                default:
                    return null;
            }
        }

        return null;
    }

    public async Task<bool> UpdateCardConfig(CardConfig baseConfig)
    {
        var result = await displayCardConfigRepository.UpdateOne(baseConfig);
        
        if (result.Changes > 0)
        {
            var displayConfigs = await displayConfigRepository.GetAllByCardConfigIdAndRotating(baseConfig.Id);
            foreach (var displayConfig in displayConfigs)
            {
                await displayConfigRepository.UpdateDisplayNurse(displayConfig,
                    new DisplayNurseDto() { CardConfig = result.Data }, masterListServiceFactory.StringNurseObs());
            }
            
            var displays = await displayConfigRepository.GetAllByCardConfigId(baseConfig.Id);
            foreach (var display in displays)
                SendDisplayConfigBroadcast(display, OperationType.UpdateCardDisplayConfig, result.Data);

            return true;
        }

        return false;
    }

    public async Task<bool> UpdateDetailConfig(CardDetailsConfig baseConfig)
    {
        var result = await displayDetailConfigRepository.UpdateOne(baseConfig);
        var displays = await displayConfigRepository.GetAllByCardDetailConfigId(baseConfig.Id);
        foreach (var display in displays)
            SendDisplayConfigBroadcast(display, OperationType.UpdateDetailDisplayConfig, result.Data);
        if (result.Changes > 0) return true;
        return false;
    }

    public async Task<bool> UpdateChartConfig(ChartConfig baseConfig)
    {
        var result = await displayChartRepository.UpdateOne(baseConfig);
        if (result.Changes > 0) return true;
        return false;
    }

    public async Task<bool> DeleteChartConfig(ObjectId objectIdConfigDisplay)
    {
        var res = await displayChartRepository.DeleteOne(objectIdConfigDisplay);
        if (res != null)
        {
            await UpdateDeletedChartConfig(objectIdConfigDisplay);
            return true;
        }

        return false;
    }

    public async Task<ChartConfig?> GetChartConfig(ObjectId objectIdConfigChart)
    {
        return await displayChartRepository.GetById(objectIdConfigChart);
    }

    public async Task<CardDetailsConfig?> InsertCardDetailConfig(CreateDisplayConfigCardDto updateDisplayConfigNameDto)
    {
        if (updateDisplayConfigNameDto.DetailConfig == null) return null;
        var result =
            await displayDetailConfigRepository.InsertOneAsyncAndReturn(updateDisplayConfigNameDto.DetailConfig);
        if (updateDisplayConfigNameDto.DisplayConfigId != null && result != null)
        {
            var res = await UpdateDetailConfigId(updateDisplayConfigNameDto.DisplayConfigId, result.Id);
            if (res)
                SendDisplayConfigBroadcast(updateDisplayConfigNameDto.DisplayConfigId.Value,
                    OperationType.UpdateDetailDisplayConfig, result);
        }

        return result;
    }

    public async Task<ChartConfig?> InsertChartConfig(CreateDisplayConfigCardDto updateDisplayConfigNameDto)
    {
        if (updateDisplayConfigNameDto.ChartConfig == null) return null;
        var result = await displayChartRepository.InsertOneAsyncAndReturn(updateDisplayConfigNameDto.ChartConfig);
        if (updateDisplayConfigNameDto.DisplayConfigId != null && result != null)
        {
            var res = await AddChartId(updateDisplayConfigNameDto.DisplayConfigId, result.Id);
            if (res)
                SendDisplayConfigBroadcast(updateDisplayConfigNameDto.DisplayConfigId.Value,
                    OperationType.UpdateChartConfig, result);
        }

        return result;
    }

    public async Task<CardConfig?> InsertCardConfig(CreateDisplayConfigCardDto updateDisplayConfigNameDto)
    {
        if(updateDisplayConfigNameDto.CardConfig == null) return null;
        var result = await displayCardConfigRepository.InsertOneAsyncAndReturn(updateDisplayConfigNameDto.CardConfig);
        if (updateDisplayConfigNameDto.DisplayConfigId != null && result != null)
        {
            var res = await UpdateCardConfigId(updateDisplayConfigNameDto.DisplayConfigId, result.Id);
            if (res)
                SendDisplayConfigBroadcast(updateDisplayConfigNameDto.DisplayConfigId.Value,
                    OperationType.UpdateDetailDisplayConfig, result);
        }

        return result;
    }

    public async Task<List<CardConfig>> GetCardConfigAll()
    {
        return await displayCardConfigRepository.GetAll();
    }

    public async Task<CardConfig?> GetCardConfigById(ObjectId id)
    {
        return await displayCardConfigRepository.GetById(id);
    }

    public async Task<DisplayConfig?> GetDefaultConfig(DisplayConfigEnums.DisplayType type)
    {
        return await displayConfigRepository.GetDefault(type);
    }

    private async Task<DisplayConfig?> GetDefaultByUnitIdAndType(ObjectId unitId,
        DisplayConfigEnums.DisplayType displayType)
    {
        return
            await displayConfigRepository.GetDefaultByUnitIdAndType(unitId,
                displayType); //?? throw new NotFoundException(ErrorMessage.NotFound_ResourceMissing);
    }

    private async Task<DisplayConfig?> AddDisplaySectionMinimal(DisplayConfig? displayConfig)
    {
        if (displayConfig == null) return null;
        var displayConfigAux = displayConfigRepository.GetById(displayConfig.Id) ??
                               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        if (displayConfig.Type == DisplayConfigEnums.DisplayType.SmartDisplay &&
            !displayConfig.DisplaySectionIdList.IsNullOrEmpty())
        {
            foreach (var displayId in displayConfig.DisplaySectionIdList)
            {
                var d = await displayService.Value.GetById(displayId);
                if (d != null)
                {
                    var minimalDisplay = new MinimalDisplaySection
                    {
                        Id = displayId,
                        Name = d.Name
                    };
                    displayConfig.DisplaySectionList.Add(minimalDisplay);
                }
            }

            await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, displayConfigAux,
                displayConfig);
        }

        return displayConfig;
    }

    private async Task UpdateDeletedChartConfig(ObjectId deletedId)
    {
        await displayConfigRepository.UpdateDeletedChartConfig(deletedId);
    }

    private async Task<bool> AddChartId(ObjectId? displayConfigId, ObjectId resultId)
    {
        var result = await displayConfigRepository.AddChartId(displayConfigId, resultId);
        return result;
    }

    private async Task<bool> UpdateCardConfigId(ObjectId? displayConfigId, ObjectId? resultId)
    {
        var result = await displayConfigRepository.UpdateCardConfigId(displayConfigId, resultId);
        return result;
    }

    private async Task<bool> UpdateDetailConfigId(ObjectId? displayConfigId, ObjectId? resultId)
    {
        var result = await displayConfigRepository.UpdateDetailConfigId(displayConfigId, resultId);
        return result;
    }

    private async void SendDisplayConfigBroadcast(ObjectId displayConfigId, OperationType operationType,
        object? newDisplayConfig)
    {
        try
        {
            var listDisplayId = await displayService.Value.GetByConfigId(displayConfigId);
            var listDisplayIdList = listDisplayId.Select(c => c.Id).ToList();
            var subscribers = subscribersService.GetSubscribers().Where(s =>
                s.DisplayId != null && listDisplayIdList.Contains((ObjectId)s.DisplayId)).ToList();

            foreach (var sub in subscribers)
                _ = clientMessageService.SendAsync(sub.Id, operationType,
                    newDisplayConfig);
        }
        catch (Exception e)
        {
            Log.Error("Error sending update for DisplayNurse config: {message}", e.Message);
            //throw new ConflictException(ErrorMessage.Conflict_UpdateFailed, e);
        }
    }

    private async void SendSmartDisplayConfigBroadcast(ObjectId displayConfigId, SmartDisplay? newDisplayConfig,
        SmartDisplay? oldDisplayConfig)
    {
        try
        {
            var listDisplayId = await displayService.Value.GetByConfigId(displayConfigId);
            var listDisplayIdList = listDisplayId.Select(c => c.Id).ToList();
            var subscribers = subscribersService.GetSubscribers().Where(s =>
                s.DisplayId != null && listDisplayIdList.Contains((ObjectId)s.DisplayId)).ToList();

            if (newDisplayConfig != null && oldDisplayConfig != null)
                SendSmartDisplayConfigUpdate(subscribers, oldDisplayConfig, newDisplayConfig);
            else Log.Error("Error sending update for SmartDisplay config config is null");
        }
        catch (Exception e)
        {
            Log.Error("Error sending update for SmartDisplay config: {message}", e.Message);
            //throw new ConflictException(ErrorMessage.Conflict_UpdateFailed, e);
        }
        finally
        {
            // Enviar un mensaje a todos los clientes para que actualicen la configuraci�n del display
            var displayConfig = await displayConfigRepository.GetById(displayConfigId);
            if (displayConfig != null)
                _ = clientMessageService.SendToAllAsync(OperationType.UpdateDisplayConfig, displayConfig);
        }
    }

    private void SendSmartDisplayConfigUpdate(List<WsSubscriber> subscribers,
        SmartDisplay? oldDisplayDisplayConfig, SmartDisplay? newDisplayDisplayConfig)
    {
        // Obtener las propiedades que han cambiado
        var differentProperties = oldDisplayDisplayConfig?.GetDifferentProperties(newDisplayDisplayConfig);

        // Enviar un mensaje a los clientes por cada propiedad que haya cambiado
        if (differentProperties != null)
            foreach (var property in differentProperties)
            foreach (var sub in subscribers)
                _ = clientMessageService.SendAsync(sub.Id, OperationType.UpdateDisplayConfig,
                    newDisplayDisplayConfig?.GetType().GetProperty(property)?.GetValue(newDisplayDisplayConfig));
    }
}