package world

import (
	_ "embed"
	"testing"

	"regionio/internal/protocol"
)

// Captured from vanilla 26.1.2 after placing minecraft:glowstone at
// (15,100,8). Layout is YZX over [0..30]x[85..115]x[-7..23].
//
//go:embed testdata/vanilla_glowstone_block_light.bin
var vanillaGlowstoneBlockLight []byte

func TestIncrementalBlockLightAgainstVanillaFixture(t *testing.T) {
	const minX, minY, minZ, size = 0, 85, -7, 31
	if len(vanillaGlowstoneBlockLight) != size*size*size {
		t.Fatalf("vanilla fixture length = %d, want %d", len(vanillaGlowstoneBlockLight), size*size*size)
	}
	cache := NewCache(-1, func(cx, cz int32) *Chunk {
		return NewChunk(cx, cz, BiomePlains)
	})
	for cz := int32(-1); cz <= 1; cz++ {
		for cx := int32(-1); cx <= 1; cx++ {
			cache.chunkAt(cx, cz)
		}
	}
	glowstone, _ := nameToStateID("minecraft:glowstone", nil)
	if valid, _ := cache.SetBlockWithLight(15, 100, 8, glowstone); !valid {
		t.Fatal("glowstone edit rejected")
	}

	fixtureIndex := 0
	mismatches := 0
	for y := minY; y < minY+size; y++ {
		for z := minZ; z < minZ+size; z++ {
			for x := minX; x < minX+size; x++ {
				chunk := cache.chunkAt(int32(x>>4), int32(z>>4))
				_, got, ready := chunk.LightAt(x, y, z)
				want := vanillaGlowstoneBlockLight[fixtureIndex]
				fixtureIndex++
				if !ready || got != want {
					if mismatches < 10 {
						t.Errorf("block light (%d,%d,%d) = %d, ready=%v; vanilla=%d", x, y, z, got, ready, want)
					}
					mismatches++
				}
			}
		}
	}
	if mismatches > 10 {
		t.Errorf("... and %d additional light mismatches", mismatches-10)
	}
}

func TestIncrementalBlockLightCrossesChunkBoundaryAndClears(t *testing.T) {
	cache := NewCache(-1, func(cx, cz int32) *Chunk {
		return NewChunk(cx, cz, BiomePlains)
	})
	left := cache.chunkAt(0, 0)
	right := cache.chunkAt(1, 0)
	if _, err := cache.FrameErr(0, 0); err != nil {
		t.Fatal(err)
	}
	if _, err := cache.FrameErr(1, 0); err != nil {
		t.Fatal(err)
	}

	glowstone, _ := nameToStateID("minecraft:glowstone", nil)
	if emission := lightEmission(glowstone); emission != 15 {
		t.Fatalf("glowstone emission = %d, want 15", emission)
	}
	valid, changed := cache.SetBlockWithLight(15, 0, 8, glowstone)
	if !valid || !containsChunkPos(changed, 0, 0) || !containsChunkPos(changed, 1, 0) {
		t.Fatalf("place changed = %v, valid=%v; want chunks (0,0) and (1,0)", changed, valid)
	}
	assertLight(t, left, 15, 0, 8, 15)
	assertLight(t, right, 0, 0, 8, 14)
	assertLight(t, right, 1, 0, 8, 13)

	valid, changed = cache.SetBlockWithLight(15, 0, 8, StateAir)
	if !valid || !containsChunkPos(changed, 0, 0) || !containsChunkPos(changed, 1, 0) {
		t.Fatalf("remove changed = %v, valid=%v; want chunks (0,0) and (1,0)", changed, valid)
	}
	assertLight(t, left, 15, 0, 8, 0)
	assertLight(t, right, 0, 0, 8, 0)
}

