using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Contracts.Search;
using Baya.Application.Contracts.SupportAlerts;
using Baya.Application.Features.Verification.Commands.RunBankAccountVerification;
using Baya.Application.Features.Verification.Commands.RunIdentityKyc;
using Baya.Application.Features.Verification.Commands.RunShahkarMatch;
using Baya.Application.Models.Identity;
using Baya.Domain.Entities.Identity;
using Baya.Domain.Entities.SupportAlerts;
using Baya.Domain.Entities.User;
using Baya.Domain.Entities.Verification;
using NSubstitute;
using static Baya.Test.Foundation.Verification.VerificationTestSupport;

namespace Baya.Test.Foundation.Verification;

public class RunStepHandlersTests
{
    private static readonly DateTimeOffset Now = new(2026, 7, 1, 12, 0, 0, TimeSpan.Zero);

    private readonly ICurrentUser _currentUser = Substitute.For<ICurrentUser>();
    private readonly IUnitOfWork _unitOfWork = Substitute.For<IUnitOfWork>();
    private readonly IVerificationRepository _verif = Substitute.For<IVerificationRepository>();
    private readonly INurseProfileRepository _nurses = Substitute.For<INurseProfileRepository>();
    private readonly INurseBankAccountRepository _accounts = Substitute.For<INurseBankAccountRepository>();
    private readonly IDateTimeProvider _clock = Substitute.For<IDateTimeProvider>();
    private readonly ISearchIndexMaintainer _searchIndex = Substitute.For<ISearchIndexMaintainer>();

    public RunStepHandlersTests()
    {
        _currentUser.UserId.Returns(7);
        _currentUser.Roles.Returns([RoleNames.Nurse]);
        _unitOfWork.VerificationRepository.Returns(_verif);
        _unitOfWork.NurseProfileRepository.Returns(_nurses);
        _unitOfWork.NurseBankAccountRepository.Returns(_accounts);
        _clock.UtcNow.Returns(Now);
        _nurses.GetProfileIdByUserIdAsync(7, Arg.Any<CancellationToken>()).Returns(42L);
        _nurses.GetTrackedByIdAsync(42, Arg.Any<CancellationToken>()).Returns(new NurseProfile());
    }

    private static NurseVerification VerificationWith(VerificationStep step)
    {
        var verification = new NurseVerification { NurseId = 42, Status = VerificationStatus.Pending };
        verification.Steps.Add(step);
        return verification;
    }

    [Fact]
    public async Task RunIdentityKyc_Pass_PopulatesNationalIdAndPassesStep()
    {
        var step = Step(1, StepType(1, VerificationStepTypeCodes.IdentityKyc, automated: true), VerificationStepStatus.Pending);
        _verif.GetTrackedByNurseIdAsync(42, Arg.Any<CancellationToken>()).Returns(VerificationWith(step));
        var user = new User();
        _verif.GetTrackedUserAsync(7, Arg.Any<CancellationToken>()).Returns(user);
        var identityKyc = Substitute.For<IIdentityKycProvider>();
        identityKyc.VerifyAsync("0012345678", null, Arg.Any<CancellationToken>())
            .Returns(new IdentityKycResult(true, "Verified Nurse", "ref", "{}", null));
        var handler = new RunIdentityKycCommandHandler(_currentUser, _unitOfWork, identityKyc, _clock, _searchIndex);

        var result = await handler.Handle(new RunIdentityKycCommand("0012345678", null), CancellationToken.None);

        Assert.True(result.IsSuccess);
        Assert.Equal(VerificationStepStatus.Passed, step.Status);
        Assert.Equal("0012345678", user.NationalId);
        Assert.Equal(Now, user.NationalIdVerifiedAt);
        await _unitOfWork.Received(1).CommitAsync();
    }

    [Fact]
    public async Task RunIdentityKyc_Fail_MarksStepFailedAndLeavesNationalIdNull()
    {
        var step = Step(1, StepType(1, VerificationStepTypeCodes.IdentityKyc, automated: true), VerificationStepStatus.Pending);
        _verif.GetTrackedByNurseIdAsync(42, Arg.Any<CancellationToken>()).Returns(VerificationWith(step));
        var user = new User();
        _verif.GetTrackedUserAsync(7, Arg.Any<CancellationToken>()).Returns(user);
        var identityKyc = Substitute.For<IIdentityKycProvider>();
        identityKyc.VerifyAsync("0000000000", null, Arg.Any<CancellationToken>())
            .Returns(new IdentityKycResult(false, null, "ref", "{}", "could not verify"));
        var handler = new RunIdentityKycCommandHandler(_currentUser, _unitOfWork, identityKyc, _clock, _searchIndex);

        var result = await handler.Handle(new RunIdentityKycCommand("0000000000", null), CancellationToken.None);

        Assert.True(result.IsSuccess);
        Assert.Equal(VerificationStepStatus.Failed, step.Status);
        Assert.Equal("could not verify", step.FailureReason);
        Assert.Null(user.NationalId);
    }

