package storage

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

	"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
	"github.com/seaweedfs/seaweedfs/weed/storage/needle"
	"github.com/seaweedfs/seaweedfs/weed/storage/super_block"
	"github.com/seaweedfs/seaweedfs/weed/storage/volume_info"
)

// A volume's replication lives in two places that can disagree, and the .vif is
// the one that counts.
//
// Store.ConfigureVolume rewrites the .vif and never the replica-placement byte
// in the .dat superblock, so that byte keeps whatever the volume was created
// with for good. readSuperBlock reads the byte and then overrides it from the
// .vif, which is what makes the change take effect and survive a remount.
//
// Preferring the superblock instead would silently revert every replication
// change on the next mount, while the .vif on disk still recorded what the
// operator asked for. That is worth a test rather than a reading of the code,
// because it is one line to invert and the field beside it -- version -- really
// does resolve the other way, with the superblock outranking the .vif.
func TestVifReplicationOutranksSuperBlock(t *testing.T) {
	dir := t.TempDir()

	// Created carrying 000: the byte written into the .dat, and left there.
	created, err := super_block.NewReplicaPlacementFromString("000")
	if err != nil {
		t.Fatal(err)
	}
	v, err := NewVolume(dir, dir, "", 1, NeedleMapInMemory, created, &needle.TTL{}, 0, needle.GetCurrentVersion(), 0, 0)
	if err != nil {
		t.Fatalf("create volume: %v", err)
	}
	if got := v.SuperBlock.ReplicaPlacement.String(); got != "000" {
		t.Fatalf("volume created with replication %q, want 000", got)
	}
	v.Close()

	// What ConfigureVolume does: rewrite the .vif alone.
	vifFile := filepath.Join(dir, "1.vif")
	info, _, _, err := volume_info.MaybeLoadVolumeInfo(vifFile)
	if err != nil {
		t.Fatalf("load vif: %v", err)
	}
	info.Replication = "010"
	if err := volume_info.SaveVolumeInfo(vifFile, info); err != nil {
		t.Fatalf("save vif: %v", err)
	}

	// The .dat still says 000 — nothing rewrote it.
	reopened, err := NewVolume(dir, dir, "", 1, NeedleMapInMemory, nil, nil, 0, needle.GetCurrentVersion(), 0, 0)
	if err != nil {
		t.Fatalf("reload volume: %v", err)
	}
	defer reopened.Close()

	if got := reopened.SuperBlock.ReplicaPlacement.String(); got != "010" {
		t.Errorf("replication after reload = %q, want 010 from the .vif; "+
			"a reconfigured volume must not revert to the byte in its .dat", got)
	}
}

// An empty replication in the .vif declares nothing, so the superblock stands.
// Without this a volume that never had its replication configured would be
// forced to whatever the zero value parses as.
func TestEmptyVifReplicationLeavesSuperBlockAlone(t *testing.T) {
	dir := t.TempDir()

	created, err := super_block.NewReplicaPlacementFromString("010")
	if err != nil {
		t.Fatal(err)
	}
	v, err := NewVolume(dir, dir, "", 2, NeedleMapInMemory, created, &needle.TTL{}, 0, needle.GetCurrentVersion(), 0, 0)
	if err != nil {
		t.Fatalf("create volume: %v", err)
	}
	v.Close()

	vifFile := filepath.Join(dir, "2.vif")
	if err := volume_info.SaveVolumeInfo(vifFile, &volume_server_pb.VolumeInfo{Version: uint32(needle.GetCurrentVersion())}); err != nil {
		t.Fatalf("save vif: %v", err)
	}
	if _, err := os.Stat(vifFile); err != nil {
		t.Fatalf("vif not written: %v", err)
	}

	reopened, err := NewVolume(dir, dir, "", 2, NeedleMapInMemory, nil, nil, 0, needle.GetCurrentVersion(), 0, 0)
	if err != nil {
		t.Fatalf("reload volume: %v", err)
	}
	defer reopened.Close()

	if got := reopened.SuperBlock.ReplicaPlacement.String(); got != "010" {
		t.Errorf("replication after reload = %q, want the superblock's 010 to stand", got)
	}
}
