using Theriapolis.Core.Data;
using Theriapolis.Core.Rules.Character;
using Theriapolis.Core.Rules.Stats;

namespace Theriapolis.Tools.Commands;

/// <summary>
/// Headless character builder. Useful for verifying CharacterBuilder
/// determinism, dumping a representative stat block, or scripted balance
/// sweeps in CI.
///
/// Usage:
///   dotnet run --project Theriapolis.Tools -- character-roll \
///     --seed 12345 \
///     --clade canidae --species wolf \
///     --class fangsworn --background pack_raised \
///     --name "Grev" \
///     [--data-dir ./Content/Data] \
///     [--roll]              (use 4d6-drop-lowest instead of Standard Array)
///     [--ms-override 1000]  (fix the rolling seed for reproducibility)
///     [--level N]           (Phase 7 M0 — apply N-1 level-ups deterministically;
///                            picks the first subclass at L3 and assigns ASI to
///                            CON each ASI level for predictable balance testing)
/// </summary>
public static class CharacterRoll
{
    public static int Run(string[] args)
    {
        ulong  seed       = 12345UL;
        string dataDir    = "./Content/Data";
        string cladeId    = "canidae";
        string speciesId  = "wolf";
        string classId    = "fangsworn";
        string bgId       = "pack_raised";
        string name       = "Wanderer";
        bool   roll       = false;
        ulong  msOverride = 0;
        bool   msOverrideSet = false;
        int    targetLevel = 1;

        for (int i = 0; i < args.Length; i++)
        {
            switch (args[i])
            {
                case "--seed":        seed     = ulong.Parse(args[++i]); break;
                case "--data-dir":    dataDir  = args[++i]; break;
                case "--clade":       cladeId  = args[++i]; break;
                case "--species":     speciesId= args[++i]; break;
                case "--class":       classId  = args[++i]; break;
                case "--background":  bgId     = args[++i]; break;
                case "--name":        name     = args[++i]; break;
                case "--roll":        roll     = true; break;
                case "--ms-override": msOverride = ulong.Parse(args[++i]); msOverrideSet = true; break;
                case "--level":       targetLevel = int.Parse(args[++i]); break;
            }
        }
        if (targetLevel < 1 || targetLevel > Theriapolis.Core.C.CHARACTER_LEVEL_MAX)
        {
            Console.Error.WriteLine($"--level must be in [1, {Theriapolis.Core.C.CHARACTER_LEVEL_MAX}]");
            return 1;
        }

        var loader   = new ContentLoader(dataDir);
        var content  = new ContentResolver(loader);

        if (!content.Clades.TryGetValue(cladeId, out var clade))
        { Console.Error.WriteLine($"Unknown clade: {cladeId}"); return 1; }
        if (!content.Species.TryGetValue(speciesId, out var species))
        { Console.Error.WriteLine($"Unknown species: {speciesId}"); return 1; }
        if (!content.Classes.TryGetValue(classId, out var classDef))
        { Console.Error.WriteLine($"Unknown class: {classId}"); return 1; }
        if (!content.Backgrounds.TryGetValue(bgId, out var bg))
        { Console.Error.WriteLine($"Unknown background: {bgId}"); return 1; }

        // Stats
        AbilityScores baseAbilities;
        if (roll)
        {
            ulong msArg = msOverrideSet ? msOverride : (ulong)Environment.TickCount64;
            baseAbilities = CharacterBuilder.RollAbilityScores(seed, msArg);
        }
        else
        {
            // Standard Array assigned by class priority (matches CharacterCreationScreen default).
            int[] values = (int[])AbilityScores.StandardArray.Clone();
            Array.Sort(values, (a, b) => b - a);
            baseAbilities = AssignByClassPriority(classDef, values);
        }

        var b = new CharacterBuilder
        {
            Clade         = clade,
            Species       = species,
            ClassDef      = classDef,
            Background    = bg,
            BaseAbilities = baseAbilities,
            Name          = name,
        };
        // Auto-pick first N skills
        int n = classDef.SkillsChoose;
        foreach (var raw in classDef.SkillOptions)
        {
            if (b.ChosenClassSkills.Count >= n) break;
            try { b.ChosenClassSkills.Add(SkillIdExtensions.FromJson(raw)); } catch { }
        }

        if (!b.Validate(out var error))
        {
            Console.Error.WriteLine($"Validation failed: {error}");
            return 1;
        }

        var c = b.Build(content.Items);

        // Phase 7 M0 — apply N-1 level-ups deterministically. The seed for
        // each level mirrors the Phase 6.5 contract:
        //   levelUpSeed = worldSeed ^ msOverride ^ RNG_LEVELUP ^ targetLevel
        // We default subclass to the class's first declared subclass and
        // ASIs to a +2-CON bias for predictable HP scaling — this is balance-
        // sweep mode, not playthrough mode. Players in-game pick their own.
        ulong msForLevelup = msOverrideSet ? msOverride : 0UL;
        for (int lv = 2; lv <= targetLevel; lv++)
        {
            ulong levelSeed = seed ^ msForLevelup ^ Theriapolis.Core.C.RNG_LEVELUP ^ (ulong)lv;
            var result = Theriapolis.Core.Rules.Character.LevelUpFlow.Compute(
                c, lv, levelSeed,
                takeAverage: true,
                subclasses: content.Subclasses);
            var choices = new Theriapolis.Core.Rules.Character.LevelUpChoices
            {
                TakeAverageHp = true,
                SubclassId    = result.GrantsSubclassChoice && classDef.SubclassIds.Length > 0
                                ? classDef.SubclassIds[0]
                                : null,
            };
            if (result.GrantsAsiChoice)
            {
                choices.AsiAdjustments[Theriapolis.Core.Rules.Stats.AbilityId.CON] = 2;
            }
            c.ApplyLevelUp(result, choices);
        }

        PrintCharacter(c, name);
        return 0;
    }

