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.DTO;
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;

namespace adas_core.Application.Services;

public class DeviceService : IDeviceService
{
    private readonly IDeviceRepository _deviceRepository;
    private readonly IObservationService _observationService;
    private readonly IAlarmService _alarmService;
    private readonly IConfigObservationService _configObservationService;
    private readonly IPointOfCareService _pointOfCareService;
    private readonly ILogger<DeviceService> _logger;
    
    public DeviceService(
        IDeviceRepository deviceRepository,
        IPointOfCareService pointOfCareService,
        ILogger<DeviceService> logger,
        IObservationService observationService,
        IConfigObservationService configObservationService,
        IAlarmService alarmService)
    {
        _deviceRepository = deviceRepository;
        _pointOfCareService = pointOfCareService;
        _logger = logger;
        _observationService = observationService;
        _configObservationService = configObservationService;
        _alarmService = alarmService;
    }

    public Device ToEntity(DeviceDto dto)
    {
        return new Device()
        {
            DeviceType = dto.DeviceType,
            MacAddr = dto.MacAddr,
            SerialNumber = dto.SerialNumber,
            Name = dto.Name,
            Battery = dto.Battery,
            Color = dto.Color,
            Connected = dto.Connected,
            Ready = dto.Ready,
            Uuid = dto.Uuid,
            Key = dto.Key,
            CreatedAt =  dto.CreatedAt,
            UpdatedAt = dto.UpdatedAt,
            PointOfCareIds = dto.PointOfCareIds ?? new List<ObjectId>(),
            Settings = dto.Settings ?? new DeviceSettings()
        };
    } 
    public async Task<Device?> Create(DeviceDto deviceDto)
    {
        var device = ToEntity(deviceDto);
        device.CreatedAt = DateTime.UtcNow;
        device.UpdatedAt = DateTime.UtcNow;
        await _deviceRepository.InsertOneAsync(device);
        return device;
    }

    public async Task<bool> Delete(ObjectId objectId)
    {
        return await _deviceRepository.DeleteAsync(objectId) != null;
    }

    public async Task<Device?> Update(DeviceDto deviceDto)
    {
        var device = ToEntity(deviceDto);
        device.UpdatedAt = DateTime.UtcNow;
        await _deviceRepository.UpdateOneAsync(device.Id, device);
        return device;
    }

    public async Task<Device?> ReceiveEvent(DeviceDto deviceDto)
    {
        Device? deviceExist = null;
        if (!string.IsNullOrEmpty(deviceDto.MacAddr))
        {
            deviceExist = await _deviceRepository.FindByMacAddr(deviceDto.MacAddr);
        }

        if (deviceExist == null && deviceDto.SerialNumber != null)
        {
            deviceExist = await _deviceRepository.FindBySerialNumber(deviceDto.SerialNumber);
        }

        if (deviceExist == null && deviceDto.Uuid != null)
        {
            deviceExist = await _deviceRepository.FindByUuid(deviceDto.Uuid);
        }

        if (deviceExist == null && deviceDto.Key != null)
        {
            deviceExist = await _deviceRepository.FindByKey(deviceDto.Key);
        }

        if (deviceExist == null)
        {
            deviceExist = ToEntity(deviceDto);
            deviceExist.CreatedAt = DateTime.UtcNow;
            deviceExist.UpdatedAt = DateTime.UtcNow;
            await _deviceRepository.InsertOneAsync(deviceExist);
        }
        else
        {
            await _deviceRepository.UpdateDeviceStats(deviceExist.Id, deviceDto);
        }

        switch (deviceDto.DeviceType)
        {
            case DeviceType.Unknown:
                break;
            case DeviceType.Button:
                await ManageDeviceButton(deviceExist, deviceDto);
                break;
        }
        
        return deviceExist;
    }

    private async Task ManageDeviceButton(Device deviceExist, DeviceDto deviceDto)
    {
        if (deviceExist.Settings?.Action?.Type != null && deviceDto.Event?.ClickType != null)
        {
            switch (deviceExist.Settings.Action.Type)
            {
                case DeviceActionType.SendObs:
                    await SendObservationOnAction(deviceExist.Settings.Action, deviceDto.Event.ClickType.Value, deviceExist.PointOfCareIds);
                    break;
                case DeviceActionType.SendAlarm:
                    await SendAlarmOnAction(deviceExist.Settings.Action, deviceDto.Event.ClickType.Value, deviceExist.PointOfCareIds);
                    break;
            }
        }
    }

