using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;

using RolemasterDb.App.Data;
using RolemasterDb.App.Features;

namespace RolemasterDb.ImportTool.Tests;

public sealed class LookupRollingTests
{
    [Fact]
    public void Attack_rolls_open_end_on_high_results()
    {
        var result = RolemasterRoller.RollAttack(new SequenceRandom(96, 100, 43));

        Assert.Equal(239, result.Total);
        Assert.Equal([96, 100, 43], result.Rolls);
        Assert.True(result.IsOpenEnded);
    }

    [Fact]
    public void Attack_resolution_applies_ob_and_db_to_raw_roll()
    {
        var resolution = AttackResolutionCalculator.Resolve(120, 35, 15);

        Assert.Equal(120, resolution.RawRoll);
        Assert.Equal(35, resolution.OffensiveBonus);
        Assert.Equal(15, resolution.DefensiveBonus);
        Assert.Equal(140, resolution.EffectiveTotal);
    }

    [Fact]
    public void Fumble_detection_uses_raw_attack_roll()
    {
        var table = new AttackTableReference("club", "Club", "melee", 1, 4);
        var resolution = AttackResolutionCalculator.Resolve(2, 3, 1);

        Assert.True(AttackResolutionCalculator.IsFumble(resolution, table));
    }

    [Fact]
    public void Offensive_bonus_does_not_prevent_a_weapon_fumble()
    {
        var table = new AttackTableReference("club", "Club", "melee", 1, 4);
        var resolution = AttackResolutionCalculator.Resolve(2, 5, 0);

        Assert.True(AttackResolutionCalculator.IsFumble(resolution, table));
    }

    [Fact]
    public void Standard_critical_rolls_are_capped_at_one_hundred()
    {
        var table = CreateCriticalTableReference(
            "slash",
            "Slash Critical Strike Table",
            [new CriticalRollBandReference("01-100", 1, 100, 1)]);

        var result = RolemasterRoller.RollCritical(new SequenceRandom(99), table);

        Assert.Equal(99, result.Total);
        Assert.Equal([99], result.Rolls);
        Assert.False(result.IsOpenEnded);
        Assert.False(RolemasterRoller.AllowsOpenEndedCritical(table));
    }

    [Fact]
    public void Extended_critical_rolls_use_open_ended_high_rules()
    {
        var table = CreateCriticalTableReference(
            "large_creature",
            "Large Creature Critical Strike Table",
            [new CriticalRollBandReference("101+", 101, null, 1)]);

        var result = RolemasterRoller.RollCritical(new SequenceRandom(97, 45), table);

        Assert.Equal(142, result.Total);
        Assert.Equal([97, 45], result.Rolls);
        Assert.True(result.IsOpenEnded);
        Assert.True(RolemasterRoller.AllowsOpenEndedCritical(table));
    }

    [Fact]
    public async Task Schema_upgrader_adds_attack_table_fumble_columns()
    {
        var databasePath = Path.Combine(Path.GetTempPath(), $"rolemaster-schema-{Guid.NewGuid():N}.db");
        await using (var connection = new SqliteConnection($"Data Source={databasePath}"))
        {
            await connection.OpenAsync();

            await using var command = connection.CreateCommand();
            command.CommandText =
                """
                CREATE TABLE "AttackTables" (
                    "Id" INTEGER NOT NULL CONSTRAINT "PK_AttackTables" PRIMARY KEY AUTOINCREMENT,
                    "Slug" TEXT NOT NULL,
                    "DisplayName" TEXT NOT NULL,
                    "AttackKind" TEXT NOT NULL,
                    "Notes" TEXT NULL
                );
                """;
            await command.ExecuteNonQueryAsync();
        }

        await using (var dbContext = CreateDbContext(databasePath))
        {
            await RolemasterDbSchemaUpgrader.EnsureLatestAsync(dbContext);
        }

        await using var verifyConnection = new SqliteConnection($"Data Source={databasePath}");
        await verifyConnection.OpenAsync();

        var columns = new List<string>();
        await using (var command = verifyConnection.CreateCommand())
        {
            command.CommandText = "PRAGMA table_info(\"AttackTables\");";
            await using var reader = await command.ExecuteReaderAsync();
            while (await reader.ReadAsync())
            {
                columns.Add(reader["name"]!.ToString()!);
            }
        }

        Assert.Contains("FumbleMinRoll", columns);
        Assert.Contains("FumbleMaxRoll", columns);
    }

    [Fact]
    public async Task Reference_data_includes_attack_table_fumble_ranges()
    {
        var databasePath = Path.Combine(Path.GetTempPath(), $"rolemaster-reference-{Guid.NewGuid():N}.db");

        await using (var dbContext = CreateDbContext(databasePath))
        {
            await dbContext.Database.EnsureCreatedAsync();
            await RolemasterDbSchemaUpgrader.EnsureLatestAsync(dbContext);
            RolemasterSeedData.SeedAttackStarterData(dbContext);
            await dbContext.SaveChangesAsync();
        }

        var lookupService = new LookupService(CreateDbContextFactory(databasePath));
        var referenceData = await lookupService.GetReferenceDataAsync();

        var broadsword = Assert.Single(referenceData.AttackTables, item => item.Key == "broadsword");
        Assert.Equal(1, broadsword.FumbleMinRoll);
        Assert.Equal(2, broadsword.FumbleMaxRoll);
    }

    private static CriticalTableReference CreateCriticalTableReference(
        string key,
        string label,
        IReadOnlyList<CriticalRollBandReference> rollBands) =>
        new(
            key,
            label,
            "standard",
            $"{label}.pdf",
            null,
            0,
            0,
            0,
            [],
            [],
            rollBands);

    private static RolemasterDbContext CreateDbContext(string databasePath)
    {
        var options = new DbContextOptionsBuilder<RolemasterDbContext>()
            .UseSqlite($"Data Source={databasePath}")
            .Options;

        return new RolemasterDbContext(options);
    }

    private static IDbContextFactory<RolemasterDbContext> CreateDbContextFactory(string databasePath)
    {
        var options = new DbContextOptionsBuilder<RolemasterDbContext>()
            .UseSqlite($"Data Source={databasePath}")
            .Options;

        return new TestRolemasterDbContextFactory(options);
    }
}
