#nullable enable
using Baya.Application.Common;
using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Contracts.Search;
using Baya.Application.Contracts.SupportAlerts;
using Baya.Application.Models.Common;
using Baya.Application.Models.Verification;
using Baya.Domain.Entities.SupportAlerts;
using Baya.Domain.Entities.Verification;
using Mediator;

namespace Baya.Application.Features.Verification.Commands.ScanExpiringCredentials;

internal sealed class ScanExpiringCredentialsCommandHandler(
    IUnitOfWork unitOfWork,
    ISupportAlertService supportAlerts,
    INotificationDispatcher notifications,
    ICacheService cache,
    IDateTimeProvider dateTimeProvider,
    ISearchIndexMaintainer searchIndex)
    : IRequestHandler<ScanExpiringCredentialsCommand, OperationResult<ScanExpiringResult>>
{
    public async ValueTask<OperationResult<ScanExpiringResult>> Handle(ScanExpiringCredentialsCommand request, CancellationToken cancellationToken)
    {
        var (page, pageSize) = Pagination.Normalize(request.Page, request.PageSize);
        var now = dateTimeProvider.UtcNow;
        var repo = unitOfWork.VerificationRepository;

        var expired = await repo.GetExpiredPassedStepsAsync(now, page, pageSize, cancellationToken);
        var scannedSteps = expired.Count;
        var revertedNurses = 0;

        foreach (var group in expired.GroupBy(e => e.NurseVerificationId))
        {
            var verification = await repo.GetTrackedByIdAsync(group.Key, cancellationToken);
            if (verification is null)
                continue;

            var nurseId = verification.NurseId;

            // Load and guard the tracked profile BEFORE mutating any step: an early `continue` must never
            // leave a dirty (Expired) step in the shared scoped DbContext for a later nurse's commit to
            // flush — that would persist the step-expiry without its atomic is_verified re-gate.
            var profile = await unitOfWork.NurseProfileRepository.GetTrackedByIdAsync(nurseId, cancellationToken);
            if (profile is null)
                continue;

            var revertedAny = false;
            foreach (var row in group)
            {
                var step = verification.Steps.FirstOrDefault(s => s.Id == row.StepId);
                if (step is { Status: VerificationStepStatus.Passed, ExpiresAt: not null } && step.ExpiresAt < now)
                {
                    step.Status = VerificationStepStatus.Expired;
                    step.FailureReason = "Credential expired; please re-upload a current certificate.";
                    step.CompletedAt = now;
                    revertedAny = true;
                }
            }

            if (!revertedAny)
                continue;

            // A lapsed required credential must never silently keep a nurse verified — re-gate atomically.
            VerificationAggregator.Finalize(verification, profile, now);

            // The un-verify must reach search in the same commit so an expired nurse stops surfacing.
            await searchIndex.ReindexNurseAsync(profile, verification.Status, cancellationToken);
            await unitOfWork.CommitAsync();
            revertedNurses++;

            // Side effects (each self-commits its own row): staff worklist + renewal prompt + badge eviction.
            await supportAlerts.RaiseAsync(
                SupportAlertType.VerificationExpired, "nurse_profile", nurseId.ToString(), SupportAlertSeverity.Medium,
                cancellationToken: cancellationToken);

            await notifications.DispatchAsync(
                new Notification(
                    profile.UserId,
                    "verification_expiry_prompt",
                    "A verification credential has expired",
                    "A required credential has expired. Please renew it to remain bookable."),
                cancellationToken);

            await VerificationCache.InvalidateBadgeAsync(cache, nurseId, cancellationToken);
        }

        return OperationResult<ScanExpiringResult>.SuccessResult(new ScanExpiringResult(scannedSteps, revertedNurses));
    }
}
