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

namespace adas_core.Application.Services;

public class UnitService(
    IUnitRepository unitRepository,
    Lazy<IPatientService> patientService,
    ILogger<UnitService> logger,
    IMasterListServiceFactory masterListServiceFactory,
    ISubscribersService subscribersService,
    Lazy<IClientMessageService> clientMessageService,
    IPointOfCareService pointOfCareService,
    IHttpContextAccessor httpContextAccessor,
    ILocalAuditService auditService)
    : IUnitService
{
    public async Task<List<Unit>> GetAll(bool withPoCs = false)
    {
        var units = await unitRepository.GetAll();

        if (withPoCs)
            foreach (var unit in units)
            {
                var pocList = await pointOfCareService.FindAllByUnitId(unit.Id);
                unit.PointOfCares = pocList?.ToList();
            }

        return units;
    }

    public async Task<List<UnitInfoDto>> GetAllCompact()
    {
        var units = await unitRepository.GetAll();
        var result = new List<UnitInfoDto>();
        foreach (var unit in units)
            result.Add(new UnitInfoDto
            {
                Id = unit.Id,
                Name = unit.Name ?? string.Empty,
                Title = unit.Title ?? string.Empty
            });
        return result;
    }

    public async Task<UnitInfoDto> GetOneCompact(ObjectId id)
    {
        var unit = await unitRepository.FindById(id);
        var result = new UnitInfoDto
        {
            Id = unit?.Id,
            Name = unit?.Name ?? string.Empty,
            Title = unit?.Title ?? string.Empty
        };
        return result;
    }

    public async Task<PaginationResponse<Unit>> GetPaginatedUnits(PaginationFilter filter, bool withPoCs = false)
    {
        var result = unitRepository.GetPaginatedUnits(filter);

        var count = await result.CountDocumentsAsync();

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


        if (withPoCs && data.Any())
        {
            var unitIds = data.Select(u => u.Id).ToList();
            var allPocsForUnits = await pointOfCareService.FindAllByUnitIds(unitIds);

            foreach (var unit in data) unit.PointOfCares = allPocsForUnits.Where(poc => poc.UnitId == unit.Id).ToList();
        }

        return new PaginationResponse<Unit>(data, filter.PageNumber, filter.PageSize, count);
    }

    // public Task<Unit?> GetByPointOfCare(PointOfCare pointOfCare)
    // {
    //     return _unitRepository.FindByPointOfCare(pointOfCare);
    // }

    public async Task<Unit?> GetInfo(ObjectId id, LocaleEnum? dataLocale, bool fillLists = true, bool withPoCs = false)
    {
        var unit = await Get(id.ToString()) ??
                   throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        //if (unit == null )// || unit.PointOfCares == null tiene sentido?
        //{
        //    _logger.LogError("Section not found: {id}", id);
        //    return null;
        //}

        if (fillLists)
        {
            if (unit.AltableOptionListId.HasValue)
                unit.AltableOptionList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.AltableOptionList,
                        unit.AltableOptionListId.Value, dataLocale) as AltableOptionList;
            if (unit.AllergyListId.HasValue)
                unit.AllergyList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.AllergyList,
                        unit.AllergyListId.Value, dataLocale) as AllergyList;
            if (unit.DestinationListId.HasValue)
                unit.DestinationList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.DestinationList,
                        unit.DestinationListId.Value, dataLocale) as DestinationList;
            if (unit.InternalDestinationListId.HasValue)
                unit.InternalDestinationList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.InternalDestinationList,
                        unit.InternalDestinationListId.Value, dataLocale) as InternalDestinationList;
            if (unit.DiagnosisListId.HasValue)
                unit.DiagnosisList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.DiagnosisList,
                        unit.DiagnosisListId.Value, dataLocale) as DiagnosisList;
            if (unit.DischargeStatusListId.HasValue)
                unit.DischargeStatusList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.DischargeStatusList,
                        unit.DischargeStatusListId.Value, dataLocale) as DischargeStatusList;
            if (unit.DoctorListId.HasValue)
                unit.DoctorList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.DoctorList, unit.DoctorListId.Value,
                        dataLocale) as DoctorList;
            if (unit.DoctorTypeListId.HasValue)
                unit.DoctorTypeList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.DoctorTypeList,
                        unit.DoctorTypeListId.Value, dataLocale) as DoctorTypeList;
            if (unit.InsulationListId.HasValue)
                unit.InsulationList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.InsulationList,
                        unit.InsulationListId.Value, dataLocale) as InsulationList;
            if (unit.MobilityOptionListId.HasValue)
                unit.MobilityOptionList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.MobilityOptionList,
                        unit.MobilityOptionListId.Value, dataLocale) as MobilityOptionList;
            if (unit.OriginListId.HasValue)
                unit.OriginList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.OriginList, unit.OriginListId.Value,
                        dataLocale) as OriginList;
            if (unit.PatientStatusListId.HasValue)
                unit.PatientStatusList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.PatientStatusList,
                        unit.PatientStatusListId.Value, dataLocale) as PatientStatusList;
            if (unit.ProcedureListId.HasValue)
                unit.ProcedureList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.ProcedureList,
                        unit.ProcedureListId.Value, dataLocale) as ProcedureList;
            if (unit.TestListId.HasValue)
                unit.TestList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.TestList, unit.TestListId.Value,
                        dataLocale) as TestList;
            if (unit.ServiceListId.HasValue)
                unit.ServiceList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.ServiceList,
                        unit.ServiceListId.Value, dataLocale) as ServiceList;
            if (unit.TherapeuticCeilingListId.HasValue)
                unit.TherapeuticCeilingList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.TherapeuticCeilingList,
                        unit.TherapeuticCeilingListId.Value, dataLocale) as TherapeuticCeilingList;
            if (unit.TreatmentListId.HasValue)
                unit.TreatmentList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.TreatmentList,
                        unit.TreatmentListId.Value, dataLocale) as TreatmentList;
            if (unit.VisitOptionListId.HasValue)
                unit.VisitOptionList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.VisitOptionList,
                        unit.VisitOptionListId.Value, dataLocale) as VisitOptionList;
            if (unit.AccessControlListId.HasValue)
                unit.AccessControlList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.AccessControlList,
                        unit.AccessControlListId.Value, dataLocale) as AccessControlList;
            if (unit.LanguageBarrierListId.HasValue)
                unit.LanguageBarrierList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.LanguageBarrierList,
                        unit.LanguageBarrierListId.Value, dataLocale) as LanguageBarrierList;
            if (unit.PassiveSittingListId.HasValue)
                unit.PassiveSittingList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.PassiveSittingList,
                        unit.PassiveSittingListId.Value, dataLocale) as PassiveSittingList;
            if (unit.GenericListId.HasValue)
                unit.GenericList =
                    await masterListServiceFactory.GetMasterListById(MasterListType.GenericList,
                        unit.GenericListId.Value, dataLocale) as GenericList;
        }

        if (withPoCs)
        {
            var pocList = await pointOfCareService.FindAllByUnitId(unit.Id);
            unit.PointOfCares = pocList?.ToList();
        }

        return unit;
    }

    public async Task<Unit?> GetInfo(ObjectId id, bool withPoCs = true, bool withDevices = true)
    {
        var unit = await Get(id.ToString()) ??
                   throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        if (withPoCs)
        {
            var pocList = await pointOfCareService.FindAllByUnitIdWithDevices(unit.Id);
            unit.PointOfCares = pocList?.ToList();
        }

        return unit;
    }

    public async Task<Unit?> GetByName(string itemUnitName)
    {
        return await unitRepository.FindByName(itemUnitName) ??
               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
    }

    public async Task<Unit?> FindByPatientId(ObjectId patientId)
    {
        //var sections = await GetAll();
        var patient = await patientService.Value.FindById(patientId);
        // if (patient != null && !string.IsNullOrEmpty(patient.Bed) && patient.IsInActivePoC())
        // {
        //     // Deberia ser una lista? revisar como gestionar varias unidades con eel mismo pointOfCare
        //     return sections.FirstOrDefault(s => s.PointOfCares.Any(c=>c.Bed==patient.Bed && c.UnitName == patient.UnitString));
        // }
        if (patient is { UnitId: not null }) return await FindById(patient.UnitId);

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

    public async Task<IEnumerable<Unit>?> FindUnitsByMasterListId(ObjectId masterListId, MasterListType masterListType)
    {
        try
        {
            var result = await unitRepository.FindByMasterListId(masterListId, masterListType);

            return result;
        }
        catch (Exception ex)
        {
            Log.Error("Exception finding by MasterId{id} section exception:{e}  ", masterListId, ex.Message);

            return null;
        }
    }

    public async Task<long> CountUnitsByMasterListId(ObjectId masterListId, MasterListType masterListType)
    {
        return await unitRepository.CountUnitsByMasterListId(masterListId, masterListType);
    }

    public async Task<IEnumerable<Unit>> FindUnitsByMasterListId(ObjectId masterListId)
    {
        try
        {
            var result = await unitRepository.FindByMasterListId(masterListId);

            return result;
        }
        catch (Exception ex)
        {
            Log.Error("Exception finding by MasterId{id} section exception:{e}  ", masterListId, ex.Message);

            return new List<Unit>();
        }
    }

    public async Task<Unit?> UpdateUnitMasterList(UpdateUnitIdListDto updateUnitListDto)
    {
        var unit = await FindById(updateUnitListDto.UnitId) ??
                   throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        var updatedUnit = await unitRepository.UpdateUnitMasterList(updateUnitListDto) ??
                          throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, unit, updatedUnit);
        return updatedUnit;
    }

    public async Task<bool> UpdateConfiguration(ObjectId unitIdParsed, UnitConfiguration unitConfiguration)
    {
        var oldConfig = await FindById(unitIdParsed) ??
                        throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        var result = await unitRepository.UpdateConfiguration(unitIdParsed, unitConfiguration);
        var newConfig = await FindById(unitIdParsed) ??
                        throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        if (!result) throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldConfig, newConfig);

        return result;
    }

    public async Task<Unit?> FindById(ObjectId? id)
    {
        if (id == null)
            return null;
        return await unitRepository.FindById(id);
    }

    public async Task<Unit?> FindByName(string? name)
    {
        if (string.IsNullOrEmpty(name))
            return null;
        return await unitRepository.FindByName(name);
    }

    public async Task<Unit?> FindByUnitNameOrPocName(string? name, string? pocName)
    {
        if (!string.IsNullOrEmpty(name))
            return await unitRepository.FindByName(name);

        if (!string.IsNullOrEmpty(pocName))
        {
            var poc = await pointOfCareService.FindByBed(pocName);
            if (poc != null)
                return await FindById(poc.FirstOrDefault()?.UnitId);
        }

        return null;
    }
    // public async Task<Unit?> FindByPointOfCare(PointOfCare pointOfCare)
    // {
    //     return await _unitRepository.FindByPointOfCare(pointOfCare);
    // }

    public async Task<Unit?> InsertOne(Unit unit)
    {
        var newUnit = await unitRepository.InsertOneUnit(unit) ??
                      throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, newUnit);
        return newUnit;
    }

    public async Task<Unit?> UpdateUnit(Unit unit)
    {
        var oldUnit = await FindById(unit.Id) ??
                      throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        var newUnit = await unitRepository.UpdateUnit(unit);
        if (newUnit == null)
            return null;
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldUnit, newUnit);

        SendUnitBroadcast(newUnit, OperationType.UpdateUnit);
        return newUnit;
    }

    public async Task<Unit?> UpdateUnitInfo(ObjectId unitId, string name, string title, string? configObsId = null)
    {
        var oldUnit = await FindById(unitId) ??
                      throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        var newUnit = await unitRepository.UpdateUnitInfo(unitId, name, title) ??
                      throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldUnit, newUnit);

        if (newUnit != null) SendUnitBroadcast(newUnit, OperationType.UpdateUnit);
        return newUnit;
    }

    public async Task<bool> DeleteUnitById(Unit unit)
    {
        //if (unit is not { Status: null }) throw new ConflictException(ErrorMessage.Conflict_ResourceInUse);
        _ = await unitRepository.DeleteAsync(unit.Id) ??
            throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, unit, null);
        return true;
    }

    public async Task<Unit?> Get(string id)
    {
        Unit? section = null;

        if (ObjectId.TryParse(id, out var oid)) section = await FindById(oid);

        var sections = await GetAll();

        section ??= sections.FirstOrDefault(s => s.Title == id);

        section ??= sections.FirstOrDefault(s => s.Name == id);

        return section;
    }

    public async Task<List<Unit>?> FindByLocation(PatientLocation location)
    {
        try
        {
            var sections = await GetAll();
            // Cambiar por comparacion con Location?
            return sections.Where(section => section.PointOfCares != null &&
                                             section.PointOfCares.Any(c =>
                                                 c.Bed == location.Bed && c.UnitName == location.UnitName)).ToList();
        }
        catch (Exception e)
        {
            Log.Error("Exception finding by location section exception:{e}  location: {location} ", e.Message,
                location);
            return null;
        }
    }

    private async void SendUnitBroadcast(Unit unit, OperationType operation)
    {
        try
        {
            var locations = new List<ObjectId>();
            var pocs = await pointOfCareService.FindAllByUnitId(unit.Id);
            if (pocs == null)
                return;

            locations.AddRange(pocs.Select(c => c.Id));
            var subscribers = subscribersService.GetSubscribers()
                .Where(s => s.LocationIds.Any(id => locations.Contains(id)))
                .ToList();

            foreach (var subscriber in subscribers)
                _ = clientMessageService.Value.SendAsync(subscriber.Id, operation, unit);
        }
        catch (Exception e)
        {
            logger.LogError(
                "Error sending Unit Broadcast {unit} with operation type {operationToString()} message: {eMessage}",
                unit, operation.ToString(), e.Message);
        }
    }
}