#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.User;
using Baya.Domain.Entities.Verification;
using Mediator;

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

internal sealed class RunShahkarMatchCommandHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    IShahkarVerifier shahkarVerifier,
    ISupportAlertService supportAlerts,
    IDateTimeProvider dateTimeProvider,
    ISearchIndexMaintainer searchIndex)
    : IRequestHandler<RunShahkarMatchCommand, OperationResult<RunStepResult>>
{
    public async ValueTask<OperationResult<RunStepResult>> Handle(RunShahkarMatchCommand 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.ShahkarMatch);
        if (verification is null || step is null)
            return OperationResult<RunStepResult>.NotFoundResult("Submit your verification first — the Shahkar step is not in your checklist.");

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

        if (string.IsNullOrEmpty(user.NationalId))
            return OperationResult<RunStepResult>.FailureResult("Complete identity verification (KYC) before running the Shahkar check.");

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

        var match = await shahkarVerifier.MatchAsync(user.PhoneNumber, user.NationalId, cancellationToken);
        step.ExternalResponseJson = match.ExternalResponseJson;

        if (match.Matched)
        {
            user.ShahkarVerifiedAt = now;
            step.Status = VerificationStepStatus.Passed;
            step.FailureReason = null;
            step.CompletedAt = now;
        }
        else
        {
            step.Status = VerificationStepStatus.Failed;
            step.FailureReason = match.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();

        // Shared-SIM is a distinct, non-accusatory handled state — flag it for staff follow-up. Raised
        // after the atomic step write (RaiseAsync self-commits its own alert row).
        if (match is { Matched: false, IsSharedSim: true })
        {
            await supportAlerts.RaiseAsync(
                SupportAlertType.SharedSim, "nurse_profile", id.ToString(), SupportAlertSeverity.High,
                cancellationToken: cancellationToken);
        }

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