using adas_core.Domain.Enums;
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 adas_core.Domain.Models.Responses;
using MongoDB.Bson;

namespace adas_core.Application.Services.Interfaces;

public interface IPatientService : IApiRequestService
{
    /// <summary>
    /// Asynchronously retrieves a patient by their unique patient identifier, optionally including location information.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient to find.</param>
    /// <param name="withLocation">When set to <c>true</c>, includes location details in the returned patient; otherwise, only basic patient data is returned.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains the found <see cref="Patient"/>, or <c>null</c> if no patient matches the specified identifier.</returns>
    Task<Patient?> FindByPatientId(string patientId, bool withLocation = false);
    /// <summary>
    /// Asynchronously retrieves a patient by their unique identifier, applying the specified locale for localized data.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient to locate.</param>
    /// <param name="localeEnum">The locale to use when retrieving or formatting localized patient information.</param>
    /// <returns>A task that resolves to the matching patient, or <c>null</c> if no patient is found for the given identifier.</returns>
    Task<Patient?> FindByPatientIdWithLocale(string patientId, LocaleEnum localeEnum);
    /// <summary>
    /// Asynchronously retrieves an archived <see cref="Patient"/> matching the specified patient number, returning <c>null</c> when no matching archived record is found.
    /// </summary>
    /// <param name="patientNumber">The unique patient number used to look up the archived patient record.</param>
    /// <returns>A <see cref="Task{Patient}"/> that yields the matching archived <see cref="Patient"/>, or <c>null</c> if no archived patient is found.</returns>
    Task<Patient?> FindByPatientNumberArchived(string patientNumber);
    /// <summary>
    /// Retrieves a patient by their unique patient number, optionally including location information in the result.
    /// </summary>
    /// <param name="patientNumber">The unique patient number used to look up the patient.</param>
    /// <param name="withLocation">When <c>true</c>, location information is included in the returned patient; otherwise, it is omitted.</param>
    /// <returns>A task that yields the matching <see cref="Patient"/> if one is found, or <c>null</c> when no patient matches the given number.</returns>
    Task<Patient?> FindByPatientNumber(string patientNumber, bool withLocation = false);
    /// <summary>
    /// Asynchronously retrieves a patient associated with the specified location.
    /// Returns <see langword="null"/> if no matching patient is found or if the location is <see langword="null"/>.
    /// </summary>
    /// <param name="location">The patient location used to search for a matching patient. May be <see langword="null"/>.</param>
    /// <returns>A <see cref="Task{TResult}"/> that yields the matching <see cref="Patient"/>, or <see langword="null"/> if no patient is found.</returns>
    Task<Patient?> FindByLocation(PatientLocation? location);
    /// <summary>
    /// Asynchronously retrieves a patient associated with the specified Point of Care identifier.
    /// </summary>
    /// <param name="pocId">The ObjectId representing the Point of Care identifier used to look up the patient.</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?> FindByPointOfCareId(ObjectId pocId);
    /// <summary>
    /// Asynchronously retrieves a patient identified by the specified unit and point-of-care identifiers.
    /// </summary>
    /// <param name="unit">The unique identifier of the unit to search within.</param>
    /// <param name="pointOfCare">The unique identifier of the point-of-care associated with the patient.</param>
    /// <returns>A task that yields the matching <see cref="Patient"/>, or <c>null</c> if no patient is found for the given unit and point-of-care.</returns>
    Task<Patient?> FindByUnitAndPocId(ObjectId unit, ObjectId pointOfCare);
    /// <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 locate matching patients.</param>
    /// <returns>A task that represents the asynchronous operation, containing a list of <see cref="Patient"/> objects that match the specified point of care.</returns>
    Task<List<Patient>> FindByPointOfCare(string pointOfCare);
    /// <summary>
    /// Asynchronously counts the number of patients associated with the specified unit identifier.
    /// </summary>
    /// <param name="unitId">The ObjectId of the unit whose patients should be counted.</param>
    /// <returns>A task that represents the asynchronous operation, containing the total count of patients for the given unit.</returns>
    Task<long> CountPatientsByUnitId(ObjectId unitId);

    /// <summary>
    /// Asynchronously locates a patient using the provided identifiers, supporting lookup by patient
    /// ID, patient number, or location when <paramref name="findByLocation"/> is enabled.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient to find.</param>
    /// <param name="patientNumber">The patient number used as an alternative lookup key.</param>
    /// <param name="location">The patient location used when searching by location.</param>
    /// <param name="findByLocation">When <c>true</c>, the search is performed using the supplied
    /// <paramref name="location"/> instead of the patient identifiers.</param>
    /// <returns>A <see cref="Task{Patient}"/> that yields the matching <see cref="Patient"/>, or
    /// <c>null</c> if no patient is found.</returns>
    Task<Patient?> FindPatient(string? patientId, string? patientNumber, PatientLocation? location,
            bool findByLocation = false);

