using adas_core.Application.Repositories.Interfaces;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Logging;
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 archived patient care plans in a MongoDB database. 
/// This class provides methods to perform CRUD operations and queries related to archived patient care plans, such as retrieving care plans by patient ID, inserting new care plans, and creating indexes. 
/// It utilizes the MongoDB driver for database interactions and is configured using application settings for collection names and other parameters.
/// </summary>
public class ArchivePatientCarePlanRepository : MongoRepository<PatientCarePlan>, IArchivePatientCarePlanRepository
{
    #region Properties
    /// <summary>
    /// API settings containing configuration for the repository, such as collection names and other relevant parameters.
    /// </summary>
    private readonly ApiSettings _apiSettings;

    /// <summary>
    /// Logger for logging information, warnings, and errors related to the operations performed by this repository.
    /// </summary>
    private readonly ILogger<ArchivePatientCarePlanRepository> _logger;

    #endregion

    #region Constructor

    /// <summary>
    /// Initializes a new instance of the <see cref="ArchivePatientCarePlanRepository"/> class with the specified API settings, MongoDB database, and logger.
    /// </summary>
    /// <param name="apiSettings">The API settings containing configuration for the repository.</param>
    /// <param name="database">The MongoDB database instance.</param>
    /// <param name="logger">The logger for logging information, warnings, and errors.</param>
    public ArchivePatientCarePlanRepository(
        IOptions<ApiSettings> apiSettings,
        IMongoDatabase database,
        ILogger<ArchivePatientCarePlanRepository> logger) : base(database)
    {
        _logger = logger;
        _apiSettings = apiSettings.Value;
    }

    /// <summary>
    /// Creates indexes for the MongoDB collection associated with this repository. 
    /// This method ensures that the necessary indexes are created to optimize query performance, particularly for queries based on patient ID. The indexes are created in the background to avoid blocking operations on the database.
    /// </summary>
    /// <returns></returns>
    public override async Task CreateIndexes()
    {
        var options = new CreateIndexOptions<PatientCarePlan> { Background = true, Unique = false };

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

        await MongoUtils.EnsureIndexes(Collection, indexes);
    }

    #endregion

    #region Create

    /// <summary>
    /// Inserts a single patient care plan into the MongoDB collection. This method takes a <see cref="PatientCarePlan"/> object as input and attempts to insert it into the database. 
    /// If an exception occurs during the insertion process, it logs the error with details about the patient and the exception, and then rethrows the exception to be handled by the calling code.
    /// </summary>
    /// <param name="patient">The patient care plan to be inserted.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public override async Task InsertOneAsync(PatientCarePlan patient)
    {
        try
        {
            await base.InsertOneAsync(patient);
        }
        catch (Exception e)
        {
            _logger.LogError(
                "Exception trying to insert patient: {patient}. on archive patient procedure Exception {e}", patient,
                e);
            throw;
        }
    }

    /// <summary>
    /// Inserts multiple patient care plans into the MongoDB collection. This method takes a list of <see cref="PatientCarePlan"/> objects as input and attempts to insert them into the database in a single operation.
    /// </summary>
    /// <param name="patient">The list of patient care plans to be inserted.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public override async Task InsertManyAsync(List<PatientCarePlan> patient)
    {
        try
        {
            await base.InsertManyAsync(patient);
        }
        catch (Exception e)
        {
            _logger.LogError(
                "Exception trying to insert many PatientCarePlan. on archive patient procedure Exception {e}", e);
            throw;
        }
    }

    #endregion

    #region Read

    /// <summary>
    /// Gets the name of the MongoDB collection associated with this repository. The collection name is determined based on the API settings provided during the initialization of the repository. If the collection name is not specified in the settings, it defaults to "archive_patients_care_plan". 
    /// This method is used by the base repository class to determine which collection to interact with for CRUD operations.
    /// </summary>
    /// <returns></returns>
    public override string GetCollectionName()
    {
        return _apiSettings.ArchivePatientProcedure ?? "archive_patients_care_plan";
    }

    /// <summary>
    /// Finds patient care plans by the specified patient ID. This method takes a patient ID as input and queries the MongoDB collection for care plans associated with that patient ID. 
    /// It returns a list of <see cref="PatientCarePlan"/> objects that match the query. If no care plans are found, it returns an empty list.
    /// </summary>
    /// <param name="patientId">The ID of the patient whose care plans are to be retrieved.</param>
    /// <returns>A task representing the asynchronous operation, containing a list of <see cref="PatientCarePlan"/> objects.</returns>
    public async Task<List<PatientCarePlan>?> FindByPatientId(ObjectId patientId)
    {
        var result = await Collection.FindAsync(Builders<PatientCarePlan>.Filter.Eq(p => p.PatientId, patientId));
        return result.ToList();
    }

    /// <summary>
    /// Finds patient care plans by the specified patient ID, where the patient ID is provided as a string. This method attempts to parse the string into an ObjectId and then queries the MongoDB collection for care plans associated with that patient ID.
    /// </summary>
    /// <param name="patientId">The ID of the patient whose care plans are to be retrieved, provided as a string.</param>
    /// <returns>A task representing the asynchronous operation, containing a list of <see cref="PatientCarePlan"/> objects.</returns>
    public async Task<List<PatientCarePlan>?> FindByPatientId(string patientId)
    {
        var isParsed = ObjectId.TryParse(patientId, out var patientIdParsed);
        if (!isParsed) return [];
        var result = await Collection.FindAsync(Builders<PatientCarePlan>.Filter.Eq(p => p.PatientId, patientIdParsed));
        return result.ToList();
    }

