package filer

import (
	"testing"
	"time"

	"github.com/seaweedfs/seaweedfs/weed/util/log_buffer"
)

// TestMetaLogInflightFloor pins the in-flight stamp bookkeeping backing
// Filer.LocalFlushedThroughTsNs: the floor is the oldest outstanding stamp,
// duplicate stamps are reference-counted, and a cleared registry reports zero.
func TestMetaLogInflightFloor(t *testing.T) {
	var inflight metaLogInflight
	if got := inflight.minTsNs(); got != 0 {
		t.Fatalf("empty registry: floor=%d want 0", got)
	}
	ts1 := inflight.stamp()
	ts2 := inflight.stamp()
	if ts2 < ts1 {
		t.Fatalf("stamps not monotonic: %d then %d", ts1, ts2)
	}
	if got := inflight.minTsNs(); got != ts1 {
		t.Fatalf("floor=%d want oldest stamp %d", got, ts1)
	}
	inflight.done(ts1)
	if got := inflight.minTsNs(); got != ts2 {
		t.Fatalf("after done(ts1): floor=%d want %d", got, ts2)
	}
	inflight.done(ts2)
	if got := inflight.minTsNs(); got != 0 {
		t.Fatalf("cleared registry: floor=%d want 0", got)
	}
}

// TestLocalFlushedThroughTsNsBoundsInflight pins the flush-watermark claim: a
// drained buffer claims "now", but an event stamped and not yet appended caps
// the claim just below its timestamp - a peer bounding disk reads by the
// claim must not advance past an event still on its way into the buffer.
func TestLocalFlushedThroughTsNsBoundsInflight(t *testing.T) {
	f := &Filer{
		LocalMetaLogBuffer: log_buffer.NewLogBuffer("inflight-test", time.Minute, nil, nil, nil),
	}
	defer f.LocalMetaLogBuffer.ShutdownLogBuffer()

	now := time.Now().UnixNano()
	if got := f.LocalFlushedThroughTsNs(now); got != now {
		t.Fatalf("drained, nothing in flight: claim=%d want now=%d", got, now)
	}

	ts := f.metaLogInflight.stamp()
	if got := f.LocalFlushedThroughTsNs(time.Now().UnixNano()); got != ts-1 {
		t.Fatalf("with in-flight stamp %d: claim=%d want %d", ts, got, ts-1)
	}

	f.metaLogInflight.done(ts)
	now = time.Now().UnixNano()
	if got := f.LocalFlushedThroughTsNs(now); got != now {
		t.Fatalf("stamp cleared: claim=%d want now=%d", got, now)
	}
}

// TestLocalDeliveredThroughTsNsBoundsInflight pins the delivery-freshness
// cap: an idle heartbeat must not claim delivery-completeness past an event
// that is stamped but not yet appended - the peer aggregator turns that claim
// into its delivery low-watermark.
func TestLocalDeliveredThroughTsNsBoundsInflight(t *testing.T) {
	f := &Filer{
		LocalMetaLogBuffer: log_buffer.NewLogBuffer("delivered-test", time.Minute, nil, nil, nil),
	}
	defer f.LocalMetaLogBuffer.ShutdownLogBuffer()

	now := time.Now().UnixNano()
	if got := f.LocalDeliveredThroughTsNs(now); got != now {
		t.Fatalf("nothing in flight: claim=%d want now=%d", got, now)
	}
	ts := f.metaLogInflight.stamp()
	if got := f.LocalDeliveredThroughTsNs(time.Now().UnixNano()); got != ts-1 {
		t.Fatalf("with in-flight stamp %d: claim=%d want %d", ts, got, ts-1)
	}
	f.metaLogInflight.done(ts)
}

// TestClaimFencesFutureStamps pins the claim fence: once a claim is issued, a
// wall-clock step backwards must not let a later stamp land at or below it -
// the peer has already advanced its watermark to the claim.
func TestClaimFencesFutureStamps(t *testing.T) {
	f := &Filer{
		LocalMetaLogBuffer: log_buffer.NewLogBuffer("claim-fence-test", time.Minute, nil, nil, nil),
	}
	defer f.LocalMetaLogBuffer.ShutdownLogBuffer()

	// Claim with a sampled clock one hour ahead: to a stamp taken at the real
	// wall clock this is exactly a backward step after the claim was issued.
	aheadNs := time.Now().Add(time.Hour).UnixNano()
	if got := f.LocalDeliveredThroughTsNs(aheadNs); got != aheadNs {
		t.Fatalf("nothing in flight: claim=%d want %d", got, aheadNs)
	}
	ts := f.metaLogInflight.stamp()
	if ts <= aheadNs {
		t.Fatalf("stamp %d not fenced above issued delivery claim %d", ts, aheadNs)
	}
	f.metaLogInflight.done(ts)

	// The flush claim fences the same way.
	aheadNs += int64(time.Hour)
	if got := f.LocalFlushedThroughTsNs(aheadNs); got != aheadNs {
		t.Fatalf("drained buffer: claim=%d want %d", got, aheadNs)
	}
	ts = f.metaLogInflight.stamp()
	if ts <= aheadNs {
		t.Fatalf("stamp %d not fenced above issued flush claim %d", ts, aheadNs)
	}
	f.metaLogInflight.done(ts)
}

// TestMetaLogInflightStampMonotonic pins the registry-local monotonicity: a
// wall clock stepping backwards must not let a new stamp slip under an
// already-sampled floor.
func TestMetaLogInflightStampMonotonic(t *testing.T) {
	var inflight metaLogInflight
	ts1 := inflight.stamp()
	// Simulate a wall-clock step backwards: force the registry's history
	// ahead of the clock; the next stamp must still move forward.
	inflight.Lock()
	inflight.lastStampNs = ts1 + int64(time.Hour)
	inflight.Unlock()
	ts2 := inflight.stamp()
	if ts2 <= ts1+int64(time.Hour) {
		t.Fatalf("stamp regressed: %d after forcing history to %d", ts2, ts1+int64(time.Hour))
	}
	inflight.done(ts1)
	inflight.done(ts2)
}
