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

namespace adas_core.Application.Services;

public class DischargeService : IDischargeService
{
    private readonly ILocalAuditService _auditService;
    private readonly IClientMessageService _clientMessageService;
    private readonly IDischargeRepository _dischargeRepository;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILogger<DischargeService> _logger;
    private readonly IMasterListServiceFactory _masterListServiceFactory;
    private readonly Lazy<IPatientService> _patientServiceLazy;
    private readonly IPointOfCareService _pointOfCareService;
    private readonly ISubscribersService _subscribersService;
    private readonly IUnitService _unitService;

    public DischargeService(ILogger<DischargeService> logger,
        ISubscribersService subscribersService,
        IDischargeRepository dischargeRepository,
        Lazy<IPatientService> patientServiceLazy,
        IClientMessageService clientMessageService,
        IPointOfCareService pointOfCareService,
        IHttpContextAccessor httpContextAccessor,
        ILocalAuditService auditService,
        IUnitService unitService,
        IMasterListServiceFactory masterListServiceFactory)
    {
        _logger = logger;
        _subscribersService = subscribersService;
        _dischargeRepository = dischargeRepository;
        _patientServiceLazy = patientServiceLazy;
        _clientMessageService = clientMessageService;
        _pointOfCareService = pointOfCareService;
        _pointOfCareService = pointOfCareService;
        _httpContextAccessor = httpContextAccessor;
        _auditService = auditService;
        _unitService = unitService;
        _masterListServiceFactory = masterListServiceFactory;
    }


    public async Task DeleteDischargeAsync(Discharge discharge)
    {
        //var patient = await _patientServiceLazy.Value.FindById(discharge.PatientId);
        //if (!discharge.MedicalDischarge.HasValue || !discharge.AdminDischarge.HasValue 
        //                                         // || DischargeStatusType.NoAltable.ToString().Equals(patient?.DischargeStatus?.OptionType)
        //    )
        //{
        //    _logger.LogError("The patient cannot be discharged");
        //    return;
        //}
        await DeleteDischargeByIdAsync(discharge.Id);
    }

