using Theriapolis.Core.Data;
using Theriapolis.Core.Rules.Character;
using Theriapolis.Core.Rules.Combat;
using Theriapolis.Core.Rules.Stats;
using Theriapolis.Core.Util;
using Xunit;

namespace Theriapolis.Tests.Combat;

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

    [Fact]
    public void EdgeToEdge_Adjacent_ReturnsZero()
    {
        var a = MakeMediumNpc(new Vec2(5, 5));
        var b = MakeMediumNpc(new Vec2(6, 5));
        Assert.Equal(0, ReachAndCover.EdgeToEdgeChebyshev(a, b));
    }

    [Fact]
    public void EdgeToEdge_OneTileApart_ReturnsOne()
    {
        var a = MakeMediumNpc(new Vec2(5, 5));
        var b = MakeMediumNpc(new Vec2(7, 5));   // 1 empty tile between
        Assert.Equal(1, ReachAndCover.EdgeToEdgeChebyshev(a, b));
    }

    [Fact]
    public void EdgeToEdge_LargeAttacker_FootprintCountedCorrectly()
    {
        var large  = MakeLargeNpc(new Vec2(0, 0));   // occupies (0..1, 0..1)
        var medium = MakeMediumNpc(new Vec2(3, 0));  // 1 empty tile between (large's right edge = 1, medium = 3)
        Assert.Equal(1, ReachAndCover.EdgeToEdgeChebyshev(large, medium));
    }

    [Fact]
    public void IsInReach_MeleeAdjacent_True()
    {
        var a = MakeMediumNpc(new Vec2(0, 0));
        var b = MakeMediumNpc(new Vec2(1, 0));
        var attack = new AttackOption { Name = "Bite", Damage = new DamageRoll(1, 4, 0, DamageType.Piercing), ReachTiles = 1 };
        Assert.True(ReachAndCover.IsInReach(a, b, attack));
    }

    [Fact]
    public void IsInReach_MeleeOutOfReach_False()
    {
        var a = MakeMediumNpc(new Vec2(0, 0));
        var b = MakeMediumNpc(new Vec2(3, 0));
        var attack = new AttackOption { Name = "Bite", Damage = new DamageRoll(1, 4, 0, DamageType.Piercing), ReachTiles = 1 };
        Assert.False(ReachAndCover.IsInReach(a, b, attack));
    }

    [Fact]
    public void IsInReach_RangedShortRange_True()
    {
        var a = MakeMediumNpc(new Vec2(0, 0));
        var b = MakeMediumNpc(new Vec2(8, 0));
        var bow = new AttackOption { Name = "Bow", Damage = new DamageRoll(1, 6, 0, DamageType.Piercing),
                                     RangeShortTiles = 16, RangeLongTiles = 64 };
        Assert.True(ReachAndCover.IsInReach(a, b, bow));
        Assert.False(ReachAndCover.IsLongRange(a, b, bow));
    }

    [Fact]
    public void IsInReach_RangedLongRange_TrueWithLongRangeFlag()
    {
        var a = MakeMediumNpc(new Vec2(0, 0));
        var b = MakeMediumNpc(new Vec2(40, 0));
        var bow = new AttackOption { Name = "Bow", Damage = new DamageRoll(1, 6, 0, DamageType.Piercing),
                                     RangeShortTiles = 16, RangeLongTiles = 64 };
        Assert.True(ReachAndCover.IsInReach(a, b, bow));
        Assert.True(ReachAndCover.IsLongRange(a, b, bow));
    }

    [Fact]
    public void StepToward_MovesOneTileTowardGoal()
    {
        var step = ReachAndCover.StepToward(new Vec2(0, 0), new Vec2(5, 3));
        Assert.Equal(1, step.X);
        Assert.Equal(1, step.Y);
    }

    [Fact]
    public void StepToward_AtGoal_ReturnsSamePosition()
    {
        var step = ReachAndCover.StepToward(new Vec2(5, 5), new Vec2(5, 5));
        Assert.Equal(5, step.X);
        Assert.Equal(5, step.Y);
    }

    private Combatant MakeMediumNpc(Vec2 pos)
        => Combatant.FromNpcTemplate(_content.Npcs.Templates.First(t => t.Id == "wolf"), id: 1, pos);

    private Combatant MakeLargeNpc(Vec2 pos)
        => Combatant.FromNpcTemplate(_content.Npcs.Templates.First(t => t.Id == "bear_brown"), id: 2, pos);
}