    /// <summary>
    /// Asynchronously inserts a new patient record into the data store.
    /// </summary>
    /// <param name="patient">The patient entity to be inserted.</param>
    Task Insert(Patient patient);
    /// <summary>
    /// Asynchronously inserts the specified <see cref="Patient"/> into the data store.
    /// </summary>
    /// <param name="patient">The patient entity to be persisted.</param>
    Task InsertAsync(Patient patient);
    /// <summary>
    /// Archives the specified patient record.
    /// </summary>
    /// <param name="patient">The patient to archive.</param>
    Task ArchivePatient(Patient patient);
    /// <summary>
    /// Archives the patient data identified by the specified patient identifier.
    /// </summary>
    /// <param name="patientid">The unique identifier of the patient whose data should be archived.</param>
    Task ArchivePatientData(ObjectId patientid);
    /// <summary>
    /// Merges the specified patient record with the existing patient identified by the old patient number.
    /// </summary>
    /// <param name="patient">The patient data to merge into the existing record.</param>
    /// <param name="oldPatienNumber">The identifier of the existing patient record to be merged.</param>
    Task MergePatient(Patient patient, string oldPatienNumber);
    /// <summary>
    /// Updates the location of a patient identified by the specified object identifier.
    /// </summary>
    /// <param name="id">The unique identifier of the patient whose location will be updated.</param>
    /// <param name="location">The new patient location, or <c>null</c> if no location is provided.</param>
    Task UpdateLocation(ObjectId id, PatientLocation? location);
    /// <summary>
    /// Updates the attending doctor for the entity identified by the specified identifier.
    /// </summary>
    /// <param name="id">The identifier of the entity whose attending doctor will be updated.</param>
    /// <param name="doctor">The new attending doctor to assign to the entity.</param>
    /// <returns>A task that represents the asynchronous update operation.</returns>
    Task UpdateAttendingDoctor(ObjectId id, Person doctor);
    /// <summary>
    /// Updates the data of an existing patient identified by the given identifier, optionally replacing the patient number when the <paramref name="updatePatientNumber"/> flag is true.
    /// </summary>
    /// <param name="id">The unique identifier of the patient whose data will be updated.</param>
    /// <param name="patientNumber">The patient number to be applied to the patient.</param>
    /// <param name="data">The person data to assign to the patient.</param>
    /// <param name="updatePatientNumber">Indicates whether the patient number should also be updated; defaults to true.</param>
    Task UpdatePatientData(ObjectId id, string patientNumber, Person data, bool updatePatientNumber = true);
    /// <summary>
    /// Updates the patient data for the specified patient identified by <paramref name="id"/> and <paramref name="patientNumber"/>, applying the changes from the provided <paramref name="patient"/> object. When <paramref name="updatePatientNumber"/> is <c>true</c>, the patient number is also updated as part of the operation; otherwise, only the remaining patient fields are updated.
    /// </summary>
    /// <param name="id">The unique identifier of the patient record to update.</param>
    /// <param name="patientNumber">The current patient number used to locate the patient record.</param>
    /// <param name="patient">The patient object containing the updated data to be applied.</param>
    /// <param name="updatePatientNumber">A flag indicating whether the patient number should also be updated; defaults to <c>true</c>.</param>
    /// <returns>A <see cref="Task"/> that represents the asynchronous update operation.</returns>
    Task UpdatePatientData(ObjectId id, string patientNumber, Patient patient, bool updatePatientNumber = true);
    /// <summary>
    /// Updates the specified patient record.
    /// </summary>
    /// <param name="patient">The patient whose information will be updated.</param>
    Task Update(Patient patient);
    /// <summary>
    /// Moves the specified patient from the old point of care to the new point of care.
    /// </summary>
    /// <param name="patient">The patient to be moved.</param>
    /// <param name="newPocId">The identifier of the destination point of care.</param>
    /// <param name="oldPocId">The identifier of the source point of care.</param>
    /// <returns>A task that resolves to <c>true</c> if the move was successful; otherwise, <c>false</c>.</returns>
    Task<bool> Move(Patient patient, ObjectId newPocId, ObjectId oldPocId);
    /// <summary>
    /// Retrieves a patient by their unique identifier, optionally including location information.
    /// Returns <c>null</c> when no patient matches the provided identifier.
    /// </summary>
    /// <param name="id">The <see cref="ObjectId"/> used to look up the patient.</param>
    /// <param name="withLocation">When <c>true</c>, includes the patient's location data in the result; otherwise, location data is omitted.</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, bool withLocation = false);
    /// <summary>
    /// Retrieves a <see cref="Box"/> associated with the specified <see cref="PointOfCare"/>, returning <see langword="null"/> when no box is found for the given point of care.
    /// </summary>
    /// <param name="poc">The point of care used to look up the associated box.</param>
    /// <param name="observations">When <see langword="true"/>, observations are included with the returned box; otherwise, observations are omitted.</param>
    /// <param name="filterObservations">An optional list of observation identifiers used to restrict which observations are loaded when <paramref name="observations"/> is <see langword="true"/>.</param>
    /// <returns>A <see cref="Task{Box}"/> that resolves to the matching <see cref="Box"/>, or <see langword="null"/> if no box exists for the specified point of care.</returns>
    Task<Box?> GetBox(PointOfCare poc, bool observations = false, List<string>? filterObservations = null);
    /// <summary>
    /// Creates a <see cref="Patient"/> instance from the provided <see cref="ApiRequest"/>, optionally ignoring location information during the creation process.
    /// </summary>
    /// <param name="apiRequest">The API request containing the data used to construct the patient.</param>
    /// <param name="ignoreLocation">When <c>true</c>, location information is ignored during patient creation. Defaults to <c>false</c>.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains the created <see cref="Patient"/>, or <c>null</c> if the patient could not be created.</returns>
    Task<Patient?> CreatePatientFromRequest(ApiRequest apiRequest, bool ignoreLocation = false);
    /// <summary>
    /// Asynchronously finds a patient based on the information provided in the specified API request.
    /// </summary>
    /// <param name="apiRequest">The API request containing the data used to look up the patient.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains the matching <see cref="Patient"/> if found, or null if no patient matches the request.</returns>
    Task<Patient?> FindPatientByApiRequest(ApiRequest apiRequest);
    /// <summary>
    /// Archives patients who have no observations recorded since the specified date.
    /// </summary>
    /// <param name="date">The cutoff date; patients without observations after this date are archived.</param>
    Task ArchivePatientWithoutObservationsSinceDate(DateTime date);

