using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using RpgRoller.Data;

namespace RpgRoller.Tests;

internal static class ServiceTestSupport
{
    internal sealed class ServiceHarness : IDisposable
    {
        internal ServiceHarness(GameService service, SqliteDbContextFactory factory, string dbPath)
        {
            Service = service;
            m_Factory = factory;
            DbPath = dbPath;
        }

        public void Dispose()
        {
            m_Factory.Dispose();
        }

        public RpgRollerDbContext CreateDbContext()
        {
            return m_Factory.CreateDbContext();
        }

        public GameService Service { get; }
        public string DbPath { get; }
        private readonly SqliteDbContextFactory m_Factory;
    }

    internal sealed class RehashingPasswordHasher : IPasswordHasher<UserAccount>
    {
        public string HashPassword(UserAccount user, string password)
        {
            HashCalls += 1;
            return $"hash:{HashCalls}:{password}";
        }

        public PasswordVerificationResult VerifyHashedPassword(UserAccount user, string hashedPassword, string providedPassword)
        {
            return providedPassword == "Password123" ? PasswordVerificationResult.SuccessRehashNeeded : PasswordVerificationResult.Failed;
        }

        public int HashCalls { get; private set; }
    }

    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);
    }

    internal sealed class SqliteDbContextFactory(string dbPath) : IDbContextFactory<RpgRollerDbContext>, IDisposable
    {
        public RpgRollerDbContext CreateDbContext()
        {
            return new(m_Options);
        }

        public void Dispose()
        {
        }

        private readonly DbContextOptions<RpgRollerDbContext> m_Options = new DbContextOptionsBuilder<RpgRollerDbContext>().UseSqlite($"Data Source={dbPath}").Options;
    }

    internal static ServiceHarness CreateHarness(params int[] rollValues)
    {
        return CreateHarness(new PasswordHasher<UserAccount>(), rollValues);
    }

    internal static ServiceHarness CreateHarness(IPasswordHasher<UserAccount> passwordHasher, params int[] rollValues)
    {
        var dbPath = Path.Combine(Path.GetTempPath(), $"rpgroller-servicetests-{Guid.NewGuid():N}.db");
        return CreateHarnessFromPath(dbPath, passwordHasher, rollValues);
    }

    internal static ServiceHarness CreateHarnessFromPath(string dbPath, params int[] rollValues)
    {
        return CreateHarnessFromPath(dbPath, new PasswordHasher<UserAccount>(), rollValues);
    }

    internal static ServiceHarness CreateHarnessFromPath(string dbPath, IPasswordHasher<UserAccount> passwordHasher, params int[] rollValues)
    {
        var options = new DbContextOptionsBuilder<RpgRollerDbContext>().UseSqlite($"Data Source={dbPath}").Options;

        using (var db = new RpgRollerDbContext(options))
        {
            db.Database.EnsureCreated();
        }

        var factory = new SqliteDbContextFactory(dbPath);
        var service = new GameService(factory, passwordHasher, new FixedDiceRoller(rollValues));
        return new(service, factory, dbPath);
    }

    internal static T GetValue<T>(ServiceResult<T> result)
    {
        Assert.True(result.Succeeded);
        Assert.NotNull(result.Value);
        return result.Value!;
    }
}