using adas_core.Domain.Models;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.Masters;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
using MongoDB.Driver;

namespace adas_core.Application.Repositories.Interfaces;

public interface IPatientRepository : IMongoRepository<Patient>
{
    /// <summary>
    /// Deletes the item identified by the specified identifier.
    /// </summary>
    /// <param name="id">The identifier of the item to delete.</param>
    Task Delete(ObjectId id);
    /// <summary>
    /// Asynchronously retrieves a <see cref="Patient"/> associated with the specified location, or <see langword="null"/> if no patient is found at that location.
    /// </summary>
    /// <param name="location">The location used to look up the patient.</param>
    /// <returns>A task that represents the asynchronous operation, containing the <see cref="Patient"/> if found; otherwise, <see langword="null"/>.</returns>
    Task<Patient?> FindByLocation(PatientLocation location);
    /// <summary>
    /// Retrieves a patient by their unique identifier, returning <c>null</c> when no matching patient exists.
    /// </summary>
    /// <param name="id">The unique <see cref="ObjectId"/> of the patient to look up.</param>
    /// <returns>A <see cref="Task{T}"/> that resolves to the matching <see cref="Patient"/>, or <c>null</c> if no patient is found.</returns>
    Task<Patient?> FindById(ObjectId id);
    /// <summary>
    /// Asynchronously updates the location of a patient identified by the specified identifier.
    /// </summary>
    /// <param name="id">The unique identifier of the patient whose location is to be updated.</param>
    /// <param name="location">The new patient location data to apply to the existing record.</param>
    Task UpdateLocation(ObjectId id, PatientLocation location);
    /// <summary>
    /// Updates the location of the entity identified by the specified identifier.
    /// </summary>
    /// <param name="id">The identifier of the entity whose location will be updated.</param>
    /// <param name="location">The identifier of the new location to associate with the entity.</param>
    Task UpdateLocation(ObjectId id, ObjectId location);
    /// <summary>
    /// Updates the attending doctor for the record identified by the specified id.
    /// </summary>
    /// <param name="id">The identifier of the record whose attending doctor will be updated.</param>
    /// <param name="attendingDoctor">The person to be set as the new attending doctor.</param>
    Task UpdateAttendingDoctor(ObjectId id, Person attendingDoctor);
    /// <summary>
    /// Updates the patient data identified by the specified id, optionally updating the patient number when <paramref name="updatePatientNumber"/> is true.
    /// </summary>
    /// <param name="id">The unique identifier of the patient to update.</param>
    /// <param name="patientNumber">The patient number associated with the patient.</param>
    /// <param name="data">The new person data to apply to the patient record.</param>
    /// <param name="updatePatientNumber">Indicates whether the patient number should be updated; defaults to true.</param>
    Task UpdatePatientData(ObjectId id, string patientNumber, Person data, bool updatePatientNumber = true);
    /// <summary>
    /// Updates the information of an existing patient in the system.
    /// </summary>
    /// <param name="patient">The patient entity containing the updated information to be persisted.</param>
    Task Update(Patient patient);
    /// <summary>
    /// Asynchronously retrieves a <see cref="Patient"/> by their unique patient number.
    /// Returns <c>null</c> when no patient matches the provided identifier.
    /// </summary>
    /// <param name="patientNumber">The unique patient number used to look up the patient.</param>
    /// <returns>A <see cref="Task{TResult}"/> containing the matching <see cref="Patient"/>, or <c>null</c> if no patient is found.</returns>
    Task<Patient?> FindByPatientNumber(string patientNumber);
    /// <summary>
    /// Asynchronously retrieves a <see cref="Patient"/> that matches the specified patient identifier.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient to look up.</param>
    /// <returns>A task that represents the asynchronous operation, containing the matching <see cref="Patient"/> if found; otherwise, <c>null</c>.</returns>
    Task<Patient?> FindByPatientId(string patientId);
    /// <summary>
    /// Asynchronously retrieves all <see cref="Patient"/> records.
    /// </summary>
    /// <returns>A task that represents the asynchronous operation, containing a list of all <see cref="Patient"/> entities.</returns>
    Task<List<Patient>> FindAll();
    /// <summary>
    /// Asynchronously retrieves a list of patients associated with the specified point of care.
    /// </summary>
    /// <param name="pointOfCare">The point of care identifier used to filter patients.</param>
    /// <returns>A task that represents the asynchronous operation, containing a list of patients matching the specified point of care.</returns>
    Task<List<Patient>> FindByPointOfCare(string pointOfCare);
    /// <summary>
    /// Asynchronously retrieves the list of patients associated with the specified point of care.
    /// </summary>
    /// <param name="pointOfCare">The unique identifier of the point of care used to filter patients.</param>
    /// <returns>A task that represents the asynchronous operation, containing the list of patients matching the specified point of care.</returns>
    Task<List<Patient>> FindByPointOfCare(ObjectId pointOfCare);
    /// <summary>
    /// Retrieves a patient by their point-of-care identifier, returning <c>null</c> when no matching patient is found.
    /// </summary>
    /// <param name="pointOfCare">The unique identifier of the point-of-care location used to look up the patient.</param>
    /// <returns>A task that resolves to the matching <see cref="Patient"/>, or <c>null</c> if no patient is associated with the specified point-of-care id.</returns>
    Task<Patient?> FindByPointOfCareId(ObjectId pointOfCare);
    /// <summary>
    /// Asynchronously retrieves a list of patients who have been discharged.
    /// </summary>
    /// <returns>A task representing the asynchronous operation, containing a list of discharged <see cref="Patient"/> records.</returns>
    Task<List<Patient>> FindDischargedPatients();
    /// <summary>
    /// Updates an existing patient record and returns the updated patient, or <see langword="null"/> if the patient was not found.
    /// </summary>
    /// <param name="updatedPatient">The patient containing the updated information.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains the updated <see cref="Patient"/>, or <see langword="null"/> if no matching patient exists.</returns>
    Task<Patient?> UpdateOne(Patient updatedPatient);
    /// <summary>
    /// Asynchronously retrieves a list of patients who have an inactive Point of Care (PoC) assignment.
    /// </summary>
    /// <returns>A task that represents the asynchronous operation. The task result contains a list of <see cref="Patient"/> objects with inactive PoC status.</returns>
    Task<List<Patient>> FindInActivePoC();
    /// <summary>
    /// Asynchronously retrieves a list of patients who are currently marked as inactive points of contact (PoC).
    /// </summary>
    /// <returns>A task that represents the asynchronous operation. The task result contains a list of inactive PoC patients.</returns>
    Task<List<Patient>> FindInInactivePoC();
    /// <summary>
    /// Retrieves a list of patients whose records have not been updated since the specified date.
    /// </summary>
    /// <param name="date">The cutoff date; patients last updated before this date will be included in the result.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains a list of <see cref="Patient"/> objects that have not been updated since the specified date.</returns>
    Task<List<Patient>> FindPatientsNotUpdatedSince(DateTime date);
    /// <summary>
    /// Asynchronously retrieves a patient from the data store by their unique identifier.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient to look up.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains the matching <see cref="Patient"/> if one is found; otherwise, <c>null</c>.</returns>
    Task<Patient?> FindByPatientId(ObjectId patientId);
    /// <summary>
    /// Updates the incoming data for the specified patient and returns the updated patient record.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose incoming data should be updated.</param>
    /// <param name="person">The patient object containing the incoming data to apply.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains the updated <see cref="Patient"/>, or <c>null</c> if the patient was not found.</returns>
    Task<Patient?> UpdatePatientIncomingData(ObjectId patientId, Patient person);
    /// <summary>
    /// Updates the demographic data of an existing patient identified by the given identifier.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose demographic data is to be updated.</param>
    /// <param name="person">The patient object containing the updated demographic information.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains the updated <see cref="Patient"/>, or <c>null</c> if the patient was not found.</returns>
    Task<Patient?> UpdatePatientDemographicData(ObjectId patientId, Patient person);
    /// <summary>
    /// Asynchronously retrieves the patient matching the specified unit and point of care identifier.
    /// </summary>
    /// <param name="unit">The identifier of the unit used to locate the patient.</param>
    /// <param name="pointOfCare">The point of care identifier used together with the unit to locate the patient.</param>
    /// <returns>A task that resolves to the <see cref="Patient"/> associated with the given unit and point of care.</returns>
    Task<Patient> FindByUnitAndPocId(ObjectId unit, ObjectId pointOfCare);
    /// <summary>
    /// Asynchronously retrieves the total count of records associated with the specified unit identifier.
    /// </summary>
    /// <param name="unitId">The identifier of the unit whose associated records should be counted.</param>
    /// <returns>A task that represents the asynchronous operation, containing the total count of matching records.</returns>
    Task<long> CountByUnitId(ObjectId unitId);
    /// <summary>
    /// Asynchronously searches for a patient by their patient number within a specific unit, ensuring the patient is associated with a distinct unit.
    /// </summary>
    /// <param name="patientNumber">The unique patient number used to identify the patient.</param>
    /// <param name="unitId">The identifier of the unit to constrain the search to a distinct unit context.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains the matching <see cref="Patient"/> if found; otherwise, <c>null</c>.</returns>
    Task<Patient?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId);
    /// <summary>
    /// Retrieves a paginated, fluent query of patients based on the specified pagination filter.
    /// </summary>
    /// <param name="filter">The pagination filter that defines page size, page number, and additional criteria applied to the patient query.</param>
    /// <returns>An <see cref="IFindFluent{Patient, Patient}"/> representing the paginated patient query that can be further composed before execution.</returns>
    IFindFluent<Patient, Patient> GetPaginatedPatients(PaginationFilter filter);
    /// <summary>
    /// Asynchronously retrieves all patients who have completed procedures, filtering by the number of minutes that have elapsed since the procedure end date for archival purposes.
    /// </summary>
    /// <param name="archiveProcedureEndDateAfterMinutes">The number of minutes after the procedure end date used to determine which finished procedures are eligible for archive retrieval.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains a list of patients with finished procedures matching the archive criteria.</returns>
    Task<List<Patient>> FindAllPatientWithFinishedProcedures(int archiveProcedureEndDateAfterMinutes);
    /// <summary>
    /// Asynchronously retrieves all patients whose tests have finished based on the specified archive end date threshold.
    /// </summary>
    /// <param name="archiveTestEndDateAfterMinutes">The number of minutes after which a test is considered finished and eligible for retrieval.</param>
    /// <returns>A task that represents the asynchronous operation, containing a list of patients with finished tests.</returns>
    Task<List<Patient>> FindAllPatientWithFinishedTests(int archiveTestEndDateAfterMinutes);
    /// <summary>
    /// Retrieves all patients whose treatment has been finished and is eligible for archival based on the specified end date threshold.
    /// </summary>
    /// <param name="archiveTreatmentEndDateAfterMinutes">The number of minutes after the treatment end date used as the archival threshold.</param>
    /// <returns>A task that represents the asynchronous operation, containing a list of patients with finished treatments that meet the archival criteria.</returns>
    Task<List<Patient>> FindAllPatientWithFinishedTreatment(int archiveTreatmentEndDateAfterMinutes);
    /// <summary>
    /// Updates a master list option for the specified units and returns the patients affected by the change.
    /// </summary>
    /// <param name="unitIds">The list of unit identifiers whose patients should be considered for the update.</param>
    /// <param name="opt">The DTO containing the master list option update details.</param>
    /// <param name="typeName">The name of the option type being updated.</param>
    /// <returns>A task that resolves to the list of patients impacted by the master list option update.</returns>
    Task<List<Patient>> UpdateMasterListOption(List<ObjectId> unitIds, UpdateOptionMasterListDto opt, string typeName);
    /// <summary>
    /// Asynchronously retrieves a list of <see cref="Patient"/> objects associated with the specified unit identifiers and patient type.
    /// </summary>
    /// <param name="unitIds">The list of unit identifiers used to filter the patients to be returned.</param>
    /// <param name="typeName">The name of the patient type used to further refine the query results.</param>
    /// <returns>A <see cref="Task{TResult}"/> that represents the asynchronous operation, containing a list of <see cref="Patient"/> objects matching the provided unit identifiers and type.</returns>
    Task<List<Patient>> GetPatientsByUnitIds(List<ObjectId> unitIds, string typeName);
    /// <summary>
    /// Deletes the specified master list option and returns the patients affected by its removal.
    /// </summary>
    /// <param name="unitIds">The identifiers of the units whose master list option should be deleted.</param>
    /// <param name="opt">The master list option to remove.</param>
    /// <param name="typeName">The name of the master list type to which the option belongs.</param>
    /// <returns>A task that yields the collection of patients impacted by deleting the master list option.</returns>
    Task<IEnumerable<Patient>> DeleteMasterListOption(List<ObjectId> unitIds, OptionList opt, string typeName);
}