    /// <summary>
    /// Archives patient records for patients who have been discharged longer than the specified time threshold.
    /// </summary>
    /// <param name="hoursBeforeArchive">The number of hours a patient must have been discharged before being archived.</param>
    Task ArchiveDischargedPatients(int hoursBeforeArchive);
    /// <summary>
    /// Retrieves all patients, optionally including their location data when requested.
    /// </summary>
    /// <param name="withLocation">Indicates whether location information should be included in the returned patient records.</param>
    /// <returns>A task that represents the asynchronous operation, containing a list of <see cref="Patient"/> objects.</returns>
    Task<List<Patient>> FindAll(bool withLocation = false);
    /// <summary>
    /// Retrieves a paginated list of patients based on the provided pagination filter.
    /// </summary>
    /// <param name="filter">The pagination filter that defines the paging criteria used to retrieve patients.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains a <see cref="PaginationResponse{Patient}"/> with the requested page of patients.</returns>
    Task<PaginationResponse<Patient>> GetPaginatedPatients(PaginationFilter filter);

    /// <summary>
    /// Discharges patients who have been inactive since the specified date and archives them after the defined retention period.
    /// </summary>
    /// <param name="sinceDate">The date used to identify patients that have been inactive since this point in time.</param>
    /// <param name="hoursBeforeArchive">The number of hours of inactivity that must elapse before a discharged patient is archived.</param>
    Task DischargeInactivePatients(DateTime sinceDate, int hoursBeforeArchive);
    /// <summary>
    /// Updates an existing patient record with the provided patient data. Returns the updated patient, or <c>null</c> if no matching patient was found.
    /// </summary>
    /// <param name="updatedPatient">The patient object containing the updated information to be persisted.</param>
    /// <returns>A <see cref="Task{Patient}"/> that resolves to the updated <see cref="Patient"/>, or <c>null</c> if the patient could not be found.</returns>
    Task<Patient?> UpdateOne(Patient updatedPatient);

