using adas_core.Domain.Models;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
using MongoDB.Driver;

namespace adas_core.Application.Services.Interfaces;

public interface IObservationService : IApiRequestService
{
    //REMOVE
    /*
    List<PatientObservation> FindLastObservations(ObjectId patientId, string codingSystem, string code, int num = 2);
    */
    /// <summary>
    /// Asynchronously retrieves all patient observations associated with the specified patient identifier.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose observations are to be retrieved.</param>
    /// <returns>A task that returns an <see cref="IAsyncCursor{TDocument}"/> of <see cref="PatientObservation"/> instances for the given patient.</returns>
    Task<IAsyncCursor<PatientObservation>> FindByPatientIdAsync(ObjectId patientId);

    /// <summary>
    /// Asynchronously retrieves patient observations matching the specified patient identifier, coding system, and name.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose observations are being queried.</param>
    /// <param name="codingSystem">The coding system used to classify the observations (e.g., LOINC, SNOMED).</param>
    /// <param name="name">The name of the observation to filter by.</param>
    /// <returns>A task that represents the asynchronous operation, containing an <see cref="IAsyncCursor{TDocument}"/> of <see cref="PatientObservation"/> matching the criteria.</returns>
    Task<IAsyncCursor<PatientObservation>> FindByPatientIdAndCodingSystemAsync(ObjectId patientId, string codingSystem,
            string name);

    /// <summary>
    /// Retrieves the most recent patient observations for the specified patient, optionally filtered by a set of observation codes.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose observations are being queried.</param>
    /// <param name="num">The maximum number of most recent observations to return. Defaults to 2.</param>
    /// <param name="filterObservations">An optional list of observation codes used to restrict the result set; if null, observations are not filtered by code.</param>
    /// <returns>A task that resolves to a list of the most recent <see cref="PatientObservation"/> entries matching the criteria.</returns>
    Task<List<PatientObservation>> FindLastObservations(ObjectId patientId, int num = 2,
            List<string>? filterObservations = null);

    /// <summary>
    /// Updates the status of the provided patient observations that have reached their expiration.
    /// </summary>
    /// <param name="expiredObservations">The list of patient observations to update as expired.</param>
    Task UpdateExpiredObservations(List<PatientObservation> expiredObservations);
    /// <summary>
    /// Asynchronously expires observations that are no longer valid and recalculates the dependent data.
    /// </summary>
    Task ExpireObservationsAndRecalculateAsync();
    /// <summary>
    /// Asynchronously expires active alerts and powers off the device.
    /// </summary>
    Task ExpireAlertsAndPowerOffAsync();

    /// <summary>
    /// Retrieves the most recent unique patient observations for the specified patient, filtered by observation name, with an optional cache expiration window in seconds.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose observations are being queried.</param>
    /// <param name="name">The name of the observation to filter by.</param>
    /// <param name="expires">Optional expiration time in seconds applied to the cached results. If <c>null</c>, no expiration is applied.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains a list of the latest unique <see cref="PatientObservation"/> values matching the specified patient and name.</returns>
    Task<List<PatientObservation>> FindLatestUniqueValuesByName(ObjectId patientId, string name, int? expires);

    /// <summary>
    /// Retrieves the most recent observations recorded for a patient, optionally filtered to a specific set of fields.
    /// </summary>
    /// <param name="patientId">The identifier of the patient whose observations are being retrieved.</param>
    /// <param name="filterObservations">An optional list of fields used to restrict which observations are returned. When null, observations for all fields are considered.</param>
    /// <param name="mapped">Indicates whether the returned observations should be mapped (default true) or returned in their raw form.</param>
    /// <param name="ct">The cancellation token used to cancel the asynchronous operation.</param>
    /// <returns>A task that resolves to a list of the patient's most recent <see cref="PatientObservation"/> entries.</returns>
    Task<List<PatientObservation>> FindLastObservationsByField(ObjectId patientId,
            List<Field>? filterObservations = null, bool mapped = true, CancellationToken ct = default);

