package erasure_coding_test

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

	"github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding"
	"github.com/seaweedfs/seaweedfs/weed/storage/needle"
	"github.com/seaweedfs/seaweedfs/weed/storage/types"
)

// setupScrubLocalVolume seeds one live .ecx entry and a shard 0 whose bytes form
// the needle's on-disk header, then returns a loaded EcVolume with shard 0 added.
func setupScrubLocalVolume(t *testing.T, shard0 []byte) *erasure_coding.EcVolume {
	t.Helper()
	dir := t.TempDir()
	collection := "test"
	vid := needle.VolumeId(1)
	base := filepath.Join(dir, collection+"_1")

	if err := os.WriteFile(base+".ecx", makeNeedleMapEntry(types.NeedleId(1), types.ToOffset(0), types.Size(100)), 0644); err != nil {
		t.Fatalf("write ecx: %v", err)
	}
	if err := os.WriteFile(base+".ecj", []byte{}, 0644); err != nil {
		t.Fatalf("write ecj: %v", err)
	}
	if err := os.WriteFile(base+".vif", []byte{}, 0644); err != nil {
		t.Fatalf("write vif: %v", err)
	}
	if err := os.WriteFile(base+".ec00", shard0, 0644); err != nil {
		t.Fatalf("write ec00: %v", err)
	}

	ecv, err := erasure_coding.NewEcVolume("hdd", dir, dir, collection, vid)
	if err != nil {
		t.Fatalf("NewEcVolume: %v", err)
	}
	shard, err := erasure_coding.NewEcVolumeShard("hdd", dir, collection, vid, 0)
	if err != nil {
		t.Fatalf("NewEcVolumeShard: %v", err)
	}
	if _, err := ecv.AddEcVolumeShard(shard); err != nil {
		t.Fatalf("AddEcVolumeShard: %v", err)
	}
	return ecv
}

// A needle the .ecx still reports as live (size 100) but whose reassembled
// on-disk header carries size 0 is a delete-state disagreement, not corruption.
func TestScrubLocal_SuppressesDeleteStateDisagreement(t *testing.T) {
	ecv := setupScrubLocalVolume(t, make([]byte, 256)) // all-zero header => size 0
	if _, _, errs := ecv.ScrubLocal(); len(errs) != 0 {
		t.Fatalf("delete-state disagreement must be suppressed, got %v", errs)
	}
}

// A non-zero on-disk header size that disagrees with the index is genuine
// corruption and must still be reported.
func TestScrubLocal_ReportsGenuineSizeCorruption(t *testing.T) {
	shard0 := make([]byte, 256)
	shard0[15] = 50 // header size field (big-endian uint32) = 50, != index 100 and != 0
	ecv := setupScrubLocalVolume(t, shard0)
	if _, _, errs := ecv.ScrubLocal(); len(errs) == 0 {
		t.Fatal("a non-zero size mismatch is genuine corruption and must be reported")
	}
}
