package wal

import (
	"os"
	"path/filepath"
	"testing"
)

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

	block := make([]byte, WalBlockSize)
	copy(block, encoded)

	recs, err := ParseBlock(block)
	if err != nil {
		t.Fatalf("ParseBlock: %v", err)
	}
	if len(recs) != 1 {
		t.Fatalf("expected 1 record, got %d", len(recs))
	}
	if recs[0].Type != RecFull {
		t.Errorf("Type = %d, want RecFull(%d)", recs[0].Type, RecFull)
	}
	if string(recs[0].Payload) != string(payload) {
		t.Errorf("Payload = %q, want %q", recs[0].Payload, payload)
	}
}

func TestParseBlockMultipleRecords(t *testing.T) {
	payloads := [][]byte{
		[]byte("first"),
		[]byte("second"),
		[]byte("third"),
	}

	block := make([]byte, WalBlockSize)
	offset := 0
	for i, p := range payloads {
		rec := EncodePhysicalRecord(RecFull, p)
		copy(block[offset:], rec)
		offset += len(rec)
		if offset > WalBlockSize {
			t.Fatalf("record %d overflows block", i)
		}
	}

	recs, err := ParseBlock(block)
	if err != nil {
		t.Fatalf("ParseBlock: %v", err)
	}
	if len(recs) != 3 {
		t.Fatalf("expected 3 records, got %d", len(recs))
	}
	for i, want := range payloads {
		if string(recs[i].Payload) != string(want) {
			t.Errorf("rec[%d].Payload = %q, want %q", i, recs[i].Payload, want)
		}
	}
}

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

	// Place record at offset 0, then fill rest with zeros.
	block := make([]byte, WalBlockSize)
	copy(block, encoded)

	recs, err := ParseBlock(block)
	if err != nil {
		t.Fatalf("ParseBlock: %v", err)
	}
	if len(recs) != 1 {
		t.Fatalf("expected 1 record, got %d", len(recs))
	}
	if string(recs[0].Payload) != string(payload) {
		t.Errorf("Payload = %q, want %q", recs[0].Payload, payload)
	}
}

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

	block := make([]byte, WalBlockSize)
	copy(block, encoded)
	// Write non-zero byte in the trailing padding area.
	block[len(encoded)+3] = 0xFF

	recs, err := ParseBlock(block)
	if err == nil {
		t.Fatal("expected tail corruption error")
	}
	if !IsTailCorruption(err) {
		t.Errorf("expected TailCorruptionError, got: %v", err)
	}
	// Should still return records parsed before the corruption.
	if len(recs) != 1 {
		t.Errorf("expected 1 record before corruption, got %d", len(recs))
	}
}

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

	block := make([]byte, WalBlockSize)
	copy(block, encoded)
	// Corrupt a payload byte.
	block[PhysicalRecordHeaderSize+1] ^= 0xFF

	_, err := ParseBlock(block)
	if err == nil {
		t.Fatal("expected CRC mismatch error")
	}
	if !IsTailCorruption(err) {
		t.Errorf("expected TailCorruptionError, got: %v", err)
	}
}

func TestParseBlockShortBlock(t *testing.T) {
	payload := []byte("short block data")
	encoded := EncodePhysicalRecord(RecFull, payload)

	// Simulate a short last block (less than WalBlockSize).
	block := make([]byte, len(encoded)+10) // extra trailing zeros
	copy(block, encoded)

	recs, err := ParseBlock(block)
	if err != nil {
		t.Fatalf("ParseBlock short block: %v", err)
	}
	if len(recs) != 1 {
		t.Fatalf("expected 1 record, got %d", len(recs))
	}
	if string(recs[0].Payload) != string(payload) {
		t.Errorf("Payload = %q, want %q", recs[0].Payload, payload)
	}
}

func TestParseBlockEmpty(t *testing.T) {
	block := make([]byte, 64) // all zeros

	recs, err := ParseBlock(block)
	if err != nil {
		t.Fatalf("ParseBlock empty: %v", err)
	}
	if len(recs) != 0 {
		t.Fatalf("expected 0 records, got %d", len(recs))
	}
}

func TestParseBlockZeroHeaderWithNonZeroTail(t *testing.T) {
	block := make([]byte, WalBlockSize)
	// First 7 bytes are zero (valid zero header), but byte at offset 8 is non-zero.
	block[8] = 0x42

	_, err := ParseBlock(block)
	if err == nil {
		t.Fatal("expected tail corruption error for zero header + non-zero tail")
	}
	if !IsTailCorruption(err) {
		t.Errorf("expected TailCorruptionError, got: %v", err)
	}
}

func TestIsTailCorruptionFalse(t *testing.T) {
	if IsTailCorruption(nil) {
		t.Error("IsTailCorruption(nil) = true, want false")
	}
	if IsTailCorruption(os.ErrNotExist) {
		t.Error("IsTailCorruption(ErrNotExist) = true, want false")
	}
}

func TestParseRecordsFromFile(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "segment-0.wal")

	// Create a segment file with header + one full block containing 2 records.
	f, err := os.Create(path)
	if err != nil {
		t.Fatal(err)
	}

	hdr := &WalFileHeader{
		SegmentID:     0,
		StartSequence: 0,
		BlockSize:     WalBlockSize,
	}
	encoded := EncodeWalHeader(hdr)
	if _, err := f.Write(encoded[:]); err != nil {
		t.Fatal(err)
	}

	block := make([]byte, WalBlockSize)
	payload1 := []byte("record-one")
	payload2 := []byte("record-two")
	offset := 0
	rec1 := EncodePhysicalRecord(RecFull, payload1)
	copy(block[offset:], rec1)
	offset += len(rec1)
	rec2 := EncodePhysicalRecord(RecFull, payload2)
	copy(block[offset:], rec2)

	if _, err := f.Write(block); err != nil {
		t.Fatal(err)
	}
	if err := f.Close(); err != nil {
		t.Fatal(err)
	}

	recs, err := ParseRecordsFromFile(path)
	if err != nil {
		t.Fatalf("ParseRecordsFromFile: %v", err)
	}
	if len(recs) != 2 {
		t.Fatalf("expected 2 records, got %d", len(recs))
	}
	if string(recs[0].Payload) != string(payload1) {
		t.Errorf("rec[0].Payload = %q, want %q", recs[0].Payload, payload1)
	}
	if string(recs[1].Payload) != string(payload2) {
		t.Errorf("rec[1].Payload = %q, want %q", recs[1].Payload, payload2)
	}
}

func TestParseRecordsFromFileShortLastBlock(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "segment-0.wal")

	f, err := os.Create(path)
	if err != nil {
		t.Fatal(err)
	}

	hdr := &WalFileHeader{
		SegmentID:     0,
		StartSequence: 0,
		BlockSize:     WalBlockSize,
	}
	encoded := EncodeWalHeader(hdr)
	if _, err := f.Write(encoded[:]); err != nil {
		t.Fatal(err)
	}

	// Write a partial block (just one record, no full 32KB).
	payload := []byte("short")
	rec := EncodePhysicalRecord(RecFull, payload)
	if _, err := f.Write(rec); err != nil {
		t.Fatal(err)
	}
	if err := f.Close(); err != nil {
		t.Fatal(err)
	}

	recs, err := ParseRecordsFromFile(path)
	if err != nil {
		t.Fatalf("ParseRecordsFromFile: %v", err)
	}
	if len(recs) != 1 {
		t.Fatalf("expected 1 record, got %d", len(recs))
	}
	if string(recs[0].Payload) != string(payload) {
		t.Errorf("Payload = %q, want %q", recs[0].Payload, payload)
	}
}
