using adas_core.Application.Repositories.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Masters;
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using Serilog;
using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;

namespace adas_core.Infrastructure.Repositories;

public class AdmissionRepository : MongoRepository<Admission>, IAdmissionRepository
{
    private readonly ApiSettings _apiSettings;

    public AdmissionRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
    {
        if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
        _apiSettings = apiSettings.Value;
    }
    

    public override string GetCollectionName()
    {
        return _apiSettings.Admissions;
    }


    public override async Task InsertOneAsync(Admission admission)
    {
        try
        {
            admission.AdmissionDate = DateTime.UtcNow;
            await base.InsertOneAsync(admission);
        }
        catch (Exception e)
        {
            Log.Warning("Exception trying to insert  admission: {admission}. Exception {e}", admission, e);
        }
    }

    public async Task Delete(ObjectId id)
    {
        try
        {
            var filter = Builders<Admission>.Filter.Eq(x => x.Id, id);
            await Collection.DeleteOneAsync(filter, null);
        }
        catch (Exception e)
        {
            Log.Error("Exception trying to delete admission: {id}. Exception {e}", id, e);
            throw;
        }
    }

    public async Task Update(Admission admission)
    {
        try
        {
            await UpdateOneAsync(admission.Id, admission);
        }
        catch (Exception e)
        {
            Log.Error("Exception trying to update admission: {admission}. Exception {e}", admission, e);
            throw;
        }
    }

    public async Task UpdateLocation(ObjectId id, ObjectId newLocation)
    {
        var filterBuilder = Builders<Admission>.Filter;
        var filter = filterBuilder.Eq(p => p.Id, id);

        var update = Builders<Admission>.Update
            .Set(p => p.PointOfCareId, newLocation);

        await Collection.UpdateOneAsync(filter, update);
    }


    public async Task UpdatePatient(ObjectId id, Person patient)
    {
        var filterBuilder = Builders<Admission>.Filter;
        var filter = filterBuilder.Eq(p => p.Id, id);

        var update = Builders<Admission>.Update
            .Set(p => p.Person, patient);

        await Collection.UpdateOneAsync(filter, update);
    }

    public async Task<IEnumerable<Admission>> FindAll()
    {
        try
        {
            var result = await Collection.FindAsync(_ => true);
            return await result.ToListAsync();
        }
        catch (Exception ex)
        {
            Log.Error("Error getting all admissions. Exception: {ex}", ex);
            return [];
        }
    }

