package wal

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

// writeSegmentFile creates a minimal WAL segment file in dir with the given
// segment ID and start sequence.
func writeSegmentFile(t *testing.T, dir string, segmentID, startSequence uint64) {
	t.Helper()
	path := filepath.Join(dir, fmt.Sprintf("segment-%d.wal", segmentID))
	f, err := os.Create(path)
	if err != nil {
		t.Fatalf("create segment file: %v", err)
	}
	defer f.Close()

	hdr := &WalFileHeader{
		SegmentID:     segmentID,
		StartSequence: startSequence,
		BlockSize:     WalBlockSize,
	}
	encoded := EncodeWalHeader(hdr)
	if _, err := f.Write(encoded[:]); err != nil {
		t.Fatalf("write header: %v", err)
	}
}

func TestParseSegmentFilename(t *testing.T) {
	tests := []struct {
		name      string
		wantID    uint64
		wantOK    bool
	}{
		{"segment-0.wal", 0, true},
		{"segment-1.wal", 1, true},
		{"segment-42.wal", 42, true},
		{"segment-18446744073709551615.wal", 18446744073709551615, true},
		{"segment-.wal", 0, false},            // no digits
		{"segment-abc.wal", 0, false},         // non-numeric
		{"segment-1.wal.bak", 0, false},       // extra suffix
		{"foo-segment-1.wal", 0, false},       // extra prefix
		{" Segment-1.wal", 0, false},          // space prefix
		{"segment--1.wal", 0, false},          // negative sign
		{"", 0, false},
		{"readme.md", 0, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotID, gotOK := ParseSegmentFilename(tt.name)
			if gotOK != tt.wantOK {
				t.Errorf("ParseSegmentFilename(%q) ok = %v, want %v", tt.name, gotOK, tt.wantOK)
			}
			if gotOK && gotID != tt.wantID {
				t.Errorf("ParseSegmentFilename(%q) id = %d, want %d", tt.name, gotID, tt.wantID)
			}
		})
	}
}

func TestScanSegmentsOrdered(t *testing.T) {
	dir := t.TempDir()
	writeSegmentFile(t, dir, 0, 100)
	writeSegmentFile(t, dir, 1, 200)
	writeSegmentFile(t, dir, 2, 300)

	segs, err := ScanSegments(dir, 0)
	if err != nil {
		t.Fatalf("ScanSegments: %v", err)
	}
	if len(segs) != 3 {
		t.Fatalf("expected 3 segments, got %d", len(segs))
	}

	for i, want := range []struct {
		id  uint64
		seq uint64
	}{
		{0, 100}, {1, 200}, {2, 300},
	} {
		if segs[i].SegmentID != want.id {
			t.Errorf("seg[%d].SegmentID = %d, want %d", i, segs[i].SegmentID, want.id)
		}
		if segs[i].StartSequence != want.seq {
			t.Errorf("seg[%d].StartSequence = %d, want %d", i, segs[i].StartSequence, want.seq)
		}
	}
}

func TestScanSegmentsFiltersBelowRecoveryID(t *testing.T) {
	dir := t.TempDir()
	for i := uint64(0); i < 5; i++ {
		writeSegmentFile(t, dir, i, i*100)
	}

	segs, err := ScanSegments(dir, 2)
	if err != nil {
		t.Fatalf("ScanSegments: %v", err)
	}
	if len(segs) != 3 {
		t.Fatalf("expected 3 segments, got %d", len(segs))
	}

	wantIDs := []uint64{2, 3, 4}
	for i, s := range segs {
		if s.SegmentID != wantIDs[i] {
			t.Errorf("seg[%d].SegmentID = %d, want %d", i, s.SegmentID, wantIDs[i])
		}
	}
}

func TestScanSegmentsGapDetection(t *testing.T) {
	dir := t.TempDir()
	writeSegmentFile(t, dir, 0, 0)
	writeSegmentFile(t, dir, 1, 100)
	// segment 2 is missing
	writeSegmentFile(t, dir, 3, 300)

	_, err := ScanSegments(dir, 0)
	if err == nil {
		t.Fatal("expected error for gap in segment sequence")
	}
	if !errors.Is(err, ErrSegmentGap) {
		t.Errorf("expected ErrSegmentGap, got: %v", err)
	}
}

func TestScanSegmentsEmpty(t *testing.T) {
	dir := t.TempDir()

	segs, err := ScanSegments(dir, 0)
	if err != nil {
		t.Fatalf("ScanSegments empty dir: %v", err)
	}
	if len(segs) != 0 {
		t.Fatalf("expected 0 segments, got %d", len(segs))
	}
}

func TestScanSegmentsIgnoresNonWalFiles(t *testing.T) {
	dir := t.TempDir()
	writeSegmentFile(t, dir, 0, 0)
	// Create non-WAL files that should be ignored.
	if err := os.WriteFile(filepath.Join(dir, "README.md"), []byte("hi"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(dir, "segment-abc.wal"), []byte("nope"), 0o644); err != nil {
		t.Fatal(err)
	}

	segs, err := ScanSegments(dir, 0)
	if err != nil {
		t.Fatalf("ScanSegments: %v", err)
	}
	if len(segs) != 1 {
		t.Fatalf("expected 1 segment, got %d", len(segs))
	}
	if segs[0].SegmentID != 0 {
		t.Errorf("SegmentID = %d, want 0", segs[0].SegmentID)
	}
}



