using Theriapolis.Core;
using Theriapolis.Core.World;
using Theriapolis.Core.World.Generation;
using Xunit;

namespace Theriapolis.Tests.Worldgen;

/// <summary>
/// Linear feature exclusion (Addendum A §2): no parallel river+rail or rail+road
/// on the same non-settlement tile. Road+river is allowed — it represents a
/// bridge crossing (see ValidationPassStage.CheckLinearExclusion).
/// </summary>
public sealed class LinearFeatureTests : IClassFixture<WorldCache>
{
    private readonly WorldCache _cache;

    public LinearFeatureTests(WorldCache cache) => _cache = cache;

    [Theory]
    [InlineData(0xCAFEBABEUL)]
    [InlineData(0x11223344UL)]
    [InlineData(0xDEADBEEFUL)]
    [InlineData(0xFEEDFACEUL)]
    [InlineData(0xABCDEF00UL)]
    [InlineData(0x00112233UL)]
    [InlineData(0x99AABBCCUL)]
    [InlineData(0x12345678UL)]
    [InlineData(0x87654321UL)]
    [InlineData(0x0DEADC0DUL)]
    public void NoParallelLinearFeatures_OnNonSettlementTiles(ulong seed)
    {
        var ctx   = _cache.Get(seed);
        var world = ctx.World;
        int W     = C.WORLD_WIDTH_TILES;
        int H     = C.WORLD_HEIGHT_TILES;

        int violations = 0;
        for (int y = 0; y < H; y++)
        for (int x = 0; x < W; x++)
        {
            ref var tile = ref world.TileAt(x, y);
            if ((tile.Features & FeatureFlags.IsSettlement) != 0) continue;

            bool hasRiver = (tile.Features & FeatureFlags.HasRiver) != 0;
            bool hasRail  = (tile.Features & FeatureFlags.HasRail)  != 0;
            bool hasRoad  = (tile.Features & FeatureFlags.HasRoad)  != 0;

            // River + Rail parallel
            if (hasRiver && hasRail &&
                tile.RiverFlowDir != Theriapolis.Core.Util.Dir.None &&
                tile.RailDir      != Theriapolis.Core.Util.Dir.None &&
                Theriapolis.Core.Util.Dir.IsParallel(tile.RiverFlowDir, tile.RailDir))
                violations++;

            // Rail + Road (always a violation outside settlements)
            if (hasRail && hasRoad)
                violations++;
        }

        Assert.Equal(0, violations);
    }

    [Fact]
    public void RoadNetwork_HasAtLeastOneRoad()
    {
        var ctx = _cache.Get(0xCAFEBABEUL);
        Assert.NotEmpty(ctx.World.Roads);
    }

    [Fact]
    public void RailNetwork_HasAtLeastOneRailLine()
    {
        if (!C.ENABLE_RAIL) return; // rail subsystem disabled; nothing to assert
        var ctx = _cache.Get(0xCAFEBABEUL);
        Assert.NotEmpty(ctx.World.Rails);
    }

    [Fact]
    public void Roads_AreInWorldPixelSpace()
    {
        var ctx      = _cache.Get(0xCAFEBABEUL);
        float maxCoord = C.WORLD_WIDTH_TILES * C.WORLD_TILE_PIXELS;
        foreach (var road in ctx.World.Roads)
        foreach (var pt in road.Points)
        {
            Assert.InRange(pt.X, 0f, maxCoord);
            Assert.InRange(pt.Y, 0f, maxCoord);
        }
    }

    [Fact]
    public void ValidationPassHash_RecordsZeroViolations()
    {
        var ctx = _cache.Get(0xCAFEBABEUL);
        if (!ctx.World.StageHashes.TryGetValue("ValidationPass", out ulong vhash))
            return; // stage didn't record hash — skip

        int violations = (int)(vhash / 1000);
        Assert.Equal(0, violations);
    }
}
