package wal

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

	"github.com/dailz/go-kv/manifest"
)

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

	replayer := &mockReplayer{}
	result, err := Recover(dir, replayer)
	if err != nil {
		t.Fatalf("Recover empty dir: %v", err)
	}
	if result.NextSequence != 0 {
		t.Errorf("NextSequence = %d, want 0", result.NextSequence)
	}
	if result.Truncated {
		t.Error("Truncated = true, want false")
	}
	if result.TruncateError != nil {
		t.Errorf("TruncateError = %v, want nil", result.TruncateError)
	}
}

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

	// Write test data: 2 batches across 1 segment.
	writeTestSegment(t, dir, 0, 0, [][]*WalEntry{
		{makePutEntry("key1", "val1"), makeDeleteEntry("key2")},
		{makePutEntry("key3", "val3")},
	})

	replayer := &mockReplayer{}
	result, err := Recover(dir, replayer)
	if err != nil {
		t.Fatalf("Recover: %v", err)
	}
	if result.NextSequence != 3 {
		t.Errorf("NextSequence = %d, want 3", result.NextSequence)
	}
	if result.NextSegmentID != 1 {
		t.Errorf("NextSegmentID = %d, want 1", result.NextSegmentID)
	}
	if result.Truncated {
		t.Error("Truncated = true, want false")
	}

	wantPuts := []replayPut{
		{key: "key1", value: "val1", seq: 0},
		{key: "key3", value: "val3", seq: 2},
	}
	if !reflect.DeepEqual(replayer.puts, wantPuts) {
		t.Errorf("puts = %#v, want %#v", replayer.puts, wantPuts)
	}
	wantDeletes := []replayDelete{
		{key: "key2", seq: 1},
	}
	if !reflect.DeepEqual(replayer.deletes, wantDeletes) {
		t.Errorf("deletes = %#v, want %#v", replayer.deletes, wantDeletes)
	}
}

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

	// Write valid batches, then corrupt the tail.
	filePath := writeTestSegment(t, dir, 0, 100, [][]*WalEntry{
		{makePutEntry("good1", "before-corruption")},
		{makePutEntry("good2", "also-before")},
	})
	appendFileBytes(t, filePath, []byte{0xDE, 0xAD, 0xBE, 0xEF})

	replayer := &mockReplayer{}
	result, err := Recover(dir, replayer)
	if err != nil {
		t.Fatalf("Recover with tail corruption: %v", err)
	}
	if !result.Truncated {
		t.Fatal("Truncated = false, want true")
	}
	if result.TruncateError == nil {
		t.Fatal("TruncateError = nil, want non-nil")
	}
	if result.NextSequence != 102 {
		t.Errorf("NextSequence = %d, want 102", result.NextSequence)
	}
	if result.NextSegmentID != 1 {
		t.Errorf("NextSegmentID = %d, want 1", result.NextSegmentID)
	}

	// Verify only the valid entries were replayed.
	wantPuts := []replayPut{
		{key: "good1", value: "before-corruption", seq: 100},
		{key: "good2", value: "also-before", seq: 101},
	}
	if !reflect.DeepEqual(replayer.puts, wantPuts) {
		t.Errorf("puts = %#v, want %#v", replayer.puts, wantPuts)
	}

	// Verify the file was truncated — it should be smaller than before.
	fi, fiErr := os.Stat(filePath)
	if fiErr != nil {
		t.Fatalf("Stat truncated file: %v", fiErr)
	}
	if fi.Size() == 0 {
		t.Error("truncated file is empty")
	}

	// Verify the truncated file still parses cleanly.
	replayer2 := &mockReplayer{}
	_, parseErr := ReplaySegmentFile(filePath, 100, replayer2)
	if parseErr != nil {
		t.Fatalf("replay after truncation: %v", parseErr)
	}
	if !reflect.DeepEqual(replayer2.puts, wantPuts) {
		t.Errorf("replay after truncation puts = %#v, want %#v", replayer2.puts, wantPuts)
	}
}

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

	// Write test data.
	writeTestSegment(t, dir, 0, 50, [][]*WalEntry{
		{makePutEntry("a", "b")},
		{makePutEntry("c", "d")},
	})

	replayer := &mockReplayer{}
	result, err := Recover(dir, replayer)
	if err != nil {
		t.Fatalf("Recover: %v", err)
	}

	// Verify MANIFEST file exists and has correct content.
	mf, err := manifest.Load(dir)
	if err != nil {
		t.Fatalf("manifest.Load after recover: %v", err)
	}
	if mf.RecoverySegmentID != result.NextSegmentID {
		t.Errorf("MANIFEST RecoverySegmentID = %d, want %d",
			mf.RecoverySegmentID, result.NextSegmentID)
	}

	// Verify MANIFEST file path.
	manifestPath := filepath.Join(dir, "MANIFEST")
	if _, err := os.Stat(manifestPath); os.IsNotExist(err) {
		t.Error("MANIFEST file should exist after Recover")
	}
}
