package wal

import (
	"bytes"
	"testing"
)

func TestRecordRoundtrip(t *testing.T) {
	payload := []byte("hello world")
	encoded := EncodePhysicalRecord(RecFull, payload)

	rec, consumed, err := DecodePhysicalRecord(encoded)
	if err != nil {
		t.Fatalf("DecodePhysicalRecord failed: %v", err)
	}

	if rec.Type != RecFull {
		t.Errorf("expected type RecFull(%d), got %d", RecFull, rec.Type)
	}
	if string(rec.Payload) != "hello world" {
		t.Errorf("expected payload 'hello world', got %q", string(rec.Payload))
	}
	if consumed != 7+len(payload) {
		t.Errorf("expected consumed %d, got %d", 7+len(payload), consumed)
	}
}

func TestSplitSmallPayload(t *testing.T) {
	payload := make([]byte, 100)
	for i := range payload {
		payload[i] = byte(i)
	}

	records := SplitIntoRecords(payload)
	if len(records) != 1 {
		t.Fatalf("expected 1 record, got %d", len(records))
	}

	rec, _, err := DecodePhysicalRecord(records[0])
	if err != nil {
		t.Fatalf("DecodePhysicalRecord failed: %v", err)
	}
	if rec.Type != RecFull {
		t.Errorf("expected RecFull, got %d", rec.Type)
	}
	if !bytes.Equal(rec.Payload, payload) {
		t.Error("payload mismatch")
	}
}

func TestSplitIntoRecords(t *testing.T) {
	// 40 KB payload → needs to split across blocks.
	payload := make([]byte, 40*1024)
	for i := range payload {
		payload[i] = byte(i % 256)
	}

	records := SplitIntoRecords(payload)
	if len(records) < 2 {
		t.Fatalf("expected at least 2 records, got %d", len(records))
	}

	// Verify fragment sequence.
	types := make([]uint8, len(records))
	var concatenated []byte
	for i, enc := range records {
		rec, _, err := DecodePhysicalRecord(enc)
		if err != nil {
			t.Fatalf("DecodePhysicalRecord record %d failed: %v", i, err)
		}
		types[i] = rec.Type
		concatenated = append(concatenated, rec.Payload...)
	}

	// First record must be RecFirst.
	if types[0] != RecFirst {
		t.Errorf("first record type: expected RecFirst(%d), got %d", RecFirst, types[0])
	}
	// Last record must be RecLast.
	if types[len(types)-1] != RecLast {
		t.Errorf("last record type: expected RecLast(%d), got %d", RecLast, types[len(types)-1])
	}
	// Middle records must be RecMiddle.
	for i := 1; i < len(types)-1; i++ {
		if types[i] != RecMiddle {
			t.Errorf("record %d type: expected RecMiddle(%d), got %d", i, RecMiddle, types[i])
		}
	}

	// Concatenated payloads must equal original.
	if !bytes.Equal(concatenated, payload) {
		t.Error("concatenated payloads do not match original")
	}
}

func TestBlockPadding(t *testing.T) {
	// blockOffset = WalBlockSize - 5 → remaining = 5, which is <= 7 → padding needed = 5.
	blockOffset := uint32(WalBlockSize - 5)
	padding := PaddingNeeded(blockOffset)
	if padding != 5 {
		t.Errorf("PaddingNeeded(%d): expected 5, got %d", blockOffset, padding)
	}

	// Cannot fit a record.
	if CanFitRecord(blockOffset, 1) {
		t.Error("CanFitRecord should return false when remaining <= 7")
	}

	// blockOffset = WalBlockSize - 8 → remaining = 8, which is > 7 → no padding needed.
	blockOffset2 := uint32(WalBlockSize - 8)
	padding2 := PaddingNeeded(blockOffset2)
	if padding2 != 0 {
		t.Errorf("PaddingNeeded(%d): expected 0, got %d", blockOffset2, padding2)
	}

	// Can fit a 1-byte payload: remaining=8, header=7, payload=1 → 8 >= 8.
	if !CanFitRecord(blockOffset2, 1) {
		t.Error("CanFitRecord should return true when remaining=8 and payloadLen=1")
	}

	// Cannot fit a 2-byte payload: remaining=8, header=7, payload=2 → 8 < 9.
	if CanFitRecord(blockOffset2, 2) {
		t.Error("CanFitRecord should return false when remaining=8 and payloadLen=2")
	}
}

func TestCRCMismatch(t *testing.T) {
	encoded := EncodePhysicalRecord(RecFull, []byte("test"))
	// Corrupt a payload byte.
	encoded[8] ^= 0xFF

	_, _, err := DecodePhysicalRecord(encoded)
	if err == nil {
		t.Error("expected CRC mismatch error")
	}
}

func TestDataTooShort(t *testing.T) {
	_, _, err := DecodePhysicalRecord([]byte{1, 2, 3})
	if err == nil {
		t.Error("expected error for data too short")
	}
}
