package backend

import (
	"testing"
	"time"
)

// TestMutationLogPurgeWorkerDeletesExpiredAppliedRows drives the worker
// end-to-end against the fake store: expired applied rows disappear while
// recent and pending rows survive, and Stop shuts the loop down cleanly.
func TestMutationLogPurgeWorkerDeletesExpiredAppliedRows(t *testing.T) {
	// The poll interval floors at 1s (see purgePollInterval), so use exactly
	// that to keep the test on the real contract.
	t.Setenv("DRIVE9_QUOTA_PURGE_INTERVAL_MS", "1000")
	t.Setenv("DRIVE9_QUOTA_MUTATION_LOG_RETENTION_HOURS", "1")

	fake := newFakeMetaQuotaStore()
	old := time.Now().UTC().Add(-2 * time.Hour)
	for i := 0; i < 5; i++ {
		fake.mutations = append(fake.mutations, fakeMutationRecord{
			id: int64(i + 1), tenantID: "tenant-a", typ: "file_create",
			status: "applied", createdAt: old,
		})
	}
	fake.mutations = append(fake.mutations,
		fakeMutationRecord{id: 100, tenantID: "tenant-a", typ: "file_create", status: "applied", createdAt: time.Now().UTC()},
		fakeMutationRecord{id: 101, tenantID: "tenant-a", typ: "file_create", status: "pending", createdAt: old},
	)
	fake.nextID = 102

	w := StartMutationLogPurgeWorker(fake)
	t.Cleanup(w.Stop)

	deadline := time.Now().Add(5 * time.Second)
	for {
		fake.mu.Lock()
		remaining := len(fake.mutations)
		fake.mu.Unlock()
		if remaining == 2 {
			return
		}
		if time.Now().After(deadline) {
			t.Fatalf("purge worker did not drain expired rows, %d rows remain", remaining)
		}
		time.Sleep(10 * time.Millisecond)
	}
}

// TestMutationLogPurgeWorkerNilStore documents that the worker is not started
// when central quota is not wired (mirrors StartMutationReplayWorker).
func TestMutationLogPurgeWorkerNilStore(t *testing.T) {
	if w := StartMutationLogPurgeWorker(nil); w != nil {
		t.Fatal("expected nil worker for nil store")
	}
}

// TestMutationLogRetentionHoursEnv covers the retention config parsing.
func TestMutationLogRetentionHoursEnv(t *testing.T) {
	t.Setenv("DRIVE9_QUOTA_MUTATION_LOG_RETENTION_HOURS", "")
	if got := mutationLogRetentionHours(); got != defaultMutationLogRetention {
		t.Fatalf("unset env: got %v, want %v", got, defaultMutationLogRetention)
	}
	t.Setenv("DRIVE9_QUOTA_MUTATION_LOG_RETENTION_HOURS", "72")
	if got := mutationLogRetentionHours(); got != 72*time.Hour {
		t.Fatalf("env=72: got %v, want 72h", got)
	}
	// A huge value must clamp, not overflow time.Duration into the future.
	t.Setenv("DRIVE9_QUOTA_MUTATION_LOG_RETENTION_HOURS", "9999999999")
	if got := mutationLogRetentionHours(); got != time.Duration(maxMutationLogRetentionHours)*time.Hour {
		t.Fatalf("env overflow: got %v, want clamp %v", got, time.Duration(maxMutationLogRetentionHours)*time.Hour)
	}
	for _, bad := range []string{"abc", "-3", "0"} {
		t.Setenv("DRIVE9_QUOTA_MUTATION_LOG_RETENTION_HOURS", bad)
		if got := mutationLogRetentionHours(); got != defaultMutationLogRetention {
			t.Fatalf("env=%q: got %v, want default %v", bad, got, defaultMutationLogRetention)
		}
	}
}

// TestEnvIntHours sanity-checks the helper's fallback behavior.
func TestEnvIntHours(t *testing.T) {
	const name = "DRIVE9_TEST_ENV_INT_HOURS"
	t.Setenv(name, "")
	if got := envIntHours(name, 7); got != 7 {
		t.Fatalf("unset: got %d, want 7", got)
	}
	t.Setenv(name, "9")
	if got := envIntHours(name, 7); got != 9 {
		t.Fatalf("set: got %d, want 9", got)
	}
	t.Setenv(name, "bogus")
	if got := envIntHours(name, 7); got != 7 {
		t.Fatalf("invalid: got %d, want 7", got)
	}
}