    /// <summary>
    /// Sends an asynchronous broadcast notification to inform relevant subscribers or systems about a newly registered patient.
    /// </summary>
    /// <param name="patient">The patient whose information will be included in the broadcast notification.</param>
    Task SendNewPatientBroadcast(Patient patient);
    /// <summary>
    /// Asynchronously sends a broadcast notification about an update to the specified patient.
    /// </summary>
    /// <param name="patient">The patient whose update information will be broadcast.</param>
    Task SendPatientUpdateBroadcast(Patient patient);
    /// <summary>
    /// Asynchronously retrieves the list of inactive Patients of Care (PoC), allowing consumers to identify
    /// patients that are no longer active in the system for reporting, cleanup, or follow-up workflows.
    /// </summary>
    /// <returns>A task representing the asynchronous operation, containing a list of inactive <see cref="Patient"/> records.</returns>
    Task<List<Patient>> FindInActivePoC();
    /// <summary>
    /// Retrieves a list of patients associated with inactive PoC records.
    /// </summary>
    /// <returns>A task that represents the asynchronous operation. The task result contains a list of <see cref="Patient"/> objects associated with inactive PoC records.</returns>
    Task<List<Patient>> FindInInactivePoC();

    /// <summary>
    /// Retrieves a <see cref="Patient"/> associated with the specified <paramref name="item"/> point of care, returning <c>null</c> when no matching patient is found.
    /// When <paramref name="observations"/> is <c>true</c>, the result includes the patient's observations, optionally restricted by the identifiers supplied in <paramref name="filterObservations"/>.
    /// </summary>
    /// <param name="item">The point of care used to look up the associated patient.</param>
    /// <param name="observations">Indicates whether the patient's observations should be included in the returned patient.</param>
    /// <param name="filterObservations">An optional list of observation identifiers used to filter which observations are returned when <paramref name="observations"/> is <c>true</c>.</param>
    /// <returns>A <see cref="Task{Patient}"/> that resolves to the matching <see cref="Patient"/>, or <c>null</c> if no patient is found for the given point of care.</returns>
    Task<Patient?> GetByPointOfCare(PointOfCare item, bool observations = false,
            List<string>? filterObservations = null);

    /// <summary>
    /// Asynchronously retrieves a <see cref="Patient"/> matching the specified point of care, optionally narrowed by unit and locale.
    /// </summary>
    /// <param name="item">The point of care used to locate the patient.</param>
    /// <param name="unit">An optional unit that further filters the lookup.</param>
    /// <param name="localeEnum">An optional locale used to scope the search.</param>
    /// <returns>A task that yields the matching <see cref="Patient"/>, or <c>null</c> when no patient is found.</returns>
    Task<Patient?> GetByPointOfCareAndLocale(PointOfCare item, Unit? unit, LocaleEnum? localeEnum);
    /// <summary>
    /// Updates the altable (allergy table) information for the specified patient and returns the updated patient.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose altable is being updated.</param>
    /// <param name="altable">The option list containing the altable data to apply to the patient.</param>
    /// <param name="user">The user performing the update, or <c>null</c> when no user context is available.</param>
    /// <returns>A task that resolves to the updated <see cref="Patient"/>, or <c>null</c> if the patient was not found.</returns>
    Task<Patient?> UpdatePatientAltable(ObjectId patientId, OptionList altable, User? user);
    /// <summary>
    /// Exits a patient identified by the given identifier, optionally archiving the patient record.
    /// </summary>
    /// <param name="id">The unique identifier of the patient to exit.</param>
    /// <param name="archivePatient">When true, the patient record is archived as part of the exit process; when false, archiving is skipped.</param>
    Task ExitPatientById(ObjectId id, bool archivePatient = true);

    /// <summary>
    /// Updates the specified master list for a patient with the provided options and returns the updated patient record.
    /// </summary>
    /// <param name="patientId">The identifier of the patient whose master list is being updated.</param>
    /// <param name="typeName">The type of master list to update.</param>
    /// <param name="updatedOptions">The new list of options to apply to the master list.</param>
    /// <param name="user">The user performing the update, or null if not specified.</param>
    /// <param name="carePlanLog">Optional care plan log entries associated with the update.</param>
    /// <returns>A task that returns the updated <see cref="Patient"/>, or null if the patient was not found.</returns>
    Task<Patient?> UpdatePatientMasterList(ObjectId patientId, MasterListType typeName,
            List<OptionList> updatedOptions,
            User? user, List<OptionList>? carePlanLog);

