using adas_core.Application.Exceptions;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.MongoModels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;

namespace adas_core.Application.Services;

public class NoticeService(
    ILogger<NoticeService> logger,
    ISubscribersService subscribersService,
    INoticeRepository noticeRepository,
    IClientMessageService clientMessageService,
    IDisplayService displayService,
    IHttpContextAccessor httpContextAccessor,
    ILocalAuditService auditService)
    : INoticeService
{
    public async Task DeleteNoticeAsync(Notice notice)
    {
        await DeleteNoticeByIdAsync(notice.Id);
    }

    public async Task DeleteNoticeByIdAsync(ObjectId noticeId)
    {
        try
        {
            var noticeAux = await noticeRepository.FindById(noticeId);
            if (noticeAux == null)
            {
                logger.LogInformation("Error deleting Notice not found, id: {noticeId} NOT DELETED  ", noticeId);
                return;
            }

            await noticeRepository.Delete(noticeId);
            await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, noticeAux, null);
            logger.LogInformation("Notice id: {noticeId} DELETED  ", noticeId);

            SendNoticeBroadcast(noticeAux, OperationType.DeleteNotice);
        }
        catch (Exception ex)
        {
            logger.LogError("Exception deleting notice id:{notice} . Exception: {ex}", noticeId, ex);
        }
    }

    public async Task<Notice?> GetNoticeByIdAsync(ObjectId noticeId)
    {
        try
        {
            return await noticeRepository.FindById(noticeId);
        }
        catch (Exception ex)
        {
            logger.LogError("Exception deleting notice id:{notice} . Exception: {ex}", noticeId, ex);
            return null;
        }
    }

    public async Task<IEnumerable<Notice>?> GetNoticeByTypeAsync(string noticeType)
    {
        return await noticeRepository.FindByType(noticeType) ??
               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
    }

    public async Task<IEnumerable<Notice>> GetNoticesAsync()
    {
        return await noticeRepository.FindAll();
    }

    public async Task<Notice?> InsertNotice(Notice notice)
    {
        try
        {
            notice.Id = new ObjectId();
            await noticeRepository.InsertOneAsync(notice);
            await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, notice);
            logger.LogInformation("Notice: {notice} INSERTED", notice);

            SendNoticeBroadcast(notice, OperationType.NewNotice);

            return notice;
        }
        catch (Exception ex)
        {
            logger.LogError("Exception inserting notice {notice} . Exception: {ex}", notice, ex);
            return null;
        }
    }

    public async Task UpdateNoticeAsync(Notice notice)
    {
        try
        {
            var auxNotice = await noticeRepository.FindById(notice.Id) ??
                            throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
            await noticeRepository.Update(notice);
            await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, auxNotice, notice);
            SendNoticeBroadcast(notice, OperationType.UpdateNotice);
        }
        catch (Exception ex)
        {
            logger.LogError("Exception updating notice {notice} . Exception: {ex}", notice, ex);
        }
    }

    public async Task SaveRequest(ApiRequest apiRequest)
    {
        try
        {
            if (apiRequest.Notice == null)
                return;

            switch (apiRequest.Type)
            {
                case "NewNotice":
                {
                    if (string.IsNullOrEmpty(apiRequest.Notice.Description))
                    {
                        logger.LogDebug("Error saving notice api request. Some values are required. Notice: {notice}",
                            apiRequest.Notice);
                        return;
                    }

                    await InsertNotice(apiRequest.Notice);
                    break;
                }
                case "UpdateNotice":
                {
                    if (string.IsNullOrEmpty(apiRequest.Notice.Description))
                    {
                        logger.LogDebug("Error updating notice api request. Some values are required. Notice: {notice}",
                            apiRequest.Notice);
                        return;
                    }

                    await UpdateNoticeAsync(apiRequest.Notice);
                    break;
                }
                case "DeleteNotice":
                {
                    await DeleteNoticeAsync(apiRequest.Notice);
                    break;
                }
            }
        }
        catch (Exception ex)
        {
            logger.LogError("Exception updating notice {notice} . Exception: {ex}", apiRequest.Notice, ex);
        }
    }

    public Task SaveRequestAsync(ApiRequest apiRequest)
    {
        return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
    }

    public async Task<IEnumerable<Notice>?> GetNoticesByDisplayId(ObjectId displayId)
    {
        try
        {
            return await noticeRepository.FindByDisplayId(displayId);
        }
        catch (Exception e)
        {
            logger.LogError("Unable to get notice by unit on service Exception: {e}", e);
            return null;
        }
    }

    private async void SendNoticeBroadcast(Notice notice, OperationType operation)
    {
        try
        {
            var display = await displayService.GetById(notice.DisplayId);

            if (display == null)
            {
                logger.LogError("Error sending notice broadcast. display is null or empty. Notice: {notice}", notice);
                return;
            }

            var subscribers = subscribersService.GetSubscribers().Where(s => s.DisplayId == display.Id).ToList();

            foreach (var subscriber in subscribers)
                _ = clientMessageService.SendAsync(subscriber.Id, operation, notice);
        }
        catch (Exception ex)
        {
            logger.LogError("Exception sending notice broadcast. Operation type: {op}. Exception: {ex}",
                operation.ToString(), ex);
        }
    }
}