#nullable enable
using System.Text.Json;
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.RunBankAccountVerification;

internal sealed class RunBankAccountVerificationCommandHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    IBankAccountOwnershipVerifier ownershipVerifier,
    IDateTimeProvider dateTimeProvider,
    ISearchIndexMaintainer searchIndex)
    : IRequestHandler<RunBankAccountVerificationCommand, OperationResult<RunStepResult>>
{
    public async ValueTask<OperationResult<RunStepResult>> Handle(RunBankAccountVerificationCommand 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 context = await unitOfWork.NurseProfileRepository.GetIdentityContextByUserIdAsync(userId, cancellationToken);
        if (context is null)
            return OperationResult<RunStepResult>.NotFoundResult("No nurse profile exists yet.");

        if (string.IsNullOrEmpty(context.NationalId))
            return OperationResult<RunStepResult>.FailureResult("Complete identity verification (KYC) before running the bank-account check.");

        var nurseId = context.NurseProfileId;

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

        var account = await unitOfWork.NurseBankAccountRepository.GetPrimaryAsync(nurseId, cancellationToken);
        if (account is null)
            return OperationResult<RunStepResult>.FailureResult("Add a primary payout account before running the bank-account check.");

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

        var inquiry = await ownershipVerifier.VerifyOwnershipAsync(account.Iban, context.NationalId, cancellationToken);
        account.ApplyOwnershipInquiry(inquiry.MatchedNationalId, inquiry.AccountHolderFromBank, inquiry.VendorRef);

        step.ExternalResponseJson = JsonSerializer.Serialize(new
        {
            provider = "mock_sheba",
            matched = inquiry.MatchedNationalId,
            vendor_ref = inquiry.VendorRef
        });

        if (inquiry.MatchedNationalId)
        {
            step.Status = VerificationStepStatus.Passed;
            step.FailureReason = null;
            step.CompletedAt = now;
        }
        else
        {
            step.Status = VerificationStepStatus.Failed;
            step.FailureReason = "The payout account holder's national ID does not match your verified national ID.";
            step.CompletedAt = now;
        }

        var profile = await unitOfWork.NurseProfileRepository.GetTrackedByIdAsync(nurseId, 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));
    }
}
