using Theriapolis.Core;
using Theriapolis.Core.Tactical;
using Xunit;

namespace Theriapolis.Tests.Tactical;

/// <summary>
/// Phase 4 chunk-determinism contract:
///  • Same (worldSeed, ChunkCoord) twice → byte-identical chunk hash.
///  • Stream cycle: generate → evict → regenerate → identical hash.
///  • Different chunk coords → different hashes (no chunk-coord collision).
/// </summary>
public sealed class TacticalChunkDeterminismTests : IClassFixture<WorldCache>
{
    private const ulong TestSeed = 0xCAFEBABEUL;
    private readonly WorldCache _cache;
    public TacticalChunkDeterminismTests(WorldCache c) => _cache = c;

    [Theory]
    [InlineData(0, 0)]
    [InlineData(5, 7)]
    [InlineData(20, 30)]
    [InlineData(60, 60)]
    public void SameChunk_GeneratesIdenticalBytes(int cx, int cy)
    {
        var w = _cache.Get(TestSeed).World;
        var a = TacticalChunkGen.Generate(TestSeed, new ChunkCoord(cx, cy), w);
        var b = TacticalChunkGen.Generate(TestSeed, new ChunkCoord(cx, cy), w);
        Assert.Equal(a.Hash(), b.Hash());
    }

    [Fact]
    public void StreamCycle_RegenerateProducesSameHash()
    {
        // Use two independent worldgen runs to avoid sharing any cached state
        // accidentally — each Generate call is supposed to be a pure function.
        var wA = _cache.Get(TestSeed, variant: 0).World;
        var wB = _cache.Get(TestSeed, variant: 1).World;
        var cc = new ChunkCoord(15, 20);
        var first  = TacticalChunkGen.Generate(TestSeed, cc, wA);
        var second = TacticalChunkGen.Generate(TestSeed, cc, wB);
        Assert.Equal(first.Hash(), second.Hash());
    }

    [Fact]
    public void DifferentCoords_DifferentHashes()
    {
        // Pick chunks that overlap a known settlement footprint so we
        // guarantee non-trivial content rather than picking edges that may
        // both be all-ocean (identical hashes are then a true positive,
        // not a determinism bug).
        var w = _cache.Get(TestSeed).World;
        var s = w.Settlements.First(s => !s.IsPoi && s.Tier <= 3);
        var anchor = ChunkCoord.ForWorldTile(s.TileX, s.TileY);
        var a = TacticalChunkGen.Generate(TestSeed, anchor, w);
        var b = TacticalChunkGen.Generate(TestSeed, new ChunkCoord(anchor.X + 4, anchor.Y), w);
        var c = TacticalChunkGen.Generate(TestSeed, new ChunkCoord(anchor.X, anchor.Y + 4), w);
        Assert.NotEqual(a.Hash(), b.Hash());
        Assert.NotEqual(a.Hash(), c.Hash());
        Assert.NotEqual(b.Hash(), c.Hash());
    }

    [Fact]
    public void DifferentSeeds_DifferentHashes()
    {
        var wA = _cache.Get(TestSeed).World;
        var wB = _cache.Get(TestSeed + 1).World;
        var sA = wA.Settlements.First(s => !s.IsPoi && s.Tier <= 3);
        var anchor = ChunkCoord.ForWorldTile(sA.TileX, sA.TileY);
        var a = TacticalChunkGen.Generate(TestSeed,     anchor, wA);
        var b = TacticalChunkGen.Generate(TestSeed + 1, anchor, wB);
        Assert.NotEqual(a.Hash(), b.Hash());
    }

    [Fact]
    public void Chunk_HasExpectedDimensions()
    {
        var w = _cache.Get(TestSeed).World;
        var chunk = TacticalChunkGen.Generate(TestSeed, new ChunkCoord(0, 0), w);
        Assert.Equal(C.TACTICAL_CHUNK_SIZE, chunk.Tiles.GetLength(0));
        Assert.Equal(C.TACTICAL_CHUNK_SIZE, chunk.Tiles.GetLength(1));
    }
}