    private static AbilityScores AssignByClassPriority(ClassDef classDef, int[] sortedDescValues)
    {
        var primary = classDef.PrimaryAbility ?? Array.Empty<string>();
        var order = new List<string>();
        foreach (var p in primary) order.Add(p.ToUpperInvariant());
        foreach (var a in new[] { "CON", "DEX", "STR", "WIS", "INT", "CHA" })
            if (!order.Contains(a)) order.Add(a);

        var assigned = new Dictionary<string, int>();
        for (int i = 0; i < 6; i++) assigned[order[i]] = sortedDescValues[i];

        return new AbilityScores(
            assigned["STR"], assigned["DEX"], assigned["CON"],
            assigned["INT"], assigned["WIS"], assigned["CHA"]);
    }

    private static void PrintCharacter(Character c, string name)
    {
        Console.WriteLine();
        Console.WriteLine($"=== {name} ===");
        Console.WriteLine($"Clade:       {c.Clade.Name} ({c.Clade.Kind})");
        Console.WriteLine($"Species:     {c.Species.Name}  size: {c.Species.Size}  speed: {c.Species.BaseSpeedFt} ft.");
        Console.WriteLine($"Class:       {c.ClassDef.Name}  hit die: d{c.ClassDef.HitDie}  level: {c.Level}");
        Console.WriteLine($"Background:  {c.Background.Name}");
        Console.WriteLine();
        Console.WriteLine("Ability scores (post clade + species mods):");
        Console.WriteLine($"  STR {c.Abilities.STR}  DEX {c.Abilities.DEX}  CON {c.Abilities.CON}");
        Console.WriteLine($"  INT {c.Abilities.INT}  WIS {c.Abilities.WIS}  CHA {c.Abilities.CHA}");
        Console.WriteLine();
        Console.WriteLine($"HP:        {c.CurrentHp}/{c.MaxHp}");
        Console.WriteLine($"AC:        {Theriapolis.Core.Rules.Stats.DerivedStats.ArmorClass(c)}");
        Console.WriteLine($"Speed:     {Theriapolis.Core.Rules.Stats.DerivedStats.SpeedFt(c)} ft.");
        Console.WriteLine($"Carry:     {c.Inventory.TotalWeightLb:F1} / {Theriapolis.Core.Rules.Stats.DerivedStats.CarryCapacityLb(c):F1} lb  ({Theriapolis.Core.Rules.Stats.DerivedStats.Encumbrance(c)})");
        Console.WriteLine($"Initiative:{(Theriapolis.Core.Rules.Stats.DerivedStats.Initiative(c) >= 0 ? "+" : "")}{Theriapolis.Core.Rules.Stats.DerivedStats.Initiative(c)}");
        Console.WriteLine($"Prof:      +{c.ProficiencyBonus}");
        Console.WriteLine();
        Console.WriteLine($"Skill proficiencies ({c.SkillProficiencies.Count}):");
        foreach (var s in c.SkillProficiencies.OrderBy(s => s.ToString()))
            Console.WriteLine($"  - {s}  ({s.Ability()})");
        Console.WriteLine();
        Console.WriteLine($"Inventory ({c.Inventory.Items.Count} stacks, {c.Inventory.TotalWeightLb:F1} lb):");
        foreach (var i in c.Inventory.Items)
            Console.WriteLine($"  - {i.Def.Name}{(i.Qty > 1 ? $" ×{i.Qty}" : "")}{(i.EquippedAt is { } slot ? $"  [{slot}]" : "")}");
    }
}
