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

namespace adas_core.Infrastructure.Repositories;

/// <summary>
/// Repository for managing patient diagnoses in a MongoDB collection. Provides methods for CRUD operations and querying diagnoses by patient ID and code.
/// </summary>
public class DiagnosisRepository : MongoRepository<PatientDiagnosis>, IDiagnosisRepository
{
    private readonly ApiSettings _apiSettings;

    /// <summary>
    /// Initializes a new instance of the <see cref="DiagnosisRepository"/> class with the specified API settings and MongoDB database. The API settings are used to determine the collection name for storing patient diagnoses.
    /// </summary>
    /// <param name="apiSettings">The API settings containing configuration for the repository.</param>
    /// <param name="database">The MongoDB database instance.</param>
    /// <exception cref="ArgumentNullException">Thrown when the API settings are null.</exception>
    public DiagnosisRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
    {
        if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
        _apiSettings = apiSettings.Value;
    } //For testing

    /// <summary>
    /// Gets the name of the MongoDB collection for storing patient diagnoses. The collection name is determined by the API settings, and defaults to "patients_diagnosis" if not specified.
    /// </summary>
    /// <returns>The name of the MongoDB collection for patient diagnoses.</returns>
    public override string GetCollectionName()
    {
        return _apiSettings.PatientsDiagnosis ?? "patients_diagnosis";
    }

    /// <summary>
    /// Retrieves a list of patient diagnoses for a given patient ID. The diagnoses are sorted in descending order by time, with the most recent diagnoses appearing first.
    /// </summary>
    /// <param name="patientId">The ID of the patient whose diagnoses are to be retrieved.</param>
    /// <returns>A list of patient diagnoses for the specified patient ID.</returns>
    public async Task<List<PatientDiagnosis>> GetByPatient(ObjectId patientId)
    {
        var filter = Builders<PatientDiagnosis>.Filter.Eq(ob => ob.PatientId, patientId);
        var result = await Collection.FindAsync(filter,
            new FindOptions<PatientDiagnosis> { Sort = Builders<PatientDiagnosis>.Sort.Descending("time") });

        return result.ToList();
    }

    /// <summary>
    /// Deletes a patient diagnosis by its ID. This method removes the diagnosis document from the MongoDB collection based on the provided ID.
    /// </summary>
    /// <param name="id">The ID of the patient diagnosis to delete.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public new async Task DeleteAsync(ObjectId id)
    {
        var filter = Builders<PatientDiagnosis>.Filter.Eq(t => t.Id, id);
        await Collection.DeleteOneAsync(filter);
    }

    /// <summary>
    /// Inserts a new patient diagnosis into the MongoDB collection. This method adds a new diagnosis document to the collection based on the provided <see cref="PatientDiagnosis"/> object.
    /// </summary>
    /// <param name="diagnosis">The patient diagnosis to insert.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public override async Task InsertOneAsync(PatientDiagnosis diagnosis)
    {
        await Collection.InsertOneAsync(diagnosis);
    }

    /// <summary>
    /// Deletes all patient diagnoses associated with a specific patient ID. This method removes all diagnosis documents from the MongoDB collection that match the provided patient ID.
    /// </summary>
    /// <param name="patientId">The ID of the patient whose diagnoses are to be deleted.</param>
    /// <returns></returns>
    public async Task DeleteByPatientId(ObjectId patientId)
    {
        var filter = Builders<PatientDiagnosis>.Filter.Eq(po => po.PatientId, patientId);
        await Collection.DeleteManyAsync(filter);
    }

    /// <summary>
    /// Finds a patient diagnosis by patient ID, code, and coding system. 
    /// This method retrieves a single diagnosis document from the MongoDB collection that matches the provided patient ID, code, and coding system. 
    /// If no matching diagnosis is found, it returns null.
    /// </summary>
    /// <param name="patientId">The ID of the patient.</param>
    /// <param name="code">The code of the diagnosis.</param>
    /// <param name="codingSystem">The coding system of the diagnosis.</param>
    /// <returns>The matching patient diagnosis, or null if not found.</returns>
    public async Task<PatientDiagnosis?> FindByPatientIdAndCode(ObjectId patientId, string? code, string? codingSystem)
    {
        var builder = Builders<PatientDiagnosis>.Filter;
        var filter = builder.And(
            builder.Eq(ob => ob.PatientId, patientId),
            builder.Eq(ob => ob.Code, code),
            builder.Eq(ob => ob.CodingSystem, codingSystem)
        );

        var result = await Collection.FindAsync(filter);

        return await result.FirstOrDefaultAsync();
    }

    /// <summary>
    /// Finds patient diagnoses by patient ID. This method retrieves all diagnosis documents from the MongoDB collection that match the provided patient ID. 
    /// The results are returned as an asynchronous cursor, allowing for efficient retrieval of large datasets.
    /// </summary>
    /// <param name="patientId">The ID of the patient whose diagnoses are to be retrieved.</param>
    /// <returns>An asynchronous cursor of patient diagnoses.</returns>
    public async Task<IAsyncCursor<PatientDiagnosis>> FindByPatientIdAsync(ObjectId patientId)
    {
        var filter = Builders<PatientDiagnosis>.Filter.Eq(ob => ob.PatientId, patientId);

        return await Collection.FindAsync(filter);
    }

    /// <summary>
    /// Updates the patient ID for all diagnoses that match the old patient ID. 
    /// This method performs a bulk update operation on the MongoDB collection, changing the patient ID from the old value to the new value for all matching diagnosis documents.
    /// </summary>
    /// <param name="nameId">The name ID associated with the patient.</param>
    /// <param name="id">The new patient ID to be set.</param>
    /// <param name="oldId">The old patient ID to be replaced.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
    {
        await UpdateManyObjectIdAsync(nameId, id, oldId);
    }

    /// <summary>
    /// Creates indexes for the patient diagnosis collection. This method ensures that the necessary indexes are created on the MongoDB collection to optimize query performance.
    /// </summary>
    /// <returns>A task representing the asynchronous operation.</returns>
    public override async Task CreateIndexes()
    {
        var options = new CreateIndexOptions { Background = true, Unique = false };
        var indexes = new List<CreateIndexModel<PatientDiagnosis>>
        {
            new("{ patientid: 1 }", options)
        };

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