using NeonSprawl.Server.Game.PositionState;
using Xunit;

namespace NeonSprawl.Server.Tests.Game.PositionState;

public class MoveCommandValidationTests
{
    private static MovementValidationOptions Rules(
        double maxH, double maxV, bool bounds = false, bool horizontalEnabled = true) =>
        new()
        {
            HorizontalStepEnabled = horizontalEnabled,
            MaxHorizontalStep = maxH,
            MaxVerticalStep = maxV,
            DistrictBoundsEnabled = bounds,
            DistrictMinX = 0,
            DistrictMaxX = 10,
            DistrictMinY = 0,
            DistrictMaxY = 10,
            DistrictMinZ = 0,
            DistrictMaxZ = 10,
        };

    [Fact]
    public void TryValidate_ShouldAllow_WhenHorizontalExactlyAtMax()
    {
        // Arrange

        var from = new PositionSnapshot(0, 0, 0, 0);
        var to = new PositionVector { X = 5, Y = 0, Z = 0 };
        // Act
        var ok = MoveCommandValidation.TryValidate(from, to, Rules(5, 10), out var code);
        // Assert
        Assert.True(ok);
        Assert.Equal("", code);
    }

    [Fact]
    public void TryValidate_ShouldRejectHorizontal_WhenJustOutsideMax()
    {
        // Arrange

        var from = new PositionSnapshot(0, 0, 0, 0);
        var to = new PositionVector { X = 5 + 1e-6, Y = 0, Z = 0 };
        // Act
        var ok = MoveCommandValidation.TryValidate(from, to, Rules(5, 10), out var code);
        // Assert
        Assert.False(ok);
        Assert.Equal(MoveCommandReasonCodes.HorizontalStepExceeded, code);
    }

    [Fact]
    public void TryValidate_ShouldAllow_WhenVerticalExactlyAtMax()
    {
        // Arrange

        var from = new PositionSnapshot(0, 1, 0, 0);
        var to = new PositionVector { X = 0, Y = 2.5, Z = 0 };
        // Act
        var ok = MoveCommandValidation.TryValidate(from, to, Rules(10, 1.5), out _);
        // Assert
        Assert.True(ok);
    }

    [Fact]
    public void TryValidate_ShouldRejectVertical_WhenJustOutsideMax()
    {
        // Arrange

        var from = new PositionSnapshot(0, 1, 0, 0);
        var to = new PositionVector { X = 0, Y = 2.5 + 1e-9, Z = 0 };
        // Act
        var ok = MoveCommandValidation.TryValidate(from, to, Rules(10, 1.5), out var code);
        // Assert
        Assert.False(ok);
        Assert.Equal(MoveCommandReasonCodes.VerticalStepExceeded, code);
    }

    [Fact]
    public void TryValidate_ShouldPreferHorizontalReason_WhenBothWouldFail()
    {
        // Arrange

        var from = new PositionSnapshot(0, 0, 0, 0);
        var to = new PositionVector { X = 100, Y = 100, Z = 0 };
        // Act
        var ok = MoveCommandValidation.TryValidate(from, to, Rules(1, 1), out var code);
        // Assert
        Assert.False(ok);
        Assert.Equal(MoveCommandReasonCodes.HorizontalStepExceeded, code);
    }

    [Fact]
    public void TryValidate_ShouldAllow_WhenHorizontalDisabled()
    {
        // Arrange

        var from = new PositionSnapshot(0, 0, 0, 0);
        var to = new PositionVector { X = 1000, Y = 0, Z = 1000 };
        // Act
        var ok = MoveCommandValidation.TryValidate(from, to, Rules(1, 10, horizontalEnabled: false), out var code);
        // Assert
        Assert.True(ok);
        Assert.Equal("", code);
    }

    [Fact]
    public void TryValidate_ShouldIgnoreBounds_WhenDistrictDisabled()
    {
        // Arrange

        var from = new PositionSnapshot(0, 0, 0, 0);
        var to = new PositionVector { X = -50, Y = 0, Z = 0 };
        // Act
        var ok = MoveCommandValidation.TryValidate(from, to, Rules(100, 100, bounds: false), out _);
        // Assert
        Assert.True(ok);
    }

    [Fact]
    public void TryValidate_ShouldRejectOutOfBounds_WhenDistrictEnabled()
    {
        // Arrange

        var from = new PositionSnapshot(5, 5, 5, 0);
        var to = new PositionVector { X = 5, Y = 5, Z = 11 };
        // Act
        var ok = MoveCommandValidation.TryValidate(from, to, Rules(20, 20, bounds: true), out var code);
        // Assert
        Assert.False(ok);
        Assert.Equal(MoveCommandReasonCodes.OutOfBounds, code);
    }

    [Fact]
    public void TryValidate_ShouldAllowOnBoundsSurface_WhenDistrictEnabled()
    {
        // Arrange

        var from = new PositionSnapshot(5, 5, 5, 0);
        var to = new PositionVector { X = 10, Y = 10, Z = 10 };
        // Act
        var ok = MoveCommandValidation.TryValidate(from, to, Rules(20, 20, bounds: true), out _);
        // Assert
        Assert.True(ok);
    }
}
