using System.Net;
using System.Net.Http.Json;
using Baya.Application.Contracts.Common;
using Baya.Domain.Entities.User;
using Baya.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace Baya.Test.Api;

public class RefreshAndLogoutTests(BayaApiFactory factory) : IClassFixture<BayaApiFactory>
{
    [Fact]
    public async Task Refresh_RotatesSession_AndReplayRevokesEverything()
    {
        var client = factory.CreateClient();
        const string phone = "09120000030";
        var tokens = await AuthTestClient.LoginAsync(factory, client, phone);
        var originalRefreshToken = tokens.GetProperty("refreshToken").GetString()!;

        var rotated = await client.PostAsJsonAsync("/api/v1/auth/refresh", new { refreshToken = originalRefreshToken });
        Assert.Equal(HttpStatusCode.OK, rotated.StatusCode);
        var rotatedData = await AuthTestClient.ReadDataAsync(rotated);
        Assert.NotEqual(originalRefreshToken, rotatedData.GetProperty("refreshToken").GetString());

        // Replaying the rotated-out token is stolen-token reuse: 401 and logout-everywhere.
        var replay = await client.PostAsJsonAsync("/api/v1/auth/refresh", new { refreshToken = originalRefreshToken });
        Assert.Equal(HttpStatusCode.Unauthorized, replay.StatusCode);

        using var scope = factory.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
        var phoneHash = scope.ServiceProvider.GetRequiredService<IFieldEncryptor>().Hash(phone);
        var user = await db.Set<User>().SingleAsync(u => u.PhoneHash == phoneHash);
        var sessions = await db.Set<UserSession>().Where(s => s.UserId == user.Id).ToListAsync();

        Assert.NotEmpty(sessions);
        Assert.All(sessions, s => Assert.True(s.IsRevoked));
    }

    [Fact]
    public async Task Logout_RevokesSession_AndKillsAccessToken()
    {
        var client = factory.CreateClient();
        const string phone = "09120000031";
        var tokens = await AuthTestClient.LoginAsync(factory, client, phone);
        AuthTestClient.UseBearer(client, tokens.GetProperty("accessToken").GetString()!);

        var logout = await client.PostAsJsonAsync("/api/v1/auth/logout", new { });
        Assert.Equal(HttpStatusCode.OK, logout.StatusCode);

        // Security-stamp rotation makes the still-unexpired access token fail server-side.
        var me = await client.GetAsync("/api/v1/me");
        Assert.Equal(HttpStatusCode.Unauthorized, me.StatusCode);

        using var scope = factory.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
        var phoneHash = scope.ServiceProvider.GetRequiredService<IFieldEncryptor>().Hash(phone);
        var user = await db.Set<User>().SingleAsync(u => u.PhoneHash == phoneHash);
        var sessions = await db.Set<UserSession>().Where(s => s.UserId == user.Id).ToListAsync();

        Assert.NotEmpty(sessions);
        Assert.All(sessions, s => Assert.True(s.IsRevoked));
    }
}