using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using RpgRoller.Data;
using RpgRoller.Hosting;

namespace RpgRoller.Tests;

public abstract class ApiTestBase(WebApplicationFactory<Program> factory) : IClassFixture<WebApplicationFactory<Program>>
{
    private sealed class FixedDiceRoller(IEnumerable<int> values) : IDiceRoller
    {
        public int Roll(int sides)
        {
            var next = m_Values.Count > 0 ? m_Values.Dequeue() : 1;
            return Math.Clamp(next, 1, sides);
        }

        private readonly Queue<int> m_Values = new(values);
    }

    protected WebApplicationFactory<Program> CreateFactory(params int[] rollValues)
    {
        return factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureLogging(logging =>
            {
                logging.AddFilter("Microsoft.AspNetCore", LogLevel.Warning);
                logging.AddFilter("Microsoft.EntityFrameworkCore", LogLevel.Warning);
                logging.AddFilter("Microsoft.Hosting", LogLevel.Warning);
            });
            builder.ConfigureServices(services =>
            {
                services.RemoveAll<IDiceRoller>();
                services.AddSingleton<IDiceRoller>(new FixedDiceRoller(rollValues));

                services.RemoveAll<DbContextOptions<RpgRollerDbContext>>();
                services.RemoveAll<IDbContextFactory<RpgRollerDbContext>>();
                services.RemoveAll<RpgRollerDbContext>();
                services.RemoveAll<SqliteDatabaseFile>();

                var dbPath = Path.Combine(Path.GetTempPath(), $"rpgroller-tests-{Guid.NewGuid():N}.db");
                services.AddSingleton(new SqliteDatabaseFile(dbPath));
                services.AddDbContextFactory<RpgRollerDbContext>(options => options.UseSqlite($"Data Source={dbPath}"));
            });
        });
    }

    protected static async Task<UserSummary> RegisterAsync(HttpClient client, string username, string password, string displayName)
    {
        return await PostAsync<RegisterRequest, UserSummary>(client, "/api/auth/register", new(username, password, displayName));
    }

    protected static async Task LoginAsync(HttpClient client, string username, string password)
    {
        var response = await client.PostAsJsonAsync("/api/auth/login", new LoginRequest(username, password));
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }

    protected static async Task<TResponse> PostAsync<TRequest, TResponse>(HttpClient client, string uri, TRequest payload)
    {
        var response = await client.PostAsJsonAsync(uri, payload);
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var result = await response.Content.ReadFromJsonAsync<TResponse>();
        Assert.NotNull(result);
        return result;
    }

    protected static async Task<TResponse> PutAsync<TRequest, TResponse>(HttpClient client, string uri, TRequest payload)
    {
        var response = await client.PutAsJsonAsync(uri, payload);
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var result = await response.Content.ReadFromJsonAsync<TResponse>();
        Assert.NotNull(result);
        return result;
    }

    protected static async Task<TResponse> GetAsync<TResponse>(HttpClient client, string uri)
    {
        var response = await client.GetAsync(uri);
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var result = await response.Content.ReadFromJsonAsync<TResponse>();
        Assert.NotNull(result);
        return result;
    }
}