    /// <summary>
    /// Generates a nurse care plan of the specified type, using the provided options, and inserts it for the patient.
    /// </summary>
    /// <param name="carePlanType">The master list type that determines which nurse care plan template to generate.</param>
    /// <param name="options">Optional list of options applied during care plan generation. May be null.</param>
    /// <param name="patient">The patient for whom the nurse care plan is generated and inserted.</param>
    /// <param name="user">The user associated with the care plan generation. May be null.</param>
    /// <returns>A task that completes when the nurse care plan has been generated and inserted.</returns>
    Task GenerateNurseCarePlanAndInsert(MasterListType carePlanType, List<OptionList>? options,
            Patient patient, User? user);

    /// <summary>
    /// Asynchronously updates the incoming data for the specified patient by applying the provided patient information.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose incoming data is being updated.</param>
    /// <param name="person">The patient object containing the incoming data to be applied to the patient record.</param>
    Task UpdatePatientIncomingData(ObjectId patientId, Patient person);
    /// <summary>
    /// Asynchronously updates the demographic data of an existing patient identified by the specified patient 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 new demographic information to apply.</param>
    /// <param name="user">The user performing the update operation, or <see langword="null"/> if no user context is available.</param>
    Task UpdatePatientDemographicData(ObjectId patientId, Patient person, User? user);
    /// <summary>
    /// Searches for a patient by their patient number within a distinct unit.
    /// </summary>
    /// <param name="patientNumber">The patient number used to identify the patient.</param>
    /// <param name="unitId">The identifier of the distinct unit where the patient is registered.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains the matching <see cref="Patient"/> if found; otherwise, <see langword="null"/>.</returns>
    Task<Patient?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId);
    /// <summary>
    /// Asynchronously retrieves all patients whose procedures have been finished, filtering based on the specified archive threshold for procedure end times.
    /// </summary>
    /// <param name="archiveProcedureEndDateAfterMinutes">The number of minutes after the procedure end date used as the archive threshold to qualify patients with finished procedures.</param>
    /// <returns>A task that represents the asynchronous operation, containing a list of <see cref="Patient"/> objects that match the finished procedures criteria.</returns>
    Task<List<Patient>> FindAllPatientWithFinishedProcedures(int archiveProcedureEndDateAfterMinutes);
    /// <summary>
    /// Asynchronously retrieves all patients who have completed a test, using the specified archive window in minutes to determine which tests are considered finished.
    /// </summary>
    /// <param name="archiveTestEndDateAfterMinutes">The time window in minutes applied to the test end date to identify tests that should be treated as finished/archived.</param>
    /// <returns>A task that represents the asynchronous operation, containing a list of <see cref="Patient"/> instances with finished tests.</returns>
    Task<List<Patient>> FindAllPatientWithFinishedTest(int archiveTestEndDateAfterMinutes);
    /// <summary>
    /// Retrieves a list of patients whose treatments have finished, based on the specified archive time threshold in minutes after the treatment end date.
    /// </summary>
    /// <param name="archiveTreatmentEndDateAfterMinutes">The number of minutes after the treatment end date used to determine which finished treatments should be included.</param>
    /// <returns>A task representing the asynchronous operation, containing a list of patients with finished treatment matching the specified criteria.</returns>
    Task<List<Patient>> FindAllPatientWithFinishedTreatment(int archiveTreatmentEndDateAfterMinutes);

    /// <summary>
    /// Updates the incoming income data of a patient using the specified changes and the user performing the operation.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose income data is being updated.</param>
    /// <param name="person">The patient entity associated with the update.</param>
    /// <param name="personDataChange">The income data changes to apply to the patient.</param>
    /// <param name="user">The user performing the update operation.</param>
    Task UpdatePatientIncomingData(ObjectId patientId, Patient person, PatientIncomeData personDataChange,
            User user);

    /// <summary>
    /// Updates a patient master list item change based on the provided update options, unit list, and type name.
    /// </summary>
    /// <param name="opt">The update options for the master list item.</param>
    /// <param name="unitList">The collection of units associated with the update.</param>
    /// <param name="typeName">The name of the type used to categorize the master list item.</param>
    Task UpdatePatientMasterListItemChange(UpdateOptionMasterListDto opt, IEnumerable<Unit> unitList,
            string typeName);

    /// <summary>
    /// Deletes a patient master list item based on the specified option, unit list, and type name.
    /// </summary>
    /// <param name="opt">The option list containing the details of the patient master list item to delete.</param>
    /// <param name="unitList">The collection of units associated with the patient master list item.</param>
    /// <param name="typeName">The name of the type identifying the patient master list item to be deleted.</param>
    Task DeletePatientMasterListItem(OptionList opt, IEnumerable<Unit> unitList, string typeName);
}