    /// <summary>
    /// Retrieves the most recent intravenous line observations associated with a specific location for the given patient.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose intravenous line observations are being queried.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains a list of nullable <see cref="PatientObservation"/> entries representing the latest intravenous line observations by location, where individual entries may be <c>null</c> when no data is available.</returns>
    Task<List<PatientObservation?>> FindLastIntravenousLinesObservationByLocation(ObjectId patientId);
    /// <summary>
    /// Asynchronously inserts a patient observation, with options to control whether the observation is persisted and whether it is mapped.
    /// </summary>
    /// <param name="patientObservation">The patient observation to insert.</param>
    /// <param name="persistObs">Indicates whether the observation should be persisted; defaults to <c>true</c>.</param>
    /// <param name="mapObs">Indicates whether the observation should be mapped; defaults to <c>true</c>.</param>
    Task InsertObservation(PatientObservation patientObservation, bool persistObs = true, bool mapObs = true);
    /// <summary>
    /// Inserts the specified patient observation only if it has changed, optionally persisting the observation and applying a mapping during the insert.
    /// </summary>
    /// <param name="name">The name associated with the patient observation being evaluated for changes.</param>
    /// <param name="observation">The patient observation to compare against the existing value and potentially insert.</param>
    /// <param name="persistObs">Indicates whether the observation should be persisted when it is inserted. Defaults to <c>true</c>.</param>
    /// <param name="mapObs">Indicates whether the observation should be mapped as part of the insert operation. Defaults to <c>true</c>.</param>
    /// <returns>A task that returns <c>true</c> if the observation was inserted because a change was detected; otherwise, <c>false</c> if no insert was performed.</returns>
    Task<bool> InsertIfChanged(string name, PatientObservation observation, bool persistObs = true, bool mapObs = true);
    /// <summary>
    /// Inserts a new nurse observation for a patient into the underlying data store.
    /// </summary>
    /// <param name="obs">The patient observation data recorded by the nurse to be persisted.</param>
    Task InsertNurseObservation(PatientObservation obs);
    /// <summary>
    /// Asynchronously deletes records associated with the specified patient identifier.
    /// </summary>
    /// <param name="id">The <see cref="ObjectId"/> of the patient whose related records should be removed.</param>
    Task DeleteByPatientId(ObjectId id);
    /// <summary>
    /// Asynchronously archives the specified patient observation, preserving it for historical or compliance purposes while removing it from the active set.
    /// </summary>
    /// <param name="observation">The patient observation to archive.</param>
    /// <returns>A task that represents the asynchronous archive operation.</returns>
    Task Archive(PatientObservation observation);
    /// <summary>
    /// Asynchronously maps a <see cref="PatientObservation"/> to a corresponding observation, optionally restricting the lookup to name-based matching. Returns <see langword="null"/> when no matching observation is found.
    /// </summary>
    /// <param name="obs">The source <see cref="PatientObservation"/> to be mapped.</param>
    /// <param name="onlyByName">When <see langword="true"/>, restricts the lookup to name-based matching; otherwise, the default mapping behavior is applied.</param>
    /// <returns>A <see cref="Task{TResult}"/> containing the mapped <see cref="PatientObservation"/>, or <see langword="null"/> if no match is found.</returns>
    Task<PatientObservation?> MapObservation(PatientObservation obs, bool onlyByName = false);
    /// <summary>
    /// Asynchronously retrieves the most recent observation time for each patient, returning a mapping of patient identifiers to their last observation timestamps.
    /// </summary>
    /// <returns>A task that represents the asynchronous operation. The task result contains a dictionary where each key is a patient <see cref="ObjectId"/> and the associated value is the <see cref="DateTime"/> of that patient's latest observation.</returns>
    Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTime();
    /// <summary>
    /// Asynchronously maps or looks up a <see cref="PatientObservation"/> based on the name of the provided observation, returning the matching observation or <c>null</c> when no match is found.
    /// </summary>
    /// <param name="obs">The <see cref="PatientObservation"/> whose name is used to perform the mapping or lookup.</param>
    /// <returns>A <see cref="Task{PatientObservation}"/> that resolves to the matching <see cref="PatientObservation"/>, or <c>null</c> if no corresponding observation is found.</returns>
    Task<PatientObservation?> MapObservationsByName(PatientObservation obs);
    /// <summary>
    /// Archives the specified patient, moving their record out of the active set so that it is retained for historical or compliance purposes while no longer appearing in routine operational queries.
    /// </summary>
    /// <param name="patient">The patient whose record is to be archived.</param>
    Task Archive(Patient patient);
    /// <summary>
    /// Archives records associated with the specified patient identifier.
    /// </summary>
    /// <param name="id">The unique identifier of the patient whose records should be archived.</param>
    Task ArchiveByPatientId(ObjectId id);

