using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Configuration;
using Baya.Application.Contracts.Identity;
using Baya.Application.Features.Identity;
using Baya.Application.Features.Identity.Commands.RequestOtp;
using Baya.Domain.Entities.User;
using Microsoft.AspNetCore.Identity;
using NSubstitute;
using NSubstitute.ReturnsExtensions;

namespace Baya.Test.Foundation.Identity;

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

    private readonly IAppUserManager _userManager = Substitute.For<IAppUserManager>();
    private readonly ISmsSender _smsSender = Substitute.For<ISmsSender>();
    private readonly IPlatformConfig _platformConfig = Substitute.For<IPlatformConfig>();
    private readonly ICacheService _cache = Substitute.For<ICacheService>();
    private readonly IFieldEncryptor _fieldEncryptor = Substitute.For<IFieldEncryptor>();
    private readonly IDateTimeProvider _clock = Substitute.For<IDateTimeProvider>();

    private RequestOtpCommandHandler CreateHandler()
    {
        _clock.UtcNow.Returns(Now);
        _fieldEncryptor.Hash(Arg.Any<string>()).Returns("PHONE_HASH");
        _platformConfig.GetConfig<int>(IdentityDefaults.OtpResendSecondsKey, Arg.Any<CancellationToken>())
            .Returns(120);

        return new RequestOtpCommandHandler(_userManager, _smsSender, _platformConfig, _cache, _fieldEncryptor, _clock);
    }

    [Fact]
    public async Task Handle_NewPhone_CreatesInactiveUserAndSendsOtp()
    {
        // Arrange
        _userManager.GetUserByPhoneNumber("09123456789").ReturnsNull();
        _userManager.CreateUser(Arg.Any<User>()).Returns(IdentityResult.Success);
        _userManager.GeneratePhoneNumberConfirmationToken(Arg.Any<User>(), "09123456789").Returns("123456");
        var handler = CreateHandler();

        // Act
        var result = await handler.Handle(new RequestOtpCommand("09123456789"), CancellationToken.None);

        // Assert
        Assert.True(result.IsSuccess);
        Assert.True(result.Result.OtpSent);
        Assert.Equal(120, result.Result.ResendAvailableInSeconds);
        await _userManager.Received(1).CreateUser(Arg.Is<User>(u => !u.IsActive && u.PhoneNumber == "09123456789"));
        await _smsSender.Received(1).SendOtpAsync("09123456789", "123456", Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_ResendWindowOpen_DoesNotSendAndReportsRemainingSeconds()
    {
        // Arrange
        _cache.GetAsync<DateTimeOffset>(Arg.Any<string>(), Arg.Any<CancellationToken>())
            .Returns(Now.AddSeconds(60));
        var handler = CreateHandler();

        // Act
        var result = await handler.Handle(new RequestOtpCommand("09123456789"), CancellationToken.None);

        // Assert
        Assert.True(result.IsSuccess);
        Assert.False(result.Result.OtpSent);
        Assert.Equal(60, result.Result.ResendAvailableInSeconds);
        await _smsSender.DidNotReceive().SendOtpAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_ExistingConfirmedUser_UsesPasswordlessOtpAndSameShape()
    {
        // Arrange
        var user = new User { PhoneNumber = "09123456789", PhoneNumberConfirmed = true };
        _userManager.GetUserByPhoneNumber("09123456789").Returns(user);
        _userManager.GenerateOtpCode(user).Returns("654321");
        var handler = CreateHandler();

        // Act
        var result = await handler.Handle(new RequestOtpCommand("+989123456789"), CancellationToken.None);

        // Assert — normalized phone, no enumeration (same shape as the new-user path).
        Assert.True(result.IsSuccess);
        Assert.True(result.Result.OtpSent);
        await _smsSender.Received(1).SendOtpAsync("09123456789", "654321", Arg.Any<CancellationToken>());
        await _userManager.DidNotReceive().CreateUser(Arg.Any<User>());
    }
}