    private async Task SendAlarmOnAction(
        DeviceAction settingsAction,
        ClickType eventClickType,
        List<ObjectId> deviceExistPointOfCareIds)
    {
        var configObs = await _configObservationService.GetConfigById(settingsAction.ConfigObservationId);
        if(configObs == null) return;
        var obsData = new ObservationData
        {
            Code = configObs.Code,
            CodingSystem = configObs.CodingSystem,
            Time = DateTime.UtcNow,
            Text = configObs.Name,
        };
        var obs = new PatientObservationAlarm
        {
            InactivationState = new InactivationState{ Visual = AlarmEnum.AudioVideoState.Enabled, Audio = AlarmEnum.AudioVideoState.Enabled},
            MessageTime = DateTime.UtcNow,
            Persist = true,
            Code = obsData.Code,
            CodingSystem = obsData.CodingSystem,
            Name = configObs.Name,
            Time =  DateTime.UtcNow
        };
        foreach (var pocId in deviceExistPointOfCareIds)
        {
            var data = await _pointOfCareService.GetInfo(pocId, null, true);
            if (data != null && data.Patient?.Id != null)
            {
                obs.PatientId = data.Patient.Id;
                obs.Patient = data.Patient;
                switch (eventClickType)
                {
                    case ClickType.SingleClick:
                        if(settingsAction.ValueOnSingleClick == null) return;
                        obs.Value = settingsAction.ValueOnSingleClick;
                        break;
                    case ClickType.DoubleClick:
                        if(settingsAction.ValueOnDoubleClick == null) return;
                        obs.Value = settingsAction.ValueOnDoubleClick;
                        break;
                    case ClickType.Hold:
                        if(settingsAction.ValueOnHoldClick == null) return;
                        obs.Value = settingsAction.ValueOnHoldClick;
                        break;
                }
                // Process Obs on service
                await _alarmService.ProcessAlarmObservations(new List<PatientObservationAlarm>{obs},new List<PatientObservation>(){new (){Name = configObs.Name, Value = obs.Value, Code = obsData.Code, CodingSystem = obsData.CodingSystem}}, data.Patient, DateTime.UtcNow, obsData);
            }

        }
    }

    private async Task SendObservationOnAction(
        DeviceAction settingsAction,
        ClickType eventClickType,
        List<ObjectId> deviceExistPointOfCareIds)
    {
        var configObs = await _configObservationService.GetConfigById(settingsAction.ConfigObservationId);
        if(configObs == null) return;
        var obsData = new ObservationData
        {
            Code = configObs.Code,
            CodingSystem = configObs.CodingSystem,
            Time = DateTime.UtcNow,
            Text = configObs.Name,
        };
        var obs = new PatientObservation
        {
            MessageTime = DateTime.UtcNow,
            Persist = true,
            Code = obsData.Code,
            CodingSystem = obsData.CodingSystem,
            Name = configObs.Name,
            Time =  DateTime.UtcNow
        };
        foreach (var pocId in deviceExistPointOfCareIds)
        {
            var data = await _pointOfCareService.GetInfo(pocId, null, true);
            if (data != null && data.Patient?.Id != null)
            {
                obs.PatientId = data.Patient.Id;
                obs.Patient = data.Patient;
                switch (eventClickType)
                {
                    case ClickType.SingleClick:
                        if(settingsAction.ValueOnSingleClick == null) return;
                        obs.Value = settingsAction.ValueOnSingleClick;
                        break;
                    case ClickType.DoubleClick:
                        if(settingsAction.ValueOnDoubleClick == null) return;
                        obs.Value = settingsAction.ValueOnDoubleClick;
                        break;
                    case ClickType.Hold:
                        if(settingsAction.ValueOnHoldClick == null) return;
                        obs.Value = settingsAction.ValueOnHoldClick;
                        break;
                }
                // Process Obs on service
                _observationService.ProcessObservations(new List<PatientObservation>{obs}, data.Patient, DateTime.UtcNow, obsData);
            }

        }
    }
}