using adas_core.Application.Repositories.Interfaces;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;

namespace adas_core.Infrastructure.Repositories;

/// <summary>
/// Repository for managing archived patient appointments in MongoDB. This repository provides methods to insert, delete, and manage archived appointments, ensuring efficient storage and retrieval of historical appointment data.
/// </summary>
public class AppointmentArchiveRepository : MongoRepository<PatientAppointment>, IAppointmentArchiveRepository
{
    /// <summary>
    /// API settings containing configuration for the archive collection, such as the collection name. This allows for flexible configuration and easy adjustments without changing the code.
    /// </summary>
    private readonly ApiSettings _apiSettings;

    /// <summary>
    /// Initializes a new instance of the <see cref="AppointmentArchiveRepository"/> class with the specified API settings and MongoDB database. 
    /// The constructor ensures that the API settings are provided and initializes the base repository with the given database connection.
    /// </summary>
    /// <param name="apiSettings">The API settings containing configuration for the archive collection.</param>
    /// <param name="database">The MongoDB database instance.</param>
    /// <exception cref="ArgumentNullException">Thrown when any of the input parameters are null.</exception>
    public AppointmentArchiveRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
    {
        if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
        _apiSettings = apiSettings.Value;
    } //For testing


    /// <summary>
    /// Inserts a single patient appointment into the archive collection. This method is asynchronous and ensures that the appointment is stored in the MongoDB collection designated for archived appointments. 
    /// It uses the MongoDB driver to perform the insertion operation efficiently.
    /// </summary>
    /// <param name="appointment">The patient appointment to be inserted into the archive collection.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public override async Task InsertOneAsync(PatientAppointment appointment)
    {
        await Collection.InsertOneAsync(appointment);
    }

    /// <summary>
    /// Deletes all patient appointments from the archive collection that were created before the specified date. This method is asynchronous and uses a filter to identify and remove the relevant documents from the MongoDB collection.
    /// </summary>
    /// <param name="date">The date before which all patient appointments should be deleted.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public async Task DeleteBeforeDate(DateTime date)
    {
        var filter = Builders<PatientAppointment>.Filter.Lt(pa => pa.CreateTime, date);
        await Collection.DeleteManyAsync(filter);
    }

    /// <summary>
    /// Inserts a batch of patient appointments into the archive collection. This method is asynchronous and uses the MongoDB driver's bulk write capabilities to efficiently insert multiple documents in a single operation. 
    /// It returns the count of inserted documents, allowing for verification of the operation's success.
    /// </summary>
    /// <param name="appointment">The collection of patient appointments to be inserted into the archive.</param>
    /// <returns>A task representing the asynchronous operation, with the result being the count of inserted documents.</returns>
    public async Task<long> InsertBatch(IEnumerable<PatientAppointment> appointment)
    {
        var writes = new List<WriteModel<PatientAppointment>>();
        writes.AddRange(appointment.Select(d => new InsertOneModel<PatientAppointment>(d)));

        var bulkInsert = await Collection.BulkWriteAsync(writes);

        return bulkInsert.InsertedCount;
    }

    /// <summary>
    /// Gets the name of the MongoDB collection used for storing archived patient appointments. This method retrieves the collection name from the API settings, allowing for flexible configuration. 
    /// If the collection name is not specified in the settings, it defaults to "archive_patients_appointments".
    /// </summary>
    /// <returns>The name of the MongoDB collection for archived patient appointments.</returns>
    public override string GetCollectionName()
    {
        return _apiSettings.ArchivePatientsAppointments ?? "archive_patients_appointments";
    }

    /// <summary>
    /// Creates indexes on the MongoDB collection for archived patient appointments to optimize query performance. This method defines the necessary indexes, such as an index on the "patientid" field, and ensures that they are created in the background without blocking other operations.
    /// </summary>
    /// <returns>A task representing the asynchronous operation.</returns>
    public override async Task CreateIndexes()
    {
        var options = new CreateIndexOptions<PatientAppointment> { Background = true, Unique = false };

        var indexes = new List<CreateIndexModel<PatientAppointment>>
        {
            new("{ patientid: 1 }", options)
        };

        await MongoUtils.EnsureIndexes(Collection, indexes);
    }
}