    /// <summary>
    /// Finds patient care plans by the specified patient number. This method takes a patient number as input and queries the MongoDB collection for care plans associated with that patient number.
    /// </summary>
    /// <param name="patientNumber">The number of the patient whose care plans are to be retrieved.</param>
    /// <returns>A task representing the asynchronous operation, containing a list of <see cref="PatientCarePlan"/> objects.</returns>
    public async Task<List<PatientCarePlan>?> FindByPatientNumber(string patientNumber)
    {
        var result = await Collection.FindAsync(Builders<PatientCarePlan>.Filter.Eq(p => p.PatientNumber, patientNumber));
        return result.ToList();
    }

    /// <summary>
    /// Finds all patient care plans in the MongoDB collection. This method retrieves all documents from the collection and returns them as a list of <see cref="PatientCarePlan"/> objects.
    /// </summary>
    /// <returns>A task representing the asynchronous operation, containing a list of <see cref="PatientCarePlan"/> objects.</returns>
    public async Task<List<PatientCarePlan>> FindAll()
    {
        var result = await Collection.Find(Builders<PatientCarePlan>.Filter.Empty).ToListAsync();
        return result;
    }

    /// <summary>
    /// Finds patient care plans by multiple identifiers, including patient ID, patient number, and patient ID as a string. 
    /// This method attempts to find care plans using the provided identifiers in a specific order: it first tries to find care plans by the patient ID (as an ObjectId), then by the patient ID (as a string), and finally by the patient number.
    /// If any of the queries return results, it returns those results immediately. If no care plans are found using any of the identifiers, it returns an empty list.
    /// </summary>
    /// <param name="oldPatientId">The ID of the patient whose care plans are to be retrieved, provided as an ObjectId.</param>
    /// <param name="oldPatientPatientId">The ID of the patient whose care plans are to be retrieved, provided as a string.</param>
    /// <param name="oldPatientPatientNumber">The number of the patient whose care plans are to be retrieved.</param>
    /// <returns>A task representing the asynchronous operation, containing a list of <see cref="PatientCarePlan"/> objects.</returns>
    public async Task<List<PatientCarePlan>?> FindByIds(ObjectId oldPatientId, string? oldPatientPatientId,
        string? oldPatientPatientNumber)
    {
        var patientFound = await FindByPatientId(oldPatientId);
        if (patientFound != null) return patientFound;
        if (oldPatientPatientId != null)
        {
            patientFound = await FindByPatientId(oldPatientPatientId);
            if (patientFound is { Count: > 0 }) return patientFound;
        }

        if (oldPatientPatientNumber != null)
        {
            patientFound = await FindByPatientNumber(oldPatientPatientNumber);
            if (patientFound is { Count: > 0 }) return patientFound;
        }

        return [];
    }

    #endregion

    #region Update

    // public async Task Update(Patient patient)
    // {
    //     patient.UpdateDate = DateTime.UtcNow;
    //     await UpdateOneAsync(patient.Id, patient);
    // }
    //
    // public async void UpdatePatientData(Patient oldPatient, Patient newPatient)
    // {
    //     var filterBuilder = Builders<Patient>.Filter;
    //     var updateBuilder = Builders<Patient>.Update
    //         .Set(p => p.Person, newPatient.Person)
    //         .Set(p => p.UpdateDate, DateTime.UtcNow)
    //         .Set(p => p.Location, newPatient.Location)
    //         .Set(p => p.PatientNumber, newPatient.PatientNumber)
    //         .Set(p => p.UnitString, newPatient.UnitString)
    //         .Set(p => p.Room, newPatient.Room)
    //         .Set(p => p.PatientId, newPatient.PatientId);
    //     var filter = filterBuilder.Eq(p => p.Id, oldPatient.Id);
    //     var update = updateBuilder;
    //
    //     await Collection.UpdateOneAsync(filter, update);
    // }

    // public async void UpdateProcedure(Patient patientFound)
    // {
    //     var filterBuilder = Builders<Patient>.Filter;
    //     var updateBuilder = Builders<Patient>.Update
    //         .Set(p => p.Procedures, patientFound.Procedures);
    //     var filter = filterBuilder.Eq(p => p.Id, patientFound.Id);
    //     var update = updateBuilder;
    //
    //     await Collection.UpdateOneAsync(filter, update);
    // }
    //
    // public async void UpdateTreatment(Patient patientFound)
    // {
    //     var filterBuilder = Builders<Patient>.Filter;
    //     var updateBuilder = Builders<Patient>.Update
    //         .Set(p => p.Treatment, patientFound.Treatment);
    //     var filter = filterBuilder.Eq(p => p.Id, patientFound.Id);
    //     var update = updateBuilder;
    //
    //     await Collection.UpdateOneAsync(filter, update);
    // }

    #endregion
}