#nullable enable
using System.Text.Json;
using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Models.Common;
using Baya.Application.Models.Patients;
using Baya.Application.Models.Reviews;
using Baya.Domain.Entities.User;
using Mediator;

namespace Baya.Application.Features.PatientCareRecords.Queries.GetPatientHistory;

/// <summary>
/// Reads a patient's longitudinal care history under the strict clinical access rule (enforced here, not just
/// at the route): the owning customer, a nurse with a confirmed booking for the patient, or an admin — nobody
/// else. Only after the check passes are the ciphertext bodies decrypted via <see cref="IFieldEncryptor"/>.
/// </summary>
internal sealed class GetPatientHistoryQueryHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    IFieldEncryptor fieldEncryptor)
    : IRequestHandler<GetPatientHistoryQuery, OperationResult<PagedResult<CareRecordDto>>>
{
    private static readonly string[] AdminRoles = [RoleNames.Admin, RoleNames.SuperAdmin];

    public async ValueTask<OperationResult<PagedResult<CareRecordDto>>> Handle(
        GetPatientHistoryQuery request, CancellationToken cancellationToken)
    {
        if (currentUser.UserId is not { } userId)
            return OperationResult<PagedResult<CareRecordDto>>.UnauthorizedResult("Not authenticated.");

        var page = request.Page < 1 ? 1 : request.Page;
        var pageSize = request.PageSize is < 1 or > 100 ? 20 : request.PageSize;
        var records = unitOfWork.PatientCareRecordRepository;

        var ownerCustomerId = await records.GetPatientOwnerCustomerIdAsync(request.PatientId, cancellationToken);
        if (ownerCustomerId is null)
            return OperationResult<PagedResult<CareRecordDto>>.NotFoundResult("Patient not found.");

        var allowed = currentUser.Roles.Any(AdminRoles.Contains);
        if (!allowed)
        {
            var customerProfileId = await unitOfWork.CustomerProfileRepository.GetProfileIdByUserIdAsync(userId, cancellationToken);
            if (customerProfileId == ownerCustomerId)
            {
                allowed = true;
            }
            else
            {
                var nurseProfileId = await unitOfWork.NurseProfileRepository.GetProfileIdByUserIdAsync(userId, cancellationToken);
                if (nurseProfileId is { } nurseId)
                    allowed = await records.NurseHasQualifyingBookingForPatientAsync(nurseId, request.PatientId, cancellationToken);
            }
        }

        if (!allowed)
            return OperationResult<PagedResult<CareRecordDto>>.ForbiddenResult(
                "You do not have clinical access to this patient's care records.");

        var cipher = await records.GetPatientHistoryAsync(request.PatientId, page, pageSize, cancellationToken);

        var items = cipher.Items
            .Select(r => new CareRecordDto(
                r.Id, r.PatientId, r.BookingId, r.NurseProfileId, r.NurseName,
                fieldEncryptor.Decrypt(r.BodyEncrypted), ParseTaskResults(r.TaskResultsJson), r.RecordedAt))
            .ToList();

        return OperationResult<PagedResult<CareRecordDto>>.SuccessResult(
            new PagedResult<CareRecordDto>(items, cipher.Total, cipher.Page, cipher.PageSize));
    }

    private static IReadOnlyList<TaskResultDto> ParseTaskResults(string? json)
    {
        if (string.IsNullOrWhiteSpace(json))
            return [];
        try
        {
            return JsonSerializer.Deserialize<List<TaskResultDto>>(json) ?? [];
        }
        catch (JsonException)
        {
            return [];
        }
    }
}
