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.Filter;
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 TreatmentRepository : MongoRepository<PatientTreatment>, ITreatmentRepository
{
    private readonly ApiSettings _apiSettings;


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


    public override string GetCollectionName()
    {
        return _apiSettings.PatientsTreatments ?? "patients_treatments";
    }

    public async Task<IEnumerable<PatientTreatment>> GetByPatientId(ObjectId patientId)
    {
        var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.PatientId, patientId);
        var result = await Collection.FindAsync(filter);
        return result.ToEnumerable();
    }

    public async Task<IAsyncCursor<PatientTreatment>> GetById(ObjectId id)
    {
        var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.Id, id);
        var result = await Collection.FindAsync(filter);
        return result;
    }

    public override async Task InsertOneAsync(PatientTreatment treatment)
    {
        treatment.OrderTime ??= DateTime.UtcNow;
        await base.InsertOneAsync(treatment);
    }

    public new async Task DeleteAsync(ObjectId id)
    {
        var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.Id, id);
        await Collection.DeleteOneAsync(filter);
    }


    public async Task<IAsyncCursor<PatientTreatment>> FindByPatientIdAsync(ObjectId patientId)
    {
        var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.PatientId, patientId);
        return await Collection.FindAsync(filter);
    }


    public async Task<bool> DeleteByPatientId(ObjectId patientId)
    {
        try
        {
            var filter = Builders<PatientTreatment>.Filter.Eq(po => po.PatientId, patientId);
            await Collection.DeleteManyAsync(filter);
            return true;
        }
        catch (Exception ex)
        {
            Log.Error(ex.ToString());
            return false;
        }
    }

    public async Task<bool> Update(PatientTreatment treatment)
    {
        try
        {
            await UpdateOneAsync(treatment.Id, treatment);
            return true;
        }
        catch (Exception ex)
        {
            Log.Error(ex.ToString());
            return false;
        }
    }

    public async Task<List<PatientTreatment>> FindBolusTreatments(ObjectId patientId)
    {
        var builder = Builders<PatientTreatment>.Filter;
        var filter = builder.And(
            builder.Eq(t => t.PatientId, patientId),
            builder.Exists(t => t.RequestedGiveCodesStatus),
            builder.SizeGt(t => t.RequestedGiveCodesStatus, 0)
        );

        var result = await Collection.FindAsync(filter);

        return await result.ToListAsync();
    }

    public async Task<List<PatientTreatment>> GetActiveTreatmentsByPatientIdAndOrder(ObjectId patientId, string order)
    {
        var builder = Builders<PatientTreatment>.Filter;
        var filter = builder.And(
            builder.Or(
                builder.Eq(t => t.OrderControl, OrderControlType.Nw),
                builder.Eq(t => t.OrderControl, OrderControlType.Xo)
            ),
            builder.Eq(t => t.PatientId, patientId),
            builder.And(
                builder.Ne(t => t.PlacerOrder, null), // Verifica que no sea nulo
                builder.Eq(t => t.PlacerOrder!.EntityIdentifier, order)
            )
        );

        var result = await Collection.FindAsync(filter);

        return await result.ToListAsync();
    }


    public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
    {
        await UpdateManyObjectIdAsync(nameId, id, oldId);
    }

    public async Task<IEnumerable<PatientTreatment>> FindByPatientId(ObjectId patientId)
    {
        var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.PatientId, patientId);
        var result = await Collection.FindAsync(filter);
        return result.ToEnumerable();
    }

    public IFindFluent<PatientTreatment, PatientTreatment> GetPaginatedTreatments(PaginationFilter filter)
    {
        var filterBuilder = Builders<PatientTreatment>.Filter;
        var sort = Builders<PatientTreatment>.Sort.Descending("orderTime");
        var filters = new List<FilterDefinition<PatientTreatment>>();

        if (filter.FilteredRequest == null)
        {
            AddDefaultTimeFilters(filters, filter, filterBuilder);
            return CreateFindFluent(filters, sort);
        }

        var requestFilter = filter.FilteredRequest;
        if (requestFilter.PatientId != null && ObjectId.TryParse(requestFilter.PatientId, out var patientObjectId))
            filters.Add(filterBuilder.Eq(t => t.PatientId, patientObjectId));

        if (requestFilter.StartDate != null) filters.Add(filterBuilder.Gt(t => t.OrderTime, requestFilter.StartDate));

        if (requestFilter.EndDate != null) filters.Add(filterBuilder.Lt(t => t.OrderTime, requestFilter.EndDate));

        if (string.IsNullOrWhiteSpace(requestFilter.Text))
        {
            //TODO falta definir la búsqueda por texto
        }

        if (requestFilter.ActiveTreatments)
            AddActiveTreatmentFilters(filters, filterBuilder);
        else
            AddDefaultTimeFilters(filters, filter, filterBuilder);

        return CreateFindFluent(filters, sort);
    }

    public override async Task CreateIndexes()
    {
        var options = new CreateIndexOptions { Background = true, Unique = false };
        var indexes = new List<CreateIndexModel<PatientTreatment>>
        {
            new("{ patientid: 1 }", options)
        };

        await MongoUtils.EnsureIndexes(Collection, indexes);
    }

    private static void AddActiveTreatmentFilters(List<FilterDefinition<PatientTreatment>> filters,
        FilterDefinitionBuilder<PatientTreatment> filterBuilder)
    {
        var currentTime = DateTime.UtcNow;

        filters.Add(
            filterBuilder.And(
                filterBuilder.Ne(t => t.PlacerOrder, null),
                filterBuilder.Ne(t => t.PlacerOrder!.EntityIdentifier, null)
            )
        );

        filters.Add(
            filterBuilder.Or(
                filterBuilder.Eq(t => t.StartTime, null),
                filterBuilder.Lte(t => t.StartTime, currentTime)
            )
        );

        filters.Add(
            filterBuilder.Or(
                filterBuilder.Eq(t => t.EndTime, null),
                filterBuilder.Gte(t => t.EndTime, currentTime)
            )
        );

        filters.Add(filterBuilder.Ne(t => t.OrderControl, OrderControlType.Dc));
        filters.Add(
            filterBuilder.Or(
                filterBuilder.Eq(t => t.OrderControl, OrderControlType.Nw),
                filterBuilder.Eq(t => t.OrderControl, OrderControlType.Xo)
            )
        );
    }

    private void AddDefaultTimeFilters(List<FilterDefinition<PatientTreatment>> filters, PaginationFilter filter,
        FilterDefinitionBuilder<PatientTreatment> filterBuilder)
    {
        filters.Add(
            filterBuilder.Or(
                filterBuilder.Eq(t => t.StartTime, null),
                filterBuilder.Gt(p => p.StartTime, filter.FilteredRequest?.StartDate ?? DateTime.MinValue)
            )
        );

        filters.Add(
            filterBuilder.Or(
                filterBuilder.Eq(t => t.EndTime, null),
                filterBuilder.Lt(p => p.EndTime, filter.FilteredRequest?.EndDate ?? DateTime.MaxValue)
            )
        );
    }

    private IFindFluent<PatientTreatment, PatientTreatment> CreateFindFluent(
        List<FilterDefinition<PatientTreatment>> filters, SortDefinition<PatientTreatment> sort)
    {
        var combinedFilter = filters.Any()
            ? Builders<PatientTreatment>.Filter.And(filters)
            : Builders<PatientTreatment>.Filter.Empty; // Filtra todo si no hay filtros
        return Collection.Find(combinedFilter).Sort(sort);
    }
}