using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.Masters;
using adas_core.Domain.Models.MongoModels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;

namespace adas_core.Application.Services;

/// <summary>
/// Provides operations for managing patient care plans, implementing the contract defined by <see cref="IPatientCarePlanService"/>.
/// Serves as the concrete service layer responsible for handling care plan related business logic.
/// </summary>
public class PatientCarePlanService : IPatientCarePlanService
{
    private readonly IArchivePatientCarePlanService _archivePatientCarePlanService;
    private readonly ILocalAuditService _auditService;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILogger<PatientCarePlanService> _logger;
    private readonly IPatientCarePlanRepository _patientCarePlanRepository;
    private readonly IUserRepository _userRepository;

    public PatientCarePlanService(
        ILogger<PatientCarePlanService> logger,
        IPatientCarePlanRepository patientCarePlanRepository,
        IUserRepository userRepository,
        IArchivePatientCarePlanService archivePatientCarePlanService,
        IHttpContextAccessor httpContextAccessor,
        ILocalAuditService auditService)
    {
        _logger = logger;
        _patientCarePlanRepository = patientCarePlanRepository;
        _userRepository = userRepository;
        _archivePatientCarePlanService = archivePatientCarePlanService;
        _httpContextAccessor = httpContextAccessor;
        _auditService = auditService;
        _httpContextAccessor = httpContextAccessor;
    }

    /// <summary>
    /// Retrieves a list of patient care plans associated with the specified patient identifier.
    /// Returns an empty list if an error occurs while querying the repository.
    /// </summary>
    /// <param name="patientId">The unique identifier 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"/> for the patient, or an empty list if an error occurs.</returns>
    public async Task<List<PatientCarePlan>> FindByPatientId(ObjectId patientId)
        {
            try
            {
                return await _patientCarePlanRepository.FindByPatientId(patientId);
            }
            catch (Exception e)
            {
                _logger.LogError("Error finding Patient care plan by patientId: {PatientId} message: {Message}", patientId,
                    e.Message);
                return [];
            }
        }

    /// <summary>
    /// Retrieves the list of patient care plans associated with the specified user identifier. If an error occurs during retrieval, the error is logged and an empty list is returned as a fallback.
    /// </summary>
    /// <param name="userId">The unique identifier of the user whose patient care plans are being retrieved.</param>
    /// <returns>A task that represents the asynchronous operation, containing a list of <see cref="PatientCarePlan"/> objects for the user, or an empty list if an error occurs.</returns>
    public async Task<List<PatientCarePlan>> FindByUserId(ObjectId userId)
        {
            try
            {
                return await _patientCarePlanRepository.FindByUserId(userId);
            }
            catch (Exception e)
            {
                _logger.LogError("Error finding Patient care plan by userId: {UserId} message: {Message}", userId,
                    e.Message);
                return [];
            }
        }

    /// <summary>
    /// Retrieves all patient care plans from the repository, returning an empty list if an error occurs while fetching the data.
    /// </summary>
    /// <returns>A task that represents the asynchronous operation, containing a list of <see cref="PatientCarePlan"/> objects, or an empty list if the retrieval fails.</returns>
    public async Task<List<PatientCarePlan>> FindAll()
        {
            try
            {
                return await _patientCarePlanRepository.FindAll();
            }
            catch (Exception e)
            {
                _logger.LogError("Error finding all Patient care plan message: {Message}", e.Message);
                return [];
            }
        }

    /// <summary>
    /// Inserts a new patient care plan into the repository, stamping it with the current UTC time, and records an audit log entry for the operation. Any errors encountered during the insertion or audit logging are caught and logged without rethrowing.
    /// </summary>
    /// <param name="patientCarePlan">The patient care plan entity to be inserted into the repository.</param>
    public async Task InsertOneAsync(PatientCarePlan patientCarePlan)
        {
            try
            {
                patientCarePlan.Time = DateTime.UtcNow;
                await _patientCarePlanRepository.InsertOneAsync(patientCarePlan);
                await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, patientCarePlan);
            }
            catch (Exception e)
            {
                _logger.LogError("Error InsertOneAsync Patient care plan message: {Message} trace: {Trace}", e.Message,
                    e.StackTrace);
            }
        }

    /// <summary>
    /// Creates archive records in the patient care plan repository for the specified care plan items that have expired from a job, attributing the action to the system user.
    /// </summary>
    /// <param name="patientWithFinishedProcedure">The patient associated with the finished procedure whose care plan items are being archived.</param>
    /// <param name="itemsToArchive">The list of care plan options to archive.</param>
    public async Task ArchiveCarePlanFromJob(Patient patientWithFinishedProcedure, List<OptionList> itemsToArchive)
        {
            var systemUser = await _userRepository.GetOrCreateSystemUser();
            foreach (var item in itemsToArchive)
            {
                var itemToArchive = new PatientCarePlan
                {
                    PatientId = patientWithFinishedProcedure.Id,
                    PatientNumber = patientWithFinishedProcedure.PatientNumber,
                    PointOfCareId = patientWithFinishedProcedure.PointOfCareId,
                    UserId = systemUser.Id,
                    CarePlan = item,
                    Description = "Archive item expired from job",
                    Action = ActionsEnum.CrudAction.Archive,
                    Time = DateTime.UtcNow
                };
                await _patientCarePlanRepository.InsertOneAsync(itemToArchive);
            }
        }

    /// <summary>
    /// Archives all care plans associated with the specified patient by moving them to the archive service, removing them from the active repository, and creating audit log entries for each archived record.
    /// </summary>
    /// <param name="patientid">The identifier of the patient whose care plans will be archived.</param>
    public async Task ArchiveByPatientId(ObjectId patientid)
        {
            var carePlansToArchive = await FindByPatientId(patientid);
            await _archivePatientCarePlanService.InsertManyAsync(carePlansToArchive);
            foreach (var item in carePlansToArchive)
            {
                await _patientCarePlanRepository.DeleteAsync(item.Id);
                await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, item, null);
            }
        }

    /// <summary>
    /// Updates multiple patient care plan records by replacing the specified old object identifier with a new one for the given patient.
    /// </summary>
    /// <param name="patientid">The unique identifier of the patient whose care plan records will be updated.</param>
    /// <param name="patientId">The new object identifier that will replace the old identifier in the matching care plan records.</param>
    /// <param name="oldId">The existing object identifier to be replaced by the new identifier.</param>
    public async Task UpdateManyObjectId(string patientid, ObjectId patientId, ObjectId oldId)
        {
            await _patientCarePlanRepository.UpdateManyObjectId(patientid, patientId, oldId);
        }
}