    public async Task<Admission?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId)
    {
        if (string.IsNullOrWhiteSpace(patientNumber)) return null;

        // Paciente ubicado en un PoC pero en diferente unidad
        var patient = await Collection.Find(Builders<Admission>.Filter.And(
            Builders<Admission>.Filter.Eq(p => p.Nhc, patientNumber),
            Builders<Admission>.Filter.Ne(p => p.UnitId, unitId)
        )).ToListAsync();
        // Si hay mas de una coincidencia devolvemos null por que puede no estar completo el patientNumber
        if (patient.Count > 1) return null;
        return patient.FirstOrDefault();
    }

    public async Task<Admission?> FindById(ObjectId id)
    {
        try
        {
            var filter = Builders<Admission>.Filter.Eq(p => p.Id, id);
            var result = await Collection.FindAsync(filter);

            return await result.FirstOrDefaultAsync();
        }
        catch (Exception ex)
        {
            Log.Error("Error searching admission by id: {id}. Exception: {ex}", id, ex);
            return null;
        }
    }

    public async Task<Admission?> FindByNhc(string nhc)
    {
        try
        {
            var filter = Builders<Admission>.Filter.Eq(p => p.Nhc, nhc);
            var result = await Collection.FindAsync(filter);

            return await result.FirstOrDefaultAsync();
        }
        catch (Exception ex)
        {
            Log.Error("Error searching admission by NHC: {nhc}. Exception: {ex}", nhc, ex);
            return null;
        }
    }

    public async Task<List<Admission>> FindByLocation(PatientLocation location)
    {
        try
        {
            var filter = Builders<Admission>.Filter.Where(p =>
                p.PatientLocation != null &&
                location.UnitName == p.PatientLocation.UnitName &&
                location.Bed == p.PatientLocation.Bed &&
                location.Room == p.PatientLocation.Room);

            return await Collection.Find(filter).ToListAsync();
        }
        catch (Exception ex)
        {
            Log.Error("Error searching admission by location: {location}. Exception: {ex}", location, ex);
            return [];
        }
    }

    public async Task<IEnumerable<Admission>?> FindByOrigin(string origin)
    {
        try
        {
            var filter = string.IsNullOrEmpty(origin)
                ? Builders<Admission>.Filter.Empty
                : Builders<Admission>.Filter.Where(p => p.Origin != null && origin.Equals(p.Origin.Name));

            var result = await Collection.FindAsync(filter);

            return result.ToEnumerable();
        }
        catch (Exception ex)
        {
            Log.Error("Error searching admissions by origin: {origin}. Exception: {ex}", origin, ex);
            return null;
        }
    }

    public async Task<Admission?> InsertOneAsyncAndReturn(Admission origin)
    {
        try
        {
            await Collection.InsertOneAsync(origin);
            return origin;
        }
        catch (Exception ex)
        {
            Log.Error(ex.Message);
            return null;
        }
    }

    public async Task<List<Admission>> GetAdmissionByUnitIdWithOutPoC(ObjectId unitId)
    {
        try
        {
            var filter = Builders<Admission>.Filter.Where(p => p.UnitId == unitId && p.PointOfCareId == null);
            var result = await Collection.FindAsync(filter);

            return result.ToList();
        }
        catch (Exception ex)
        {
            Log.Error(ex.Message);
            return [];
        }
    }

    public async Task<long> CountByUnitId(ObjectId unitId)
    {
        try
        {
            var filter = Builders<Admission>.Filter.Where(p => p.UnitId == unitId);
            var result = await Collection.CountDocumentsAsync(filter);
            return result;
        }
        catch (Exception ex)
        {
            Log.Error(ex.Message);
            return 0;
        }
    }

    public async Task<List<Admission>> FindByPointOfCareId(ObjectId pocId)
    {
        try
        {
            var filter = Builders<Admission>.Filter.Where(p => p.PointOfCareId == pocId);
            var result = await Collection.FindAsync(filter);

            return result.ToList();
        }
        catch (Exception ex)
        {
            Log.Error(ex.Message);
            return [];
        }
    }

    public async Task<List<Admission>> FindByUnitIds(List<ObjectId> unitIds)
    {
        var filterUnit = Builders<Admission>.Filter.In("unitId", unitIds);
        return await Collection.Find(filterUnit).ToListAsync();
    }

    public async Task<IEnumerable<Admission>> UpdateMasterListOption(List<ObjectId> unitIds,
        UpdateOptionMasterListDto opt, string typeName)
    {
        var isParsed = Enum.TryParse<MasterListType>(typeName, out var parsedTypeName);
        if (isParsed)
        {
            var filterUnit = Builders<Admission>.Filter.In("unitId", unitIds);
            switch (parsedTypeName)
            {
                case MasterListType.OriginList:
                    var originFilter = Builders<Admission>.Filter.Eq(
                        "origin.name", opt.OldOption?.Name
                    );
                    var filterUpdateorigin = Builders<Admission>.Filter.And(filterUnit, originFilter);
                    var updateorigin = Builders<Admission>.Update
                        .Set("origin.name", opt.UpdatedOption?.Name);
                    await Collection.UpdateManyAsync(filterUpdateorigin, updateorigin);

                    await Collection.UpdateManyAsync(Builders<Admission>.Filter.And(filterUnit,
                        Builders<Admission>.Filter.Eq(
                            "originAux", opt.OldOption?.Name
                        )), Builders<Admission>.Update
                        .Set("originAux", opt.UpdatedOption?.Name));

                    // Filtro para devolver los documentos actualizados (OR entre diagnosis.name y diagnosisAux)
                    var originFilterToReturn = Builders<Admission>.Filter.Eq("origin.name", opt.UpdatedOption?.Name);
                    var originAuxFilterToReturn = Builders<Admission>.Filter.Eq("originAux", opt.UpdatedOption?.Name);
                    var filterToReturnorigin = Builders<Admission>.Filter.And(
                        filterUnit,
                        Builders<Admission>.Filter.Or(originFilterToReturn, originAuxFilterToReturn)
                    );

                    // Devolver los documentos actualizados
                    var updatedDocumentsorigin = await Collection.Find(filterToReturnorigin).ToListAsync();
                    return updatedDocumentsorigin;
                case MasterListType.DiagnosisList:
                    var diagnosisFilter = Builders<Admission>.Filter.Eq(
                        "diagnosis.name", opt.OldOption?.Name
                    );
                    var filterUpdateDiagnosis = Builders<Admission>.Filter.And(filterUnit, diagnosisFilter);
                    var updateDiagnosis = Builders<Admission>.Update
                        .Set("diagnosis.name", opt.UpdatedOption?.Name)
                        .Set("diagnosis.description", opt.UpdatedOption?.Description);
                    await Collection.UpdateManyAsync(filterUpdateDiagnosis, updateDiagnosis);

                    await Collection.UpdateManyAsync(Builders<Admission>.Filter.And(filterUnit,
                        Builders<Admission>.Filter.Eq(
                            "diagnosisAux", opt.OldOption?.Name
                        )), Builders<Admission>.Update
                        .Set("diagnosisAux", opt.UpdatedOption?.Name));

                    // Filtro para devolver los documentos actualizados (OR entre diagnosis.name y diagnosisAux)
                    var diagnosisFilterToReturn =
                        Builders<Admission>.Filter.Eq("diagnosis.name", opt.UpdatedOption?.Name);
                    var diagnosisAuxFilterToReturn =
                        Builders<Admission>.Filter.Eq("diagnosisAux", opt.UpdatedOption?.Name);
                    var filterToReturn = Builders<Admission>.Filter.And(
                        filterUnit,
                        Builders<Admission>.Filter.Or(diagnosisFilterToReturn, diagnosisAuxFilterToReturn)
                    );

                    // Devolver los documentos actualizados
                    var updatedDocuments = await Collection.Find(filterToReturn).ToListAsync();
                    return updatedDocuments;
                case MasterListType.AllergyList:
                    // allergies []
                    break;
                case MasterListType.InsulationList:
                    // insulation
                    break;
                case MasterListType.LanguageBarrierList:
                    // languageBarrier
                    break;
                case MasterListType.PassiveSittingList:
                    // passiveSitting
                    break;
            }
        }

        return new List<Admission>();
    }

    public async Task<IEnumerable<Admission>> DeleteMasterListOption(List<ObjectId> unitIds, OptionList opt,
        string typeName)
    {
        var isParsed = Enum.TryParse<MasterListType>(typeName, out var parsedTypeName);
        if (isParsed)
        {
            var filterUnit = Builders<Admission>.Filter.In("unitId", unitIds);
            switch (parsedTypeName)
            {
                case MasterListType.OriginList:
                    // origin
                    // originAux
                    // Filtro para devolver los documentos actualizados (OR entre diagnosis.name y diagnosisAux)
                    var originFilterToReturn = Builders<Admission>.Filter.Eq("origin.name", opt.Name);
                    var originAuxFilterToReturn = Builders<Admission>.Filter.Eq("originAux", opt.Name);
                    var filterToReturnOrigin = Builders<Admission>.Filter.And(
                        filterUnit,
                        Builders<Admission>.Filter.Or(originFilterToReturn, originAuxFilterToReturn)
                    );

                    // Devolver los documentos actualizados
                    var updatedDocumentOrigin = await Collection.Find(filterToReturnOrigin).ToListAsync();

                    var originFilter = Builders<Admission>.Filter.Eq(
                        "origin.name", opt.Name
                    );
                    var filterUpdateOrigin = Builders<Admission>.Filter.And(filterUnit, originFilter);
                    var updateorigin = Builders<Admission>.Update
                        .Set(x => x.Origin, null);
                    await Collection.UpdateManyAsync(filterUpdateOrigin, updateorigin);

                    await Collection.UpdateManyAsync(Builders<Admission>.Filter.And(filterUnit,
                        Builders<Admission>.Filter.Eq(
                            "originAux", opt.Name
                        )), Builders<Admission>.Update
                        .Set(x => x.OriginAux, ""));


                    return updatedDocumentOrigin;

                case MasterListType.DiagnosisList:

                    // Filtro para devolver los documentos actualizados (OR entre diagnosis.name y diagnosisAux)
                    var diagnosisFilterToReturn = Builders<Admission>.Filter.Eq("diagnosis.name", opt.Name);
                    var diagnosisAuxFilterToReturn = Builders<Admission>.Filter.Eq("diagnosisAux", opt.Name);
                    var filterToReturn = Builders<Admission>.Filter.And(
                        filterUnit,
                        Builders<Admission>.Filter.Or(diagnosisFilterToReturn, diagnosisAuxFilterToReturn)
                    );

                    // Devolver los documentos actualizados
                    var updatedDocuments = await Collection.Find(filterToReturn).ToListAsync();

                    var diagnosisFilter = Builders<Admission>.Filter.Eq(
                        "diagnosis.name", opt.Name
                    );
                    var filterUpdateDiagnosis = Builders<Admission>.Filter.And(filterUnit, diagnosisFilter);
                    var updateDiagnosis = Builders<Admission>.Update
                        .Set(x => x.Diagnosis, null);
                    await Collection.UpdateManyAsync(filterUpdateDiagnosis, updateDiagnosis);

                    await Collection.UpdateManyAsync(Builders<Admission>.Filter.And(filterUnit,
                        Builders<Admission>.Filter.Eq(
                            "diagnosisAux", opt.Name
                        )), Builders<Admission>.Update
                        .Set(x => x.DiagnosisAux, null));


                    return updatedDocuments;
                case MasterListType.AllergyList:
                    // allergies []
                    break;
                case MasterListType.InsulationList:
                    // insulation
                    break;
                case MasterListType.LanguageBarrierList:
                    // languageBarrier
                    break;
                case MasterListType.PassiveSittingList:
                    // passiveSitting
                    break;
            }
        }

        return new List<Admission>();
    }

    public async Task<bool> DeleteAdmissionsByUnitId(ObjectId unitId)
    {
        try
        {
            var filter = Builders<Admission>.Filter.Eq(p => p.UnitId, unitId);
            await Collection.DeleteManyAsync(filter);
            return true;
        }
        catch (Exception e)
        {
            Log.Logger.Error(e.Message);
            return false;
        }
    }

    public override async Task CreateIndexes()
    {
        var optionsUq = new CreateIndexOptions<Admission>
        {
            Background = true,
            Unique = true,
            PartialFilterExpression = Builders<Admission>.Filter.Exists(p => p.Nhc)
        };

        var indexes = new List<CreateIndexModel<Admission>>
        {
            new("{ nhc: 1 }", optionsUq)
        };

        await MongoUtils.EnsureIndexes(Collection, indexes);
    }

    public async Task<IEnumerable<Admission>?> FindByDiagnosis(string diagnosis)
    {
        try
        {
            var filter =
                Builders<Admission>.Filter.Where(p => p.Diagnosis != null && diagnosis.Equals(p.Diagnosis.Name));
            var result = await Collection.FindAsync(filter);

            return result.ToEnumerable();
        }
        catch (Exception ex)
        {
            Log.Error("Error searching admissions by diagnosis: {origin}. Exception: {ex}", diagnosis, ex);
            return null;
        }
    }
}