using Theriapolis.Core;
using Theriapolis.Core.Data;
using Theriapolis.Core.Items;
using Theriapolis.Core.Persistence;
using Theriapolis.Core.Rules.Character;
using Theriapolis.Core.Rules.Stats;
using Xunit;

namespace Theriapolis.Tests.Persistence;

public sealed class Phase5SaveRoundTripTests
{
    private readonly ContentResolver _content = new(new ContentLoader(TestHelpers.DataDirectory));

    [Fact]
    public void Character_RoundTripsThroughCharacterCodec()
    {
        var c = MakeBasicCharacter();
        // Add and equip an item so the codec hits the inventory + equip-slot path.
        var sword = c.Inventory.Add(_content.Items["rend_sword"]);
        c.Inventory.TryEquip(sword, EquipSlot.MainHand, out _);
        c.Conditions.Add(Condition.Frightened);
        c.ExhaustionLevel = 2;
        c.CurrentHp = c.MaxHp - 3;
        c.Xp = 142;

        var snap = CharacterCodec.Capture(c);
        var restored = CharacterCodec.Restore(snap, _content);

        Assert.Equal(c.Clade.Id,        restored.Clade.Id);
        Assert.Equal(c.Species.Id,      restored.Species.Id);
        Assert.Equal(c.ClassDef.Id,     restored.ClassDef.Id);
        Assert.Equal(c.Background.Id,   restored.Background.Id);
        Assert.Equal(c.Abilities.STR,   restored.Abilities.STR);
        Assert.Equal(c.Abilities.WIS,   restored.Abilities.WIS);
        Assert.Equal(c.Level,           restored.Level);
        Assert.Equal(c.Xp,              restored.Xp);
        Assert.Equal(c.MaxHp,           restored.MaxHp);
        Assert.Equal(c.CurrentHp,       restored.CurrentHp);
        Assert.Equal(c.ExhaustionLevel, restored.ExhaustionLevel);
        Assert.Contains(Condition.Frightened, restored.Conditions);
        Assert.Single(restored.Inventory.Items);
        Assert.Equal("rend_sword", restored.Inventory.Items[0].Def.Id);
        Assert.Equal(EquipSlot.MainHand, restored.Inventory.Items[0].EquippedAt);
        Assert.Same(restored.Inventory.Items[0], restored.Inventory.GetEquipped(EquipSlot.MainHand));

        // Skill set should match (set equality)
        Assert.Equal(c.SkillProficiencies, restored.SkillProficiencies);
    }

    [Fact]
    public void SaveBody_PlayerCharacter_RoundTripsThroughSaveCodec()
    {
        var c = MakeBasicCharacter();
        var snap = CharacterCodec.Capture(c);

        var header = new SaveHeader { Version = C.SAVE_SCHEMA_VERSION, WorldSeedHex = "0xFEED" };
        var body   = new SaveBody { PlayerCharacter = snap };
        // Player is not interesting for this test; clock + chunks empty.
        body.Player.Id = 1;
        body.Player.Name = "Tester";

        var bytes = SaveCodec.Serialize(header, body);
        var (h2, body2) = SaveCodec.Deserialize(bytes);

        Assert.Equal(header.Version, h2.Version);
        Assert.NotNull(body2.PlayerCharacter);
        Assert.Equal(snap.CladeId,   body2.PlayerCharacter!.CladeId);
        Assert.Equal(snap.SpeciesId, body2.PlayerCharacter.SpeciesId);
        Assert.Equal(snap.ClassId,   body2.PlayerCharacter.ClassId);
        Assert.Equal(snap.STR,       body2.PlayerCharacter.STR);
        Assert.Equal(snap.WIS,       body2.PlayerCharacter.WIS);
        Assert.Equal(snap.MaxHp,     body2.PlayerCharacter.MaxHp);
    }

    [Fact]
    public void SaveBody_NoCharacter_StillRoundTrips()
    {
        // Phase-4-style body with no character. Round-trips cleanly because
        // TAG_CHARACTER is only written when PlayerCharacter is non-null.
        var header = new SaveHeader { Version = C.SAVE_SCHEMA_VERSION, WorldSeedHex = "0xDEAD" };
        var body   = new SaveBody();
        var bytes = SaveCodec.Serialize(header, body);
        var (_, body2) = SaveCodec.Deserialize(bytes);
        Assert.Null(body2.PlayerCharacter);
    }

    private Character MakeBasicCharacter()
    {
        var b = new CharacterBuilder
        {
            Clade        = _content.Clades["canidae"],
            Species      = _content.Species["wolf"],
            ClassDef     = _content.Classes["fangsworn"],
            Background   = _content.Backgrounds["pack_raised"],
            BaseAbilities = new AbilityScores(15, 14, 13, 12, 10, 8),
            Name         = "Roundtrip",
        };
        // Class.SkillsChoose = 2 for fangsworn
        b.ChosenClassSkills.Add(SkillId.Athletics);
        b.ChosenClassSkills.Add(SkillId.Intimidation);
        return b.Build();
    }
}
