using Health.Domain.Entities;
using Health.Infrastructure.Data;
using Health.Infrastructure.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace Health.Tests;

/// <summary>
/// 认证流程测试
/// </summary>
public class AuthTests
{
    private AppDbContext CreateDbContext()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;
        return new AppDbContext(options);
    }

    private IConfiguration CreateConfig()
    {
        var settings = new Dictionary<string, string?>
        {
            { "JWT_SECRET", "test-secret-key-for-unit-tests-min-32-chars!!" },
            { "JWT_ISSUER", "health-manager" },
            { "JWT_AUDIENCE", "health-manager-app" }
        };
        return new ConfigurationBuilder().AddInMemoryCollection(settings).Build();
    }

    [Fact]
    public async Task SendSms_Should_Create_VerificationCode()
    {
        // Arrange
        using var db = CreateDbContext();
        var sms = new SmsService();

        // Act
        var code = sms.GenerateCode();
        db.VerificationCodes.Add(new VerificationCode
        {
            Id = Guid.NewGuid(), Phone = "13800138000", Code = code,
            ExpiresAt = DateTime.UtcNow.AddMinutes(5),
        });
        await db.SaveChangesAsync();

        // Assert
        var saved = await db.VerificationCodes.FirstOrDefaultAsync(v => v.Phone == "13800138000");
        Assert.NotNull(saved);
        Assert.Equal(code, saved!.Code);
        Assert.True(saved.ExpiresAt > DateTime.UtcNow);
    }

    [Fact]
    public async Task Login_Should_Create_User_And_Return_Token()
    {
        // Arrange
        using var db = CreateDbContext();
        var config = CreateConfig();
        var jwt = new JwtProvider(config);

        var phone = "13800138000";

        // Act
        var user = new User { Id = Guid.NewGuid(), Phone = phone, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow };
        db.Users.Add(user);
        await db.SaveChangesAsync();

        var accessToken = jwt.GenerateAccessToken(user.Id, phone);
        var refreshToken = jwt.GenerateRefreshToken();
        db.RefreshTokens.Add(new RefreshToken { Id = Guid.NewGuid(), UserId = user.Id, Token = refreshToken, ExpiresAt = DateTime.UtcNow.AddDays(30) });
        await db.SaveChangesAsync();

        // Assert
        Assert.NotNull(accessToken);
        Assert.True(accessToken.Length > 50);
        Assert.NotNull(refreshToken);
        Assert.True(refreshToken.Length > 50);

        var savedUser = await db.Users.FirstOrDefaultAsync(u => u.Phone == phone);
        Assert.NotNull(savedUser);

        var savedToken = await db.RefreshTokens.FirstOrDefaultAsync(t => t.Token == refreshToken);
        Assert.NotNull(savedToken);
    }

    [Fact]
    public async Task RefreshToken_Should_Revoke_Old_And_Issue_New()
    {
        // Arrange
        using var db = CreateDbContext();
        var config = CreateConfig();
        var jwt = new JwtProvider(config);

        var user = new User { Id = Guid.NewGuid(), Phone = "13800138000", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow };
        db.Users.Add(user);

        var oldRefresh = jwt.GenerateRefreshToken();
        db.RefreshTokens.Add(new RefreshToken { Id = Guid.NewGuid(), UserId = user.Id, Token = oldRefresh, ExpiresAt = DateTime.UtcNow.AddDays(30) });
        await db.SaveChangesAsync();

        // Act — 吊销旧 token，签发新 token
        var old = await db.RefreshTokens.FirstOrDefaultAsync(t => t.Token == oldRefresh && !t.IsRevoked);
        Assert.NotNull(old);
        old!.IsRevoked = true;

        var newToken = jwt.GenerateRefreshToken();
        db.RefreshTokens.Add(new RefreshToken { Id = Guid.NewGuid(), UserId = user.Id, Token = newToken, ExpiresAt = DateTime.UtcNow.AddDays(30) });
        await db.SaveChangesAsync();

        // Assert
        var revoked = await db.RefreshTokens.FirstOrDefaultAsync(t => t.Token == oldRefresh);
        Assert.True(revoked!.IsRevoked);

        var active = await db.RefreshTokens.FirstOrDefaultAsync(t => t.Token == newToken && !t.IsRevoked);
        Assert.NotNull(active);
    }

    [Fact]
    public async Task VerificationCode_Expired_Should_Fail_Login()
    {
        // Arrange
        using var db = CreateDbContext();
        var expiredCode = new VerificationCode
        {
            Id = Guid.NewGuid(), Phone = "13800138000", Code = "123456",
            ExpiresAt = DateTime.UtcNow.AddMinutes(-1), // 已过期
        };
        db.VerificationCodes.Add(expiredCode);
        await db.SaveChangesAsync();

        // Act
        var valid = await db.VerificationCodes
            .Where(v => v.Phone == "13800138000" && v.Code == "123456"
                && v.ExpiresAt > DateTime.UtcNow && !v.IsUsed)
            .FirstOrDefaultAsync();

        // Assert — 过期的验证码查不到
        Assert.Null(valid);
    }
}