    /// <summary>
    /// Updates an existing patient observation in the data store.
    /// </summary>
    /// <param name="observation">The patient observation containing the updated information.</param>
    /// <returns>A task that represents the asynchronous update operation.</returns>
    Task UpdateObservation(PatientObservation observation);
    /// <summary>
    /// Updates the specified identifier field (<paramref name="nameId"/>) across multiple objects, replacing the existing value <paramref name="oldId"/> with the new value <paramref name="id"/>.
    /// </summary>
    /// <param name="nameId">The name of the identifier field to be updated.</param>
    /// <param name="id">The new <see cref="ObjectId"/> value to assign to the field.</param>
    /// <param name="oldId">The current <see cref="ObjectId"/> value to be replaced.</param>
    /// <returns>A task that represents the asynchronous bulk update operation.</returns>
    Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);

    /// <summary>
    /// Asynchronously broadcasts a patient observation to subscribed listeners or endpoints.
    /// </summary>
    /// <param name="obs">The base patient observation to be broadcast.</param>
    Task SendObsBroadcast(BasePatientObservation obs);
    /// <summary>
    /// Sends a broadcast containing the specified patient observations to the given patient location.
    /// </summary>
    /// <param name="obs">The list of patient observations to include in the broadcast.</param>
    /// <param name="location">The target patient location that will receive the broadcast.</param>
    Task SendObsBroadcast(List<PatientObservation> obs, PatientLocation location);
    /// <summary>
    /// Asynchronously sends a broadcast of patient observations to the specified Point of Care (POC) system.
    /// </summary>
    /// <param name="obs">The list of patient observations to be transmitted in the broadcast.</param>
    /// <param name="pocId">The identifier of the Point of Care system that will receive the observations.</param>
    Task SendObsBroadcast(List<PatientObservation> obs, ObjectId pocId);

    /// <summary>
    /// Asynchronously retrieves the most recent <see cref="PatientObservation"/> for a patient recorded before the specified date, optionally filtered by observation name.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose observation is being queried.</param>
    /// <param name="date">The cutoff date; only observations recorded strictly before this date are considered.</param>
    /// <param name="obsName">The optional name of the observation to filter by, or <c>null</c> to match any observation.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains the latest matching <see cref="PatientObservation"/>, or <c>null</c> if none was found before the given date.</returns>
    Task<PatientObservation?> FindLastBeforeDate(ObjectId patientId, DateTime date, string? obsName);

    /// <summary>
    /// Asynchronously retrieves the patient observations for the specified patient that share the given date, optionally filtered by observation name.
    /// </summary>
    /// <param name="patientId">The identifier of the patient whose observations will be searched.</param>
    /// <param name="date">The date used to match observations.</param>
    /// <param name="obsName">The optional observation name used to filter the results. When null, observations are not filtered by name.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains a list of matching <see cref="PatientObservation"/> records, or null when no observations match the criteria.</returns>
    Task<List<PatientObservation>?> FindAnyWithSameDate(ObjectId patientId, DateTime date, string? obsName);

    //TODO To implement
    /// <summary>
    /// Retrieves all patient observations recorded before the specified date, optionally filtered to a specific set of observation types.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose observations are being queried.</param>
    /// <param name="date">The cutoff date; only observations recorded before this date will be returned.</param>
    /// <param name="filterObservations">An optional list of observation identifiers used to restrict the results to specific observation types. When null, all observation types are included.</param>
    /// <returns>A task that resolves to a list of PatientObservation instances matching the criteria.</returns>
    Task<List<PatientObservation>> FindAllBeforeDate(ObjectId patientId, DateTime date,
            List<string>? filterObservations = null);

    //TODO To implement
    /// <summary>
    /// Retrieves all <see cref="PatientObservation"/> entries for the specified patient recorded after the given date, optionally restricted to a subset of observation names.
    /// </summary>
    /// <param name="patientId">The identifier of the patient whose observations are being queried.</param>
    /// <param name="date">The cutoff date; only observations with a timestamp after this value are returned.</param>
    /// <param name="filterObservations">An optional list of observation names to restrict the result to. When <c>null</c> or empty, all observations after the date are returned.</param>
    /// <returns>A <see cref="Task{T}"/> that yields the list of matching <see cref="PatientObservation"/> entries.</returns>
    Task<List<PatientObservation>> FindAllAfterDate(ObjectId patientId, DateTime date,
            List<string>? filterObservations = null);

    /// <summary>
    /// Retrieves a paginated list of patient observations for the specified patient within an optional date range, optionally filtered by observation names and including archived records when requested.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose observations are being queried.</param>
    /// <param name="startDate">The inclusive lower bound of the observation date range, or null to apply no lower bound.</param>
    /// <param name="endDate">The inclusive upper bound of the observation date range, or null to apply no upper bound.</param>
    /// <param name="filterObservations">An optional list of observation names used to restrict the returned observations.</param>
    /// <param name="fromArchived">When true, observations are retrieved from archived records; otherwise, only active records are considered.</param>
    /// <param name="filter">An optional pagination filter applied to the result set.</param>
    /// <returns>A task that resolves to a list of <see cref="PatientObservation"/> entries matching the provided criteria.</returns>
    Task<List<PatientObservation>> FindAllBetweenDates(ObjectId patientId, DateTime? startDate, DateTime? endDate,
            List<string>? filterObservations = null, bool fromArchived = false, PaginationFilter? filter = null);

    /// <summary>
    /// Asynchronously retrieves the most recent non-expired observations for the specified patient, optionally filtered by observation name and constrained by pagination parameters.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose observations are being queried.</param>
    /// <param name="name">The name of the observation type used to filter the results.</param>
    /// <param name="endAfter">Optional parameter that defines the pagination boundary; when provided, observations are returned starting after this position.</param>
    /// <param name="num">Optional parameter that limits the maximum number of observations returned.</param>
    /// <returns>A task representing the asynchronous operation, containing a collection of matching non-expired <see cref="PatientObservation"/> records; an empty collection is returned if none are found.</returns>
    Task<IEnumerable<PatientObservation>> FindLastNotExpiredObservatonsByPatient(ObjectId patientId, string name,
            int? endAfter = null, int? num = null);


    /// <summary>
    /// Checks observations and expires those that meet the expiration criteria.
    /// </summary>
    Task CheckAndExpireObservations();

    /// <summary>
    /// Retrieves patient observations that have not been marked as expired but should be, based on their validity period or business rules.
    /// </summary>
    /// <returns>An asynchronous stream of <see cref="PatientObservation"/> instances that are not expired but meet the criteria to be expired.</returns>
    IAsyncEnumerable<PatientObservation> FindNotExpiredObservationsShouldBeExpired();


    /// <summary>
    /// Processes a collection of patient observations, associating them with the specified patient and recording the message time.
    /// </summary>
    /// <param name="observations">The list of patient observations to process.</param>
    /// <param name="patient">The patient associated with the observations.</param>
    /// <param name="messageTime">The timestamp of the message containing the observations.</param>
    /// <param name="observationData">Optional additional data related to the observations.</param>
    void ProcessObservations(List<PatientObservation> observations, Patient patient, DateTime messageTime,
            ObservationData? observationData = null);

    /// <summary>
    /// Asynchronously processes and expires observations that have exceeded their validity period.
    /// </summary>
    Task ExpireObservations();

    /// <summary>
    /// Asynchronously inserts a simple patient observation record.
    /// </summary>
    /// <param name="observation">The patient observation to insert.</param>
    /// <returns>A task that represents the asynchronous insert operation.</returns>
    Task InsertSimpleObservation(PatientObservation observation);
    /// <summary>
    /// Asynchronously retrieves a paginated collection of patient observations based on the specified filter criteria.
    /// </summary>
    /// <param name="filter">The pagination filter that controls the page size, page number, and any additional query criteria applied to the patient observations.</param>
    /// <returns>A task that represents the asynchronous operation, containing a <see cref="PaginationResponse{T}"/> of <see cref="PatientObservation"/> with the requested page of results.</returns>
    Task<PaginationResponse<PatientObservation>> GetPaginatedObservations(PaginationFilter filter);
    /// <summary>
    /// Asynchronously saves a nurse observation request.
    /// </summary>
    /// <param name="request">The API request containing the nurse observation data to save.</param>
    Task SaveRequestNurseObsAsync(ApiRequest request);
}