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

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

internal sealed class RunIdentityKycCommandHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    IIdentityKycProvider identityKyc,
    IDateTimeProvider dateTimeProvider,
    ISearchIndexMaintainer searchIndex)
    : IRequestHandler<RunIdentityKycCommand, OperationResult<RunStepResult>>
{
    public async ValueTask<OperationResult<RunStepResult>> Handle(RunIdentityKycCommand request, CancellationToken cancellationToken)
    {
        if (currentUser.UserId is not { } userId)
            return OperationResult<RunStepResult>.UnauthorizedResult("Not authenticated.");

        if (currentUser.Roles?.Contains(RoleNames.Nurse) != true)
            return OperationResult<RunStepResult>.ForbiddenResult("Only a nurse can run verification.");

        var repo = unitOfWork.VerificationRepository;

        var nurseId = await unitOfWork.NurseProfileRepository.GetProfileIdByUserIdAsync(userId, cancellationToken);
        if (nurseId is not { } id)
            return OperationResult<RunStepResult>.NotFoundResult("No nurse profile exists yet.");

        var verification = await repo.GetTrackedByNurseIdAsync(id, cancellationToken);
        var step = verification?.Steps.FirstOrDefault(s => s.StepType.Code == VerificationStepTypeCodes.IdentityKyc);
        if (verification is null || step is null)
            return OperationResult<RunStepResult>.NotFoundResult("Submit your verification first — the identity step is not in your checklist.");

        var now = dateTimeProvider.UtcNow;
        step.StartedAt ??= now;

        var kyc = await identityKyc.VerifyAsync(request.NationalId, request.LivenessPayload, cancellationToken);
        step.ExternalResponseJson = kyc.ExternalResponseJson;

        if (kyc.Passed)
        {
            var user = await repo.GetTrackedUserAsync(userId, cancellationToken);
            if (user is null)
                return OperationResult<RunStepResult>.NotFoundResult("User not found.");

            // national_id is populated only after this step passes — every downstream comparison uses it.
            user.NationalId = request.NationalId;
            user.NationalIdVerifiedAt = now;

            step.Status = VerificationStepStatus.Passed;
            step.FailureReason = null;
            step.CompletedAt = now;
        }
        else
        {
            step.Status = VerificationStepStatus.Failed;
            step.FailureReason = kyc.FailureReason;
            step.CompletedAt = now;
        }

        var profile = await unitOfWork.NurseProfileRepository.GetTrackedByIdAsync(id, cancellationToken);
        if (profile is null)
            return OperationResult<RunStepResult>.NotFoundResult("Nurse profile not found.");

        VerificationAggregator.Finalize(verification, profile, now);

        // An automated pass can flip is_verified — keep the search projection in step within this commit.
        await searchIndex.ReindexNurseAsync(profile, verification.Status, cancellationToken);
        await unitOfWork.CommitAsync();

        return OperationResult<RunStepResult>.SuccessResult(new RunStepResult(step.Id, step.Status.ToCode(), step.FailureReason));
    }
}