    [Fact]
    public async Task RunShahkar_SharedSim_FailsStepAndRaisesAlert()
    {
        var step = Step(1, StepType(1, VerificationStepTypeCodes.ShahkarMatch, automated: true), VerificationStepStatus.Pending);
        _verif.GetTrackedByNurseIdAsync(42, Arg.Any<CancellationToken>()).Returns(VerificationWith(step));
        var user = new User { PhoneNumber = "09120000000", NationalId = "0012345678" };
        _verif.GetTrackedUserAsync(7, Arg.Any<CancellationToken>()).Returns(user);
        var shahkar = Substitute.For<IShahkarVerifier>();
        shahkar.MatchAsync("09120000000", "0012345678", Arg.Any<CancellationToken>())
            .Returns(new ShahkarMatchResult(false, true, "ref", "{}", "shared sim"));
        var alerts = Substitute.For<ISupportAlertService>();
        var handler = new RunShahkarMatchCommandHandler(_currentUser, _unitOfWork, shahkar, alerts, _clock, _searchIndex);

        var result = await handler.Handle(new RunShahkarMatchCommand(), CancellationToken.None);

        Assert.True(result.IsSuccess);
        Assert.Equal(VerificationStepStatus.Failed, step.Status);
        await alerts.Received(1).RaiseAsync(
            SupportAlertType.SharedSim, "nurse_profile", "42", SupportAlertSeverity.High,
            Arg.Any<long?>(), Arg.Any<long?>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task RunShahkar_BeforeIdentityKyc_IsRejected()
    {
        var step = Step(1, StepType(1, VerificationStepTypeCodes.ShahkarMatch, automated: true), VerificationStepStatus.Pending);
        _verif.GetTrackedByNurseIdAsync(42, Arg.Any<CancellationToken>()).Returns(VerificationWith(step));
        _verif.GetTrackedUserAsync(7, Arg.Any<CancellationToken>()).Returns(new User { PhoneNumber = "09121112233" });
        var shahkar = Substitute.For<IShahkarVerifier>();
        var alerts = Substitute.For<ISupportAlertService>();
        var handler = new RunShahkarMatchCommandHandler(_currentUser, _unitOfWork, shahkar, alerts, _clock, _searchIndex);

        var result = await handler.Handle(new RunShahkarMatchCommand(), CancellationToken.None);

        Assert.False(result.IsSuccess);
        await shahkar.DidNotReceive().MatchAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task RunBankAccount_Mismatch_FailsStepAndRecordsMismatch()
    {
        _nurses.GetIdentityContextByUserIdAsync(7, Arg.Any<CancellationToken>())
            .Returns(new NurseIdentityContext(42, "0012345678"));
        var step = Step(1, StepType(1, VerificationStepTypeCodes.BankAccountVerification, automated: true), VerificationStepStatus.Pending);
        _verif.GetTrackedByNurseIdAsync(42, Arg.Any<CancellationToken>()).Returns(VerificationWith(step));
        var account = new NurseBankAccount { NurseId = 42, Iban = "IR000000000000000000000000" };
        _accounts.GetPrimaryAsync(42, Arg.Any<CancellationToken>()).Returns(account);
        var verifier = Substitute.For<IBankAccountOwnershipVerifier>();
        verifier.VerifyOwnershipAsync(account.Iban, "0012345678", Arg.Any<CancellationToken>())
            .Returns(new OwnershipInquiryResult(false, "Someone Else", "ref"));
        var handler = new RunBankAccountVerificationCommandHandler(_currentUser, _unitOfWork, verifier, _clock, _searchIndex);

        var result = await handler.Handle(new RunBankAccountVerificationCommand(), CancellationToken.None);

        Assert.True(result.IsSuccess);
        Assert.Equal(VerificationStepStatus.Failed, step.Status);
        Assert.False(account.MatchedNationalId);
    }
}