func TestIncrementalSkyLightSpreadsUnderRoofAcrossChunkBoundary(t *testing.T) {
	cache := NewCache(-1, func(cx, cz int32) *Chunk {
		chunk := NewChunk(cx, cz, BiomePlains)
		for z := 0; z < 16; z++ {
			for x := 0; x < 16; x++ {
				chunk.setBlockRaw(x, 1, z, StateStone)
			}
		}
		return chunk
	})
	left := cache.chunkAt(0, 0)
	right := cache.chunkAt(1, 0)
	if _, err := cache.FrameErr(0, 0); err != nil {
		t.Fatal(err)
	}
	if _, err := cache.FrameErr(1, 0); err != nil {
		t.Fatal(err)
	}

	assertSky(t, right, 0, 0, 8, 0)
	valid, changed := cache.SetBlockWithLight(15, 1, 8, StateAir)
	if !valid || !containsChunkPos(changed, 0, 0) || !containsChunkPos(changed, 1, 0) {
		t.Fatalf("open changed = %v, valid=%v; want chunks (0,0) and (1,0)", changed, valid)
	}
	assertSky(t, left, 15, 0, 8, 15)
	assertSky(t, right, 0, 0, 8, 14)
	assertSky(t, right, 1, 0, 8, 13)

	valid, changed = cache.SetBlockWithLight(15, 1, 8, StateStone)
	if !valid || !containsChunkPos(changed, 0, 0) || !containsChunkPos(changed, 1, 0) {
		t.Fatalf("close changed = %v, valid=%v; want chunks (0,0) and (1,0)", changed, valid)
	}
	assertSky(t, left, 15, 0, 8, 0)
	assertSky(t, right, 0, 0, 8, 0)
}

func assertLight(t *testing.T, chunk *Chunk, x, y, z int, want byte) {
	t.Helper()
	_, got, ready := chunk.LightAt(x, y, z)
	if !ready || got != want {
		t.Fatalf("block light (%d,%d,%d) = %d, ready=%v; want %d", x, y, z, got, ready, want)
	}
}

func assertSky(t *testing.T, chunk *Chunk, x, y, z int, want byte) {
	t.Helper()
	got, _, ready := chunk.LightAt(x, y, z)
	if !ready || got != want {
		t.Fatalf("sky light (%d,%d,%d) = %d, ready=%v; want %d", x, y, z, got, ready, want)
	}
}

func containsChunkPos(chunks []ChunkPos, x, z int32) bool {
	for _, chunk := range chunks {
		if chunk.X == x && chunk.Z == z {
			return true
		}
	}
	return false
}

func TestEncodeLightUpdateLayout(t *testing.T) {
	chunk := NewChunk(-2, 3, BiomePlains)
	r := protocol.NewReader(chunk.EncodeLightUpdate())

	if x, err := r.VarInt(); err != nil || x != -2 {
		t.Fatalf("chunk x = %d, %v; want -2", x, err)
	}
	if z, err := r.VarInt(); err != nil || z != 3 {
		t.Fatalf("chunk z = %d, %v; want 3", z, err)
	}

	masks := make([]uint64, 4)
	for i := range masks {
		length, err := r.VarInt()
		if err != nil || length < 0 || length > 1 {
			t.Fatalf("mask[%d] length = %d, %v; want 0 or 1", i, length, err)
		}
		if length == 1 {
			value, err := r.Int64()
			if err != nil {
				t.Fatalf("mask[%d]: %v", i, err)
			}
			masks[i] = uint64(value)
		}
	}
	if masks[0] == 0 || masks[2] == 0 {
		t.Fatalf("sky masks were not populated: data=%#x empty=%#x", masks[0], masks[2])
	}
	if masks[1] != 0 || masks[3] != (uint64(1)<<lightSections)-1 {
		t.Fatalf("block masks = data %#x, empty %#x", masks[1], masks[3])
	}

	consumeLightArrays(t, r)
	consumeLightArrays(t, r)
	if r.Remaining() != 0 {
		t.Fatalf("light update trailing bytes = %d", r.Remaining())
	}
}

func consumeLightArrays(t *testing.T, r *protocol.Reader) {
	t.Helper()
	count, err := r.VarInt()
	if err != nil {
		t.Fatal(err)
	}
	for i := int32(0); i < count; i++ {
		length, err := r.VarInt()
		if err != nil || length != 2048 {
			t.Fatalf("light array[%d] length = %d, %v; want 2048", i, length, err)
		}
		for j := int32(0); j < length; j++ {
			if _, err := r.ReadByte(); err != nil {
				t.Fatalf("light array[%d] byte %d: %v", i, j, err)
			}
		}
	}
}