    public async Task DeleteDischargeByIdAsync(ObjectId dischargeId)
    {
        try
        {
            var dischargeAux = await _dischargeRepository.FindById(dischargeId);
            if (dischargeAux == null)
            {
                _logger.LogError("Error Discharge not found, id: {discharge} ", dischargeId);
                return;
            }

            await _dischargeRepository.Delete(dischargeId);

            _logger.LogInformation("Discharge id: {dischargeId} DELETED  ", dischargeId);

            //var patient = await _patientServiceLazy.Value.FindById(dischargeAux.PatientId);
            // if (patient != null)
            //     await _patientServiceLazy.Value.ArchivePatient(patient);
            // else
            //     _logger.LogError("Patient not found on Discharge id: {discharge} ", dischargeId);

            await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, dischargeAux, null);
            SendDischargeBroadcast(dischargeAux, OperationType.DeleteDischarge);
        }
        catch (Exception ex)
        {
            _logger.LogError("Exception deleting discharge id:{admission} . Exception: {ex}", dischargeId, ex);
        }
    }

    public async Task<long> CountDischargesByUnitId(ObjectId unitId)
    {
        return await _dischargeRepository.CountByUnitId(unitId);
    }

    public async Task<Discharge?> GetDischargeByIdAsync(ObjectId dischargeId)
    {
        var result = await _dischargeRepository.FindById(dischargeId) ??
                     throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        if (result.PointOfCareId == null)
            return result;

        var poc = await _pointOfCareService.GetInfo(result.PointOfCareId.Value, null,false);
        result.PatientLocation = new PatientLocation(poc?.UnitName, poc?.Bed, poc?.Room);

        return result;
    }

    public async Task<IEnumerable<Discharge>> GetDischargesAsync()
    {
        return await _dischargeRepository.FindAll();
    }

    public async Task<Discharge?> GetDischargeByPatientId(ObjectId patientId)
    {
        try
        {
            var discharge = await _dischargeRepository.GetByPatientId(patientId);
            if (discharge == null)
                _logger.LogError("Discharge not found by patient Id {id}", patientId);
            if (discharge is { PointOfCareId: not null })
            {
                var poc = await _pointOfCareService.GetInfo(discharge.PointOfCareId.Value, null,false);
                discharge.PatientLocation = new PatientLocation(poc?.UnitName, poc?.Bed, poc?.Room);
            }

            return discharge;
        }
        catch (Exception ex)
        {
            _logger.LogError("Exception getting discharge by patient id: {id} . Exception: {ex}", patientId, ex);
            return null;
        }
    }

    public async Task<Discharge?> InsertDischarge(Discharge discharge)
    {
        await _dischargeRepository.InsertOneAsync(discharge);

        var dischargeAux = await _dischargeRepository.FindById(discharge.Id) ??
                           throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
        _logger.LogInformation("Discharge: {discharge} INSERTED", discharge);
        await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, dischargeAux);
        SendDischargeBroadcast(discharge, OperationType.NewDischarge);
        return dischargeAux;
    }

    public async Task<Discharge?> GetDischargeByLocation(PatientLocation location)
    {
        try
        {
            return await _dischargeRepository.GetDischargeByLocation(location);
        }
        catch (Exception e)
        {
            _logger.LogError($"Unable to get discharge by location on service Exception: {e}");
            return null;
        }
    }

    public async Task<Discharge?> GetDischargeByPointOfCareId(ObjectId poc)
    {
        try
        {
            var discharge = await _dischargeRepository.GetDischargeByPointOfCareId(poc);
            if (discharge == null)
                _logger.LogInformation("Discharge not found by PointOfCareId {id}", poc);
            if (discharge is { PointOfCareId: not null })
            {
                var pocInfo = await _pointOfCareService.GetInfo(discharge.PointOfCareId.Value, null,false);
                discharge.PatientLocation = new PatientLocation(pocInfo?.UnitName, pocInfo?.Bed, pocInfo?.Room);
            }

            return discharge;
        }
        catch (Exception e)
        {
            _logger.LogError($"Unable to get discharge by location on service Exception: {e}");
            return null;
        }
    }

    public async Task<Discharge?> GetDischargeByPointOfCareIdWithLocale(ObjectId location, LocaleEnum dataLocale)
    {
        var discharge = await GetDischargeByPointOfCareId(location);
        if (discharge == null) return null;
        var unit = await _unitService.FindById(discharge.UnitId);
        if (unit == null) return discharge;
        var dischargeWithLocale = await GetDischargeWithLocale(unit, discharge, dataLocale);
        return dischargeWithLocale;
    }

    public async Task UpdateDischargeAsync(Discharge discharge)
    {
        var oldDischarge = await GetDischargeByIdAsync(discharge.Id) ??
                           throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
        await _dischargeRepository.Update(discharge);
        await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldDischarge, discharge);
        SendDischargeBroadcast(discharge, OperationType.UpdateDischarge);
        _logger.LogInformation("Discharge: {discharge} UPDATED", discharge);
    }


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

            var patientId = apiRequest.Discharge.Patient.Id;

            var patient = await _patientServiceLazy.Value.FindById(patientId);
            if (patient == null)
            {
                _logger.LogError("Error discharging patient id: {id}  NOT FOUND", patientId);
                return;
            }

            await _patientServiceLazy.Value.Update(apiRequest.Discharge.Patient);

            switch (apiRequest.Type)
            {
                case "NewDischarge":
                {
                    //TODO: ver qué tipos llegan
                    if (!"Altable".Equals(apiRequest.Discharge.Patient.DischargeStatus?.OptionType))
                    {
                        _logger.LogError("Error discharging. Patient not altable: {patient}", patient);
                        return;
                    }


                    await _dischargeRepository.InsertOneAsync(apiRequest.Discharge);
                    await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null,
                        apiRequest.Discharge);
                    SendDischargeBroadcast(apiRequest.Discharge, OperationType.NewDischarge);

                    break;
                }
                case "UpdateDischarge":
                {
                    await GetDischargeByIdAsync(apiRequest.Discharge.Id);
                    await UpdateDischargeAsync(apiRequest.Discharge);
                    break;
                }
                case "DeleteDischarge":
                {
                    if (StatusEnum.Discharge.NoAltable.ToString().Equals(patient.DischargeStatus?.OptionType))
                    {
                        _logger.LogError("Error deleting discharge. Patient altable: {patient}", patient);
                        return;
                    }

                    await DeleteDischargeAsync(apiRequest.Discharge);
                    break;
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogError("Exception processing discharge api request {discharge} . Exception: {ex}",
                apiRequest.Discharge, ex);
        }
    }

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

    // Revisar
    public async void SendDischargeBroadcast(Discharge discharge, OperationType operation)
    {
        try
        {
            if (discharge.PointOfCareId == null)
            {
                _logger.LogError("Error sending discharge broadcast. Unit name is null or empty {discharge} .",
                    discharge);
                return;
            }

            var subscribersGroup = _subscribersService.GetSubscribers()
                .Where(s => s.LocationIds.Any(c => c == discharge.PointOfCareId)).GroupBy(h => h.Locale)
                .ToList();

            var unit = await _unitService.FindById(discharge.UnitId);
            foreach (var group in subscribersGroup)
            {
                var locale = group.Key ?? LocaleEnum.Default;
                IEnumerable<WsSubscriber> subscribers = group;

                foreach (var subscriber in subscribers)
                {
                    var dischargeWithLocale = await GetDischargeWithLocale(unit, discharge, locale);
                    _ = _clientMessageService.SendAsync(subscriber.Id, operation, dischargeWithLocale);
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogError("Exception sending discharge broadcast. Operation type: {op}. Exception: {ex}",
                operation.ToString(), ex);
        }
    }

    public async Task UpdatePatientMasterListItemChange(UpdateOptionMasterListDto opt, IEnumerable<Unit> unitList,
        string typeName)
    {
        var unitIds = unitList.Select(x => x.Id).ToList();
        await _dischargeRepository.GetDischargesByUnitIds(unitIds);
        var dischargeUpdatedList = await _dischargeRepository.UpdateMasterListOption(unitIds, opt, typeName);
        var updatedList = dischargeUpdatedList as Discharge[] ?? dischargeUpdatedList.ToArray();
        foreach (var discharge in updatedList)
        {
            var oldDischarge = updatedList.FirstOrDefault(dis => dis.Id == discharge.Id);
            await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldDischarge!, discharge);
            SendDischargeBroadcast(discharge, OperationType.UpdateDischarge);
        }
    }

    public async Task DeletePatientMasterListItem(OptionList opt, IEnumerable<Unit> unitList, string typeName)
    {
        var unitIds = unitList.Select(x => x.Id).ToList();
        var patientUpdatedList = await _dischargeRepository.DeleteMasterListOption(unitIds, opt, typeName);
        foreach (var discharge in patientUpdatedList)
        {
            var dischargeUpdated = await GetDischargeByIdAsync(discharge.Id);
            await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, discharge, null);
            if (dischargeUpdated != null)
                SendDischargeBroadcast(discharge, OperationType.UpdateDischarge);
        }
    }

    public async Task DeleteDischargesByUnitId(ObjectId unitId)
    {
        await _dischargeRepository.DeleteByUnitId(unitId);
    }

    private async Task<Discharge> GetDischargeWithLocale(Unit? unit, Discharge discharge, LocaleEnum locale)
    {
        if (unit == null)
            return discharge;

        if (locale == LocaleEnum.Default)
            return discharge;

        // Campos del discharge que deben traducirse
        var listMap = new List<(string field, ObjectId? listId, MasterListType type)>
        {
            ("serviceOption", unit.ServiceListId, MasterListType.ServiceList),
            ("destinationOption", unit.DestinationListId, MasterListType.DestinationList)
        };

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

            // Obtener propiedad desde DISCHARGE, no Patient
            var prop = typeof(Discharge).GetProperty(
                field,
                BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);

            if (prop == null)
                continue;

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

            // Cargar master list traducida según locale
            var listObj = await _masterListServiceFactory
                .GetMasterListById(masterListType, listId.Value, locale);

            if (listObj == null)
                continue;

            var master = listObj as dynamic;

            IEnumerable<OptionList> masterOptions = master.Options;

            // El campo puede ser OptionList simple
            if (propValue is OptionList { Id: not null } option)
            {
                var translated = masterOptions.FirstOrDefault(o => o.Id == option.Id);
                if (translated != null) option.Name = translated.Name; // Solo traducimos el nombre
            }
        }

        return discharge;
    }
}