using RolemasterDb.App.Domain;

namespace RolemasterDb.App.Data;

public static class RolemasterSeedData
{
    private static readonly IReadOnlyDictionary<string, (int? FumbleMinRoll, int? FumbleMaxRoll)> AttackTableMetadata =
        new Dictionary<string, (int? FumbleMinRoll, int? FumbleMaxRoll)>(StringComparer.OrdinalIgnoreCase)
        {
            ["broadsword"] = (1, 2),
            ["short_bow"] = (1, 3)
        };

    public static void SeedAttackStarterData(RolemasterDbContext dbContext)
    {
        List<ArmorType> armorTypes;

        if (dbContext.ArmorTypes.Any())
        {
            armorTypes = dbContext.ArmorTypes.ToList();
        }
        else
        {
            armorTypes = CreateArmorTypes();
            dbContext.ArmorTypes.AddRange(armorTypes);
        }

        if (dbContext.AttackTables.Any())
        {
            return;
        }

        var armorLookup = armorTypes.ToDictionary(item => item.Code, StringComparer.OrdinalIgnoreCase);
        var attackTables = CreateAttackTables(armorLookup);
        dbContext.AttackTables.AddRange(attackTables);
    }

    public static void BackfillAttackTableMetadata(RolemasterDbContext dbContext)
    {
        foreach (var table in dbContext.AttackTables)
        {
            if (!AttackTableMetadata.TryGetValue(table.Slug, out var metadata))
            {
                continue;
            }

            if (table.FumbleMinRoll is null)
            {
                table.FumbleMinRoll = metadata.FumbleMinRoll;
            }

            if (table.FumbleMaxRoll is null)
            {
                table.FumbleMaxRoll = metadata.FumbleMaxRoll;
            }
        }
    }

    private static List<ArmorType> CreateArmorTypes() =>
    [
        new ArmorType { Code = "AT1", Label = "AT1 - Robes", SortOrder = 1 },
        new ArmorType { Code = "AT5", Label = "AT5 - Rigid Leather", SortOrder = 5 },
        new ArmorType { Code = "AT10", Label = "AT10 - Chain", SortOrder = 10 },
        new ArmorType { Code = "AT15", Label = "AT15 - Plate", SortOrder = 15 },
        new ArmorType { Code = "AT20", Label = "AT20 - Full Plate", SortOrder = 20 }
    ];

    private static List<AttackTable> CreateAttackTables(IReadOnlyDictionary<string, ArmorType> armorLookup)
    {
        var broadsword = new AttackTable
        {
            Slug = "broadsword",
            DisplayName = "Broadsword",
            AttackKind = "melee",
            FumbleMinRoll = 1,
            FumbleMaxRoll = 2,
            Notes = "Starter subset with slash criticals to prove the end-to-end lookup flow."
        };

        var shortBow = new AttackTable
        {
            Slug = "short_bow",
            DisplayName = "Short Bow",
            AttackKind = "missile",
            FumbleMinRoll = 1,
            FumbleMaxRoll = 3,
            Notes = "Starter subset with puncture criticals."
        };

        AddAttackData(
            broadsword,
            armorLookup,
            "slash",
            new (string Label, int MinRoll, int? MaxRoll)[]
            {
                ("01-25", 1, 25),
                ("26-50", 26, 50),
                ("51-75", 51, 75),
                ("76-100", 76, 100),
                ("101-125", 101, 125),
                ("126+", 126, null)
            },
            new Dictionary<string, (int Hits, string? Severity)[]>
            {
                ["AT1"] = [(0, null), (4, "A"), (10, "B"), (18, "C"), (26, "D"), (34, "E")],
                ["AT5"] = [(0, null), (2, null), (8, "A"), (14, "B"), (22, "C"), (30, "D")],
                ["AT10"] = [(0, null), (0, null), (4, null), (10, "A"), (18, "B"), (26, "C")],
                ["AT15"] = [(0, null), (0, null), (2, null), (6, null), (12, "A"), (20, "B")],
                ["AT20"] = [(0, null), (0, null), (0, null), (4, null), (8, null), (16, "A")]
            });

        AddAttackData(
            shortBow,
            armorLookup,
            "puncture",
            new (string Label, int MinRoll, int? MaxRoll)[]
            {
                ("01-25", 1, 25),
                ("26-50", 26, 50),
                ("51-75", 51, 75),
                ("76-100", 76, 100),
                ("101-125", 101, 125),
                ("126+", 126, null)
            },
            new Dictionary<string, (int Hits, string? Severity)[]>
            {
                ["AT1"] = [(0, null), (3, "A"), (8, "B"), (12, "C"), (18, "D"), (24, "E")],
                ["AT5"] = [(0, null), (1, null), (5, "A"), (9, "B"), (14, "C"), (20, "D")],
                ["AT10"] = [(0, null), (0, null), (2, null), (6, "A"), (10, "B"), (15, "C")],
                ["AT15"] = [(0, null), (0, null), (1, null), (4, "A"), (8, "B"), (12, "C")],
                ["AT20"] = [(0, null), (0, null), (0, null), (2, null), (5, "A"), (9, "B")]
            });

        return [broadsword, shortBow];
    }

    private static void AddAttackData(
        AttackTable table,
        IReadOnlyDictionary<string, ArmorType> armorLookup,
        string criticalType,
        IReadOnlyList<(string Label, int MinRoll, int? MaxRoll)> rollBands,
        IReadOnlyDictionary<string, (int Hits, string? Severity)[]> matrix)
    {
        table.RollBands = rollBands
            .Select((band, index) => new AttackRollBand
            {
                Label = band.Label,
                MinRoll = band.MinRoll,
                MaxRoll = band.MaxRoll,
                SortOrder = index + 1
            })
            .ToList();

        var rollBandLookup = table.RollBands.ToDictionary(item => item.SortOrder);

        foreach (var (armorCode, results) in matrix)
        {
            for (var index = 0; index < results.Length; index++)
            {
                var result = results[index];
                table.Results.Add(new AttackResult
                {
                    ArmorType = armorLookup[armorCode],
                    AttackRollBand = rollBandLookup[index + 1],
                    Hits = result.Hits,
                    CriticalType = result.Severity is null ? null : criticalType,
                    CriticalSeverity = result.Severity,
                    RawNotation = BuildAttackNotation(result.Hits, result.Severity, criticalType),
                    Notes = result.Severity is null
                        ? "No critical triggered from the seeded starter data."
                        : "Critical can be resolved automatically if a critical roll is supplied."
                });
            }
        }
    }

    private static string BuildAttackNotation(int hits, string? severity, string criticalType)
    {
        return severity is null
            ? $"{hits} hits"
            : $"{hits} hits, {severity.ToUpperInvariant()} {ToTitleCase(criticalType)} critical";
    }

    private static string ToTitleCase(string value) =>
        string.Join(' ', value.Split('_', StringSplitOptions.RemoveEmptyEntries)
            .Select(segment => char.ToUpperInvariant(segment[0]) + segment[1..]));
}
