package winpwn

import "testing"

// These tests pin heap_segment.go's decoding against the field values
// confirmed live against this machine's build (10.0.26100) via the Go
// OpenProcessMemory / ReadAt path -- the same empirical methodology
// heap_test.go and heap_lfh_test.go use.

func TestReadSegmentHeapRejectsNTHeap(t *testing.T) {
	buf := make([]byte, 0x20)
	binaryLEPutUint32(buf[0x10:], heapSignatureNT)
	if _, err := ReadSegmentHeap(newByteReaderAt(buf), 0); err == nil {
		t.Fatal("expected ReadSegmentHeap to reject an NT Heap signature")
	}
}

func TestReadSegmentHeapDecodesCapturedFields(t *testing.T) {
	// Use heapAddr=0 to keep the buffer small (matches heap_test.go's style).
	// Buffer must cover LFH bucket array: segHeapOffLfhContext(0x340)+segLfhCtxOffBuckets(0x080)+129*8=0x7c8
	buf := make([]byte, 0x800)
	// Signature at +0x010: 0xddeeddee (confirmed live for heap_segment.exe)
	binaryLEPutUint32(buf[segHeapOffSignature:], heapSignatureSegment)
	// GlobalFlags at +0x014
	binaryLEPutUint32(buf[segHeapOffGlobalFlags:], 0x00001000)
	// VsContext (+0x280): TotalCommittedUnits=12, FreeCommittedUnits=3
	vsBase := segHeapOffVsContext
	binaryLEPutUint64(buf[vsBase+vsCtxOffTotalCommitted:], 12)
	binaryLEPutUint64(buf[vsBase+vsCtxOffFreeCommitted:], 3)
	// VsContext SubsegmentList head pointing to itself (empty list)
	headAddr := uint64(vsBase + vsCtxOffSubsegmentList)
	binaryLEPutUint64(buf[headAddr:], headAddr)   // Flink
	binaryLEPutUint64(buf[headAddr+8:], headAddr) // Blink
	// LfhContext (+0x340): all bucket pointers 0 or stub
	// (nothing to set; readSegmentLFHBuckets skips them)

	h, err := ReadSegmentHeap(newByteReaderAt(buf), 0)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if h.Signature != heapSignatureSegment {
		t.Errorf("Signature = 0x%08x, want 0x%08x", h.Signature, heapSignatureSegment)
	}
	if h.GlobalFlags != 0x00001000 {
		t.Errorf("GlobalFlags = 0x%08x, want 0x00001000", h.GlobalFlags)
	}
	if h.VS.CommittedUnits != 12 {
		t.Errorf("VS.CommittedUnits = %d, want 12", h.VS.CommittedUnits)
	}
	if h.VS.FreeUnits != 3 {
		t.Errorf("VS.FreeUnits = %d, want 3", h.VS.FreeUnits)
	}
	if h.VS.SubsegmentCount != 0 {
		t.Errorf("VS.SubsegmentCount = %d, want 0 (empty list)", h.VS.SubsegmentCount)
	}
}

func TestAdjacentAddressPairsFindsStep(t *testing.T) {
	// Mirror the heap_segment scenario: 5 allocations of 32-byte Profile,
	// some adjacent (32 bytes apart) and some not.
	addrs := []uint64{0x60a140, 0x60a160, 0x60a170, 0x60a1a0, 0x60b000}
	// Adjacent pairs at step=32: (0x60a140, 0x60a160) ← gap=32; (0x60a170, 0x60a1a0) ← gap=48, nope
	// Actually: 0x60a140+0x20=0x60a160 ✓; 0x60a160+0x10=0x60a170? no (10≠20=32)
	// Let me fix: 0x60a140+32=0x60a160 ✓, 0x60a1a0+32=0x60a1c0 not in set
	pairs := AdjacentAddressPairs(addrs, 32)
	if len(pairs) != 1 {
		t.Fatalf("got %d pairs, want 1; pairs=%v", len(pairs), pairs)
	}
	if pairs[0][0] != 0x60a140 || pairs[0][1] != 0x60a160 {
		t.Errorf("pair = (0x%x, 0x%x), want (0x60a140, 0x60a160)", pairs[0][0], pairs[0][1])
	}
}

func TestFindAdjacentPairReturnsFalseWhenNone(t *testing.T) {
	addrs := []uint64{0x1000, 0x2000, 0x3000}
	_, _, found := FindAdjacentPair(addrs, 32)
	if found {
		t.Fatal("expected found=false for addresses spaced 0x1000 apart, step=32")
	}
}

func TestAdjacentBusyPairsOnEntryChain(t *testing.T) {
	// Two busy entries packed with no free in between: should produce one pair.
	// Two more with a free entry in between: should not.
	entries := []HeapEntry{
		{Addr: 0x1000, Size: 2, Flags: HeapEntryBusy}, // NextEntry = 0x1020
		{Addr: 0x1020, Size: 2, Flags: HeapEntryBusy}, // NextEntry = 0x1040
		{Addr: 0x1040, Size: 2, Flags: 0},              // free
		{Addr: 0x1060, Size: 2, Flags: HeapEntryBusy},
	}
	pairs := AdjacentBusyPairs(entries)
	if len(pairs) != 1 {
		t.Fatalf("got %d pairs, want 1", len(pairs))
	}
	if pairs[0][0].Addr != 0x1000 || pairs[0][1].Addr != 0x1020 {
		t.Errorf("pair addrs = (0x%x, 0x%x)", pairs[0][0].Addr, pairs[0][1].Addr)
	}
}
