package main

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"sync/atomic"
	"testing"
	"time"

	"github.com/mem9-ai/drive9/pkg/backend"
	"github.com/mem9-ai/drive9/pkg/embedding"
	"github.com/mem9-ai/drive9/pkg/meta"
	"github.com/mem9-ai/drive9/pkg/server"
	"github.com/mem9-ai/drive9/pkg/tenant"
)

func TestBuildEmbeddingProcessConfigFromEnvDefaultsDisabled(t *testing.T) {
	keys := []string{
		"DRIVE9_EMBED_ENABLED",
		"DRIVE9_EMBED_API_BASE",
		"DRIVE9_EMBED_API_KEY",
		"DRIVE9_EMBED_MODEL",
		"DRIVE9_EMBED_TIMEOUT_SECONDS",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)

	cfg, err := buildEmbeddingProcessConfigFromEnv(context.Background())
	if err != nil {
		t.Fatalf("buildEmbeddingProcessConfigFromEnv: %v", err)
	}
	if cfg.Runtime != nil || cfg.Provider != nil {
		t.Fatalf("disabled config = %+v, want no runtime or provider", cfg)
	}
}

func TestBuildEmbeddingProcessConfigFromEnvEnabledRequiresProvider(t *testing.T) {
	keys := []string{
		"DRIVE9_EMBED_ENABLED",
		"DRIVE9_EMBED_API_BASE",
		"DRIVE9_EMBED_API_KEY",
		"DRIVE9_EMBED_MODEL",
		"DRIVE9_EMBED_TIMEOUT_SECONDS",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)
	setEnv(t, "DRIVE9_EMBED_ENABLED", "true")

	if _, err := buildEmbeddingProcessConfigFromEnv(context.Background()); err == nil {
		t.Fatal("enabled embedding without provider succeeded")
	}
}

func TestBuildEmbeddingProcessConfigFromEnvProbesAndFingerprintsProvider(t *testing.T) {
	var calls atomic.Int32
	provider := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		calls.Add(1)
		if r.URL.Path != "/v1/embeddings" {
			t.Errorf("path=%q, want /v1/embeddings", r.URL.Path)
		}
		if got := r.Header.Get("Authorization"); got != "Bearer secret-a" && got != "Bearer secret-b" {
			t.Errorf("authorization=%q", got)
		}
		vec := make([]float32, embedding.Dimensions)
		vec[0] = 1
		_ = json.NewEncoder(w).Encode(map[string]any{"data": []any{map[string]any{"embedding": vec}}})
	}))
	defer provider.Close()

	keys := []string{
		"DRIVE9_EMBED_ENABLED",
		"DRIVE9_EMBED_API_BASE",
		"DRIVE9_EMBED_API_KEY",
		"DRIVE9_EMBED_MODEL",
		"DRIVE9_EMBED_TIMEOUT_SECONDS",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)
	setEnv(t, "DRIVE9_EMBED_ENABLED", "true")
	setEnv(t, "DRIVE9_EMBED_API_BASE", provider.URL)
	setEnv(t, "DRIVE9_EMBED_API_KEY", "secret-a")
	setEnv(t, "DRIVE9_EMBED_MODEL", "embed-model-a")

	first, err := buildEmbeddingProcessConfigFromEnv(context.Background())
	if err != nil {
		t.Fatalf("first config: %v", err)
	}
	second, err := buildEmbeddingProcessConfigFromEnv(context.Background())
	if err != nil {
		t.Fatalf("second config: %v", err)
	}
	if first.Runtime == nil || first.Runtime.Client == nil || first.Provider == nil {
		t.Fatalf("enabled config missing runtime/provider: %+v", first)
	}
	if first.Provider.APIBase != provider.URL || first.Provider.APIKey != "secret-a" || first.Provider.Model != "embed-model-a" {
		t.Fatalf("provider view=%+v", first.Provider)
	}
	if first.Runtime.Generation == 0 || first.Runtime.ConfigRevision == 0 {
		t.Fatalf("fingerprints=(%d,%d), want non-zero", first.Runtime.Generation, first.Runtime.ConfigRevision)
	}
	if first.Runtime.Generation != second.Runtime.Generation || first.Runtime.ConfigRevision != second.Runtime.ConfigRevision {
		t.Fatalf("same config fingerprints changed: first=(%d,%d) second=(%d,%d)", first.Runtime.Generation, first.Runtime.ConfigRevision, second.Runtime.Generation, second.Runtime.ConfigRevision)
	}

	setEnv(t, "DRIVE9_EMBED_API_KEY", "secret-b")
	keyChanged, err := buildEmbeddingProcessConfigFromEnv(context.Background())
	if err != nil {
		t.Fatalf("key-changed config: %v", err)
	}
	if keyChanged.Runtime.Generation != first.Runtime.Generation {
		t.Fatalf("key-only generation=%d, want %d", keyChanged.Runtime.Generation, first.Runtime.Generation)
	}
	if keyChanged.Runtime.ConfigRevision == first.Runtime.ConfigRevision {
		t.Fatal("key-only change did not change config revision")
	}

	setEnv(t, "DRIVE9_EMBED_MODEL", "embed-model-b")
	modelChanged, err := buildEmbeddingProcessConfigFromEnv(context.Background())
	if err != nil {
		t.Fatalf("model-changed config: %v", err)
	}
	if modelChanged.Runtime.Generation == keyChanged.Runtime.Generation || modelChanged.Runtime.ConfigRevision == keyChanged.Runtime.ConfigRevision {
		t.Fatalf("model change fingerprints did not both change: before=(%d,%d) after=(%d,%d)", keyChanged.Runtime.Generation, keyChanged.Runtime.ConfigRevision, modelChanged.Runtime.Generation, modelChanged.Runtime.ConfigRevision)
	}
	if calls.Load() != 4 {
		t.Fatalf("provider probe calls=%d, want 4", calls.Load())
	}
}

func TestBuildEmbeddingProcessConfigFromEnvRejectsInvalidTimeout(t *testing.T) {
	keys := []string{
		"DRIVE9_EMBED_ENABLED",
		"DRIVE9_EMBED_API_BASE",
		"DRIVE9_EMBED_API_KEY",
		"DRIVE9_EMBED_MODEL",
		"DRIVE9_EMBED_TIMEOUT_SECONDS",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)
	setEnv(t, "DRIVE9_EMBED_ENABLED", "true")
	setEnv(t, "DRIVE9_EMBED_API_BASE", "https://example.com")
	setEnv(t, "DRIVE9_EMBED_API_KEY", "secret")
	setEnv(t, "DRIVE9_EMBED_MODEL", "model")

	for _, raw := range []string{"0", "-1", "bad"} {
		setEnv(t, "DRIVE9_EMBED_TIMEOUT_SECONDS", raw)
		if _, err := buildEmbeddingProcessConfigFromEnv(context.Background()); err == nil {
			t.Fatalf("timeout %q succeeded", raw)
		}
	}
}

func TestBuildEmbeddingProcessConfigFromEnvSanitizesProviderFailure(t *testing.T) {
	const providerDetail = "provider-secret-response-detail"
	provider := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusUnauthorized)
		_, _ = w.Write([]byte(`{"error":{"message":"` + providerDetail + `"}}`))
	}))
	defer provider.Close()

	keys := []string{
		"DRIVE9_EMBED_ENABLED",
		"DRIVE9_EMBED_API_BASE",
		"DRIVE9_EMBED_API_KEY",
		"DRIVE9_EMBED_MODEL",
		"DRIVE9_EMBED_TIMEOUT_SECONDS",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)
	setEnv(t, "DRIVE9_EMBED_ENABLED", "true")
	setEnv(t, "DRIVE9_EMBED_API_BASE", provider.URL)
	setEnv(t, "DRIVE9_EMBED_API_KEY", "secret")
	setEnv(t, "DRIVE9_EMBED_MODEL", "model")

	_, err := buildEmbeddingProcessConfigFromEnv(context.Background())
	if err == nil {
		t.Fatal("invalid provider unexpectedly succeeded")
	}
	if strings.Contains(err.Error(), providerDetail) {
		t.Fatalf("provider detail leaked into startup error: %v", err)
	}
	if !strings.Contains(err.Error(), "category=auth_rejected") || !strings.Contains(err.Error(), "status=401") {
		t.Fatalf("startup error lacks bounded classification: %v", err)
	}
}

func TestVersionTextUsesDrive9ServerComponent(t *testing.T) {
	got := versionText()
	if !strings.Contains(got, "component: drive9-server\n") {
		t.Fatalf("versionText() missing drive9-server component line: %q", got)
	}
}

func TestTenantBackendCacheMaxTenantsUsesProviderDefault(t *testing.T) {
	const key = "DRIVE9_POOL_MAX_TENANTS"
	restore := snapshotEnv(t, []string{key})
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, []string{key})

	tests := []struct {
		provider string
		want     int
	}{
		{provider: tenant.ProviderTiDBCloudNativeShared, want: 20480},
		{provider: tenant.ProviderTiDBCloudNative, want: 1024},
		{provider: tenant.ProviderTiDBZero, want: 1024},
		{provider: tenant.ProviderDB9, want: 1024},
	}
	for _, tt := range tests {
		t.Run(tt.provider, func(t *testing.T) {
			if got := tenantBackendCacheMaxTenants(tt.provider); got != tt.want {
				t.Errorf("tenantBackendCacheMaxTenants(%q) = %d, want %d", tt.provider, got, tt.want)
			}
		})
	}
}

func TestTenantBackendCacheMaxTenantsExplicitOverrideWins(t *testing.T) {
	const key = "DRIVE9_POOL_MAX_TENANTS"
	restore := snapshotEnv(t, []string{key})
	t.Cleanup(func() { restoreEnv(t, restore) })
	setEnv(t, key, "4096")

	for _, provider := range []string{
		tenant.ProviderTiDBCloudNativeShared,
		tenant.ProviderTiDBCloudNative,
		tenant.ProviderTiDBZero,
		tenant.ProviderDB9,
	} {
		if got := tenantBackendCacheMaxTenants(provider); got != 4096 {
			t.Errorf("tenantBackendCacheMaxTenants(%q) = %d, want explicit 4096", provider, got)
		}
	}
}

func TestTenantBackendCacheMaxTenantsFallsBackForInvalidOverrides(t *testing.T) {
	const key = "DRIVE9_POOL_MAX_TENANTS"
	restore := snapshotEnv(t, []string{key})
	t.Cleanup(func() { restoreEnv(t, restore) })

	for _, raw := range []string{"0", "-1", "bad"} {
		setEnv(t, key, raw)
		if got := tenantBackendCacheMaxTenants(tenant.ProviderTiDBCloudNativeShared); got != 20480 {
			t.Errorf("tenantBackendCacheMaxTenants(shared) with %q = %d, want 20480", raw, got)
		}
		if got := tenantBackendCacheMaxTenants(tenant.ProviderTiDBCloudNative); got != 1024 {
			t.Errorf("tenantBackendCacheMaxTenants(native) with %q = %d, want 1024", raw, got)
		}
	}

	unsetEnv(t, []string{key})
	if got := tenantBackendCacheMaxTenants("unknown-provider"); got != 1024 {
		t.Errorf("tenantBackendCacheMaxTenants(unknown-provider) = %d, want 1024", got)
	}
}

func TestRestoreEnvPreservesPresentEmptyValue(t *testing.T) {
	const key = "DRIVE9_TEST_PRESENT_EMPTY_ENV"
	t.Setenv(key, "")
	snapshot := snapshotEnv(t, []string{key})
	unsetEnv(t, []string{key})

	restoreEnv(t, snapshot)
	value, present := os.LookupEnv(key)
	if !present || value != "" {
		t.Errorf("restored env = (%q, %t), want present empty value", value, present)
	}
}

func TestSlockOAuthFromEnvDisabledByDefault(t *testing.T) {
	keys := []string{
		"DRIVE9_SLOCK_ORIGIN",
		"DRIVE9_SLOCK_API_ORIGIN",
		"DRIVE9_SLOCK_CLIENT_ID",
		"DRIVE9_SLOCK_CLIENT_SECRET",
		"DRIVE9_PUBLIC_URL",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)

	c, err := slockOAuthFromEnv()
	if err != nil {
		t.Fatalf("slockOAuthFromEnv: %v", err)
	}
	if c != nil {
		t.Fatal("expected nil Slock client when DRIVE9_SLOCK_ORIGIN is unset")
	}
}

func TestSlockOAuthFromEnvRequiresCompanionVars(t *testing.T) {
	keys := []string{
		"DRIVE9_SLOCK_ORIGIN",
		"DRIVE9_SLOCK_API_ORIGIN",
		"DRIVE9_SLOCK_CLIENT_ID",
		"DRIVE9_SLOCK_CLIENT_SECRET",
		"DRIVE9_PUBLIC_URL",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)
	setEnv(t, "DRIVE9_SLOCK_ORIGIN", "https://app.slock.ai")

	if _, err := slockOAuthFromEnv(); err == nil {
		t.Fatal("expected partial Slock config to fail")
	}
}

func TestSlockOAuthFromEnvEnabled(t *testing.T) {
	keys := []string{
		"DRIVE9_SLOCK_ORIGIN",
		"DRIVE9_SLOCK_API_ORIGIN",
		"DRIVE9_SLOCK_CLIENT_ID",
		"DRIVE9_SLOCK_CLIENT_SECRET",
		"DRIVE9_PUBLIC_URL",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)
	setEnv(t, "DRIVE9_SLOCK_ORIGIN", "https://app.slock.ai")
	setEnv(t, "DRIVE9_SLOCK_API_ORIGIN", "https://api.slock.ai")
	setEnv(t, "DRIVE9_SLOCK_CLIENT_ID", "drive9")
	setEnv(t, "DRIVE9_SLOCK_CLIENT_SECRET", "secret")
	setEnv(t, "DRIVE9_PUBLIC_URL", "https://drive9.example.com")

	c, err := slockOAuthFromEnv()
	if err != nil {
		t.Fatalf("slockOAuthFromEnv: %v", err)
	}
	if c == nil || !strings.Contains(c.LoginURL(), "client_id=drive9") {
		t.Fatalf("unexpected Slock client: %#v", c)
	}
}

func TestTenantPoolMaxSizeFromEnv(t *testing.T) {
	const key = "DRIVE9_TENANT_POOL_MAX_SIZE"
	restore := snapshotEnv(t, []string{key})
	t.Cleanup(func() { restoreEnv(t, restore) })

	unsetEnv(t, []string{key})
	got, err := tenantPoolMaxSizeFromEnv()
	if err != nil {
		t.Fatalf("tenantPoolMaxSizeFromEnv empty: %v", err)
	}
	if got != server.DefaultTenantPoolMaxSize {
		t.Fatalf("empty max size = %d, want %d", got, server.DefaultTenantPoolMaxSize)
	}

	setEnv(t, key, "25")
	got, err = tenantPoolMaxSizeFromEnv()
	if err != nil {
		t.Fatalf("tenantPoolMaxSizeFromEnv valid: %v", err)
	}
	if got != 25 {
		t.Fatalf("max size = %d, want 25", got)
	}

	for _, raw := range []string{"0", "-1", "bad"} {
		setEnv(t, key, raw)
		if _, err := tenantPoolMaxSizeFromEnv(); err == nil {
			t.Fatalf("tenantPoolMaxSizeFromEnv(%q) error = nil, want error", raw)
		}
	}
}

func TestTenantPoolRefillFreeRatioFromEnv(t *testing.T) {
	const key = "DRIVE9_TENANT_POOL_REFILL_FREE_RATIO"
	restore := snapshotEnv(t, []string{key})
	t.Cleanup(func() { restoreEnv(t, restore) })

	unsetEnv(t, []string{key})
	got, err := tenantPoolRefillFreeRatioFromEnv()
	if err != nil {
		t.Fatalf("tenantPoolRefillFreeRatioFromEnv empty: %v", err)
	}
	if got != server.DefaultTenantPoolRefillFreeRatio {
		t.Fatalf("empty refill ratio = %f, want %f", got, server.DefaultTenantPoolRefillFreeRatio)
	}

	setEnv(t, key, "0.75")
	got, err = tenantPoolRefillFreeRatioFromEnv()
	if err != nil {
		t.Fatalf("tenantPoolRefillFreeRatioFromEnv valid: %v", err)
	}
	if got != 0.75 {
		t.Fatalf("refill ratio = %f, want 0.75", got)
	}

	for _, raw := range []string{"0", "-0.1", "1.1", "NaN", "bad"} {
		setEnv(t, key, raw)
		if _, err := tenantPoolRefillFreeRatioFromEnv(); err == nil {
			t.Fatalf("tenantPoolRefillFreeRatioFromEnv(%q) error = nil, want error", raw)
		}
	}
}

func TestSharedDBHardCapRatioFromEnv(t *testing.T) {
	const key = "DRIVE9_TIDBCLOUD_NATIVE_SHARED_HARD_CAP_RATIO"
	restore := snapshotEnv(t, []string{key})
	t.Cleanup(func() { restoreEnv(t, restore) })

	unsetEnv(t, []string{key})
	got, err := sharedDBHardCapRatioFromEnv()
	if err != nil {
		t.Fatalf("sharedDBHardCapRatioFromEnv empty: %v", err)
	}
	if got != server.DefaultSharedDBHardCapRatio {
		t.Fatalf("empty hard cap ratio = %f, want %f", got, server.DefaultSharedDBHardCapRatio)
	}

	setEnv(t, key, "1.1")
	got, err = sharedDBHardCapRatioFromEnv()
	if err != nil {
		t.Fatalf("sharedDBHardCapRatioFromEnv valid: %v", err)
	}
	if got != 1.1 {
		t.Fatalf("hard cap ratio = %f, want 1.1", got)
	}

	for _, raw := range []string{"0.9", "1", "NaN", "+Inf", "bad"} {
		setEnv(t, key, raw)
		if _, err := sharedDBHardCapRatioFromEnv(); err == nil {
			t.Fatalf("sharedDBHardCapRatioFromEnv(%q) error = nil, want error", raw)
		}
	}
}

func TestSharedDBReopenRatioFromEnv(t *testing.T) {
	const key = "DRIVE9_TIDBCLOUD_NATIVE_SHARED_REOPEN_RATIO"
	restore := snapshotEnv(t, []string{key})
	t.Cleanup(func() { restoreEnv(t, restore) })

	unsetEnv(t, []string{key})
	got, err := sharedDBReopenRatioFromEnv()
	if err != nil {
		t.Fatalf("sharedDBReopenRatioFromEnv empty: %v", err)
	}
	if got != server.DefaultSharedDBReopenRatio {
		t.Fatalf("empty reopen ratio = %f, want %f", got, server.DefaultSharedDBReopenRatio)
	}

	setEnv(t, key, "0.65")
	got, err = sharedDBReopenRatioFromEnv()
	if err != nil {
		t.Fatalf("sharedDBReopenRatioFromEnv valid: %v", err)
	}
	if got != 0.65 {
		t.Fatalf("reopen ratio = %f, want 0.65", got)
	}

	for _, raw := range []string{"0", "-0.1", "1", "NaN", "+Inf", "bad"} {
		setEnv(t, key, raw)
		if _, err := sharedDBReopenRatioFromEnv(); err == nil {
			t.Fatalf("sharedDBReopenRatioFromEnv(%q) error = nil, want error", raw)
		}
	}
}

func TestSharedDBCapacityConfigFromEnv(t *testing.T) {
	keys := []string{
		"DRIVE9_TIDBCLOUD_NATIVE_SHARED_MAX_TENANTS",
		"DRIVE9_TIDBCLOUD_NATIVE_DB_POOL_DEFAULT_SPENDING_LIMIT",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })

	unsetEnv(t, keys)
	maxTenants, err := sharedDBMaxTenantsFromEnv()
	if err != nil {
		t.Fatalf("sharedDBMaxTenantsFromEnv empty: %v", err)
	}
	if maxTenants != 100 {
		t.Fatalf("empty max tenants = %d, want 100", maxTenants)
	}
	spendingLimit, err := sharedDBDefaultSpendingLimitFromEnv()
	if err != nil {
		t.Fatalf("sharedDBDefaultSpendingLimitFromEnv empty: %v", err)
	}
	if spendingLimit != server.DefaultManagedSharedDBSpendingLimit {
		t.Fatalf("empty spending limit = %d, want %d", spendingLimit, server.DefaultManagedSharedDBSpendingLimit)
	}
	setEnv(t, keys[0], "250")
	maxTenants, err = sharedDBMaxTenantsFromEnv()
	if err != nil || maxTenants != 250 {
		t.Fatalf("valid max tenants = %d/%v, want 250/nil", maxTenants, err)
	}
	setEnv(t, keys[1], "2000000")
	spendingLimit, err = sharedDBDefaultSpendingLimitFromEnv()
	if err != nil || spendingLimit != 2_000_000 {
		t.Fatalf("valid spending limit = %d/%v, want 2000000/nil", spendingLimit, err)
	}
	for _, raw := range []string{"0", "-1", "bad"} {
		setEnv(t, keys[0], raw)
		if _, err := sharedDBMaxTenantsFromEnv(); err == nil {
			t.Fatalf("max tenants %q error = nil, want error", raw)
		}
	}
	for _, raw := range []string{"0", "-1", "2147483648", "bad"} {
		setEnv(t, keys[1], raw)
		if _, err := sharedDBDefaultSpendingLimitFromEnv(); err == nil {
			t.Fatalf("spending limit %q error = nil, want error", raw)
		}
	}
}

func TestTiDBCloudNonFreePlanCacheTTLFromEnv(t *testing.T) {
	const key = "DRIVE9_TIDBCLOUD_NON_FREE_PLAN_CACHE_TTL"
	restore := snapshotEnv(t, []string{key})
	t.Cleanup(func() { restoreEnv(t, restore) })

	unsetEnv(t, []string{key})
	got, err := tiDBCloudNonFreePlanCacheTTLFromEnv()
	if err != nil || got != 15*time.Minute {
		t.Fatalf("default cache TTL = %v/%v, want 15m/nil", got, err)
	}
	setEnv(t, key, "45m")
	got, err = tiDBCloudNonFreePlanCacheTTLFromEnv()
	if err != nil || got != 45*time.Minute {
		t.Fatalf("configured cache TTL = %v/%v, want 45m/nil", got, err)
	}
	for _, raw := range []string{"0", "-1s", "bad"} {
		setEnv(t, key, raw)
		if _, err := tiDBCloudNonFreePlanCacheTTLFromEnv(); err == nil {
			t.Fatalf("cache TTL %q error = nil, want error", raw)
		}
	}
}

func TestTiDBCloudIAMCacheTTLFromEnv(t *testing.T) {
	const key = "DRIVE9_TIDBCLOUD_IAM_CACHE_TTL"
	restore := snapshotEnv(t, []string{key})
	t.Cleanup(func() { restoreEnv(t, restore) })

	unsetEnv(t, []string{key})
	got, err := tiDBCloudIAMCacheTTLFromEnv()
	if err != nil || got != 30*time.Minute {
		t.Fatalf("default IAM cache TTL = %v/%v, want 30m/nil", got, err)
	}
	setEnv(t, key, "  ")
	got, err = tiDBCloudIAMCacheTTLFromEnv()
	if err != nil || got != 30*time.Minute {
		t.Fatalf("whitespace IAM cache TTL = %v/%v, want 30m/nil", got, err)
	}
	setEnv(t, key, "15m")
	got, err = tiDBCloudIAMCacheTTLFromEnv()
	if err != nil || got != 15*time.Minute {
		t.Fatalf("configured IAM cache TTL = %v/%v, want 15m/nil", got, err)
	}
	for _, raw := range []string{"0", "-1s", "bad"} {
		setEnv(t, key, raw)
		if _, err := tiDBCloudIAMCacheTTLFromEnv(); err == nil {
			t.Fatalf("IAM cache TTL %q error = nil, want error", raw)
		}
	}
}

func TestPositiveDurationEnv(t *testing.T) {
	const key = "DRIVE9_TEST_POSITIVE_DURATION"

	t.Run("uses fallback for whitespace", func(t *testing.T) {
		t.Setenv(key, "  ")
		got, err := positiveDurationEnv(key, 5*time.Minute)
		if err != nil || got != 5*time.Minute {
			t.Fatalf("duration = %v/%v, want 5m/nil", got, err)
		}
	})
	t.Run("accepts positive duration", func(t *testing.T) {
		t.Setenv(key, "15s")
		got, err := positiveDurationEnv(key, 5*time.Minute)
		if err != nil || got != 15*time.Second {
			t.Fatalf("duration = %v/%v, want 15s/nil", got, err)
		}
	})
	t.Run("rejects zero when positive required", func(t *testing.T) {
		t.Setenv(key, "0")
		if _, err := positiveDurationEnv(key, 5*time.Minute); err == nil {
			t.Fatal("zero duration error = nil, want error")
		}
	})
	for _, raw := range []string{"-1s", "bad"} {
		t.Run("rejects "+raw, func(t *testing.T) {
			t.Setenv(key, raw)
			if _, err := positiveDurationEnv(key, 5*time.Minute); err == nil {
				t.Fatalf("duration %q error = nil, want error", raw)
			}
		})
	}
}

func TestEnvDurationAllowsZeroAndFallsBackForInvalidValues(t *testing.T) {
	const key = "DRIVE9_TEST_DURATION"
	fallback := 5 * time.Minute

	t.Setenv(key, "0")
	if got := envDuration(key, fallback); got != 0 {
		t.Fatalf("zero duration = %v, want 0", got)
	}

	for _, raw := range []string{"-1s", "bad"} {
		t.Run("fallback for "+raw, func(t *testing.T) {
			t.Setenv(key, raw)
			if got := envDuration(key, fallback); got != fallback {
				t.Fatalf("duration %q = %v, want %v", raw, got, fallback)
			}
		})
	}
}

func TestDeleteReconcileIntervalFromEnvFallsBackToEffectiveDefault(t *testing.T) {
	t.Setenv("DRIVE9_DELETE_RECONCILE_INTERVAL", "bad")

	got, configured := deleteReconcileIntervalFromEnv()
	if !configured {
		t.Fatal("configured = false, want true")
	}
	if got != server.DefaultDeleteReconcileInterval {
		t.Fatalf("interval = %v, want %v", got, server.DefaultDeleteReconcileInterval)
	}
}

func TestTiDBCloudFreePlanLimitsFromEnv(t *testing.T) {
	keys := []string{
		"DRIVE9_TIDBCLOUD_FREE_TENANT_COUNT",
		"DRIVE9_TIDBCLOUD_FREE_MAX_STORAGE_BYTES",
		"DRIVE9_TIDBCLOUD_FREE_MAX_FILE_SIZE_BYTES",
		"DRIVE9_TIDBCLOUD_FREE_MAX_FILE_COUNT",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })

	unsetEnv(t, keys)
	got, err := tiDBCloudFreePlanLimitsFromEnv(server.DefaultMaxUploadBytes)
	if err != nil {
		t.Fatalf("default free limits: %v", err)
	}
	want := server.TiDBCloudFreePlanLimits{
		TenantCount: 1, MaxStorageBytes: 2147483648,
		MaxFileSizeBytes: 524288000, MaxFileCount: 2000,
	}
	if got != want {
		t.Fatalf("default free limits = %+v, want %+v", got, want)
	}

	got, err = tiDBCloudFreePlanLimitsFromEnv(1 << 20)
	if err != nil {
		t.Fatalf("default free max file size with smaller upload cap: %v", err)
	}
	if got.MaxFileSizeBytes != 1<<20 {
		t.Fatalf("default free max file size = %d, want upload cap %d", got.MaxFileSizeBytes, 1<<20)
	}

	setEnv(t, keys[0], "5")
	setEnv(t, keys[1], "4096")
	setEnv(t, keys[2], "1024")
	setEnv(t, keys[3], "2000")
	got, err = tiDBCloudFreePlanLimitsFromEnv(server.DefaultMaxUploadBytes)
	if err != nil || got.TenantCount != 5 || got.MaxStorageBytes != 4096 || got.MaxFileSizeBytes != 1024 || got.MaxFileCount != 2000 {
		t.Fatalf("configured free limits = %+v/%v", got, err)
	}

	for i, key := range keys {
		unsetEnv(t, keys)
		setEnv(t, key, "0")
		if _, err := tiDBCloudFreePlanLimitsFromEnv(server.DefaultMaxUploadBytes); err == nil {
			t.Fatalf("zero %s (index %d) error = nil", key, i)
		}
	}
	unsetEnv(t, keys)
	setEnv(t, keys[2], "1048577")
	if _, err := tiDBCloudFreePlanLimitsFromEnv(1048576); err == nil {
		t.Fatal("free max file size above upload limit error = nil")
	}
}

func TestGlobalMaxTenantFileCountFromEnv(t *testing.T) {
	const key = "DRIVE9_MAX_TENANT_FILE_COUNT"
	restore := snapshotEnv(t, []string{key})
	t.Cleanup(func() { restoreEnv(t, restore) })

	unsetEnv(t, []string{key})
	if got, err := maxTenantFileCountFromEnv(); err != nil || got != 0 {
		t.Fatalf("default max file count = %d/%v, want 0/nil", got, err)
	}
	setEnv(t, key, "2500")
	if got, err := maxTenantFileCountFromEnv(); err != nil || got != 2500 {
		t.Fatalf("configured max file count = %d/%v, want 2500/nil", got, err)
	}
	for _, raw := range []string{"-1", "bad"} {
		setEnv(t, key, raw)
		if _, err := maxTenantFileCountFromEnv(); err == nil {
			t.Fatalf("max file count %q error = nil", raw)
		}
	}
}

func TestDBHealthProbeOptionsFromEnvDefaults(t *testing.T) {
	keys := []string{
		"DRIVE9_DB_HEALTH_PROBE_META_ENABLED",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)

	opts := dbHealthProbeOptionsFromEnv()
	if !opts.ProbeMeta {
		t.Fatal("ProbeMeta=false, want true")
	}
}

func TestDBHealthProbeOptionsFromEnvOverrides(t *testing.T) {
	keys := []string{
		"DRIVE9_DB_HEALTH_PROBE_META_ENABLED",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)

	setEnv(t, "DRIVE9_DB_HEALTH_PROBE_META_ENABLED", "false")

	opts := dbHealthProbeOptionsFromEnv()
	if opts.ProbeMeta {
		t.Fatal("ProbeMeta=true, want false")
	}
}

func TestBuildBackendOptionsFromEnvAudioDisabled(t *testing.T) {
	keys := []string{
		"DRIVE9_IMAGE_EXTRACT_ENABLED",
		"DRIVE9_AUDIO_EXTRACT_ENABLED",
		"DRIVE9_AUDIO_EXTRACT_MODE",
		"DRIVE9_AUDIO_EXTRACT_API_BASE",
		"DRIVE9_AUDIO_EXTRACT_API_KEY",
		"DRIVE9_AUDIO_EXTRACT_MODEL",
		"DRIVE9_AUDIO_EXTRACT_PROMPT",
		"DRIVE9_AUDIO_EXTRACT_TIMEOUT_SECONDS",
		"DRIVE9_AUDIO_EXTRACT_MAX_BYTES",
		"DRIVE9_AUDIO_EXTRACT_MAX_TEXT_BYTES",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)

	opts, _, err := buildBackendOptionsFromEnv()
	if err != nil {
		t.Fatalf("buildBackendOptionsFromEnv: %v", err)
	}
	if backend.AsyncAudioExtractWillWireRuntime(opts.AsyncAudioExtract) {
		t.Fatalf("expected audio runtime disabled, got %+v", opts.AsyncAudioExtract)
	}
}

func TestObjectGCConfigFromEnv(t *testing.T) {
	const (
		graceKey = "DRIVE9_OBJECT_GC_GRACE_PERIOD"
		batchKey = "DRIVE9_OBJECT_GC_BATCH_SIZE"
	)
	restore := snapshotEnv(t, []string{graceKey, batchKey})
	t.Cleanup(func() { restoreEnv(t, restore) })

	unsetEnv(t, []string{graceKey, batchKey})
	opts, _, err := buildBackendOptionsFromEnv()
	if err != nil {
		t.Fatalf("buildBackendOptionsFromEnv defaults: %v", err)
	}
	if opts.ObjectGCGracePeriod != 24*time.Hour {
		t.Fatalf("default object GC grace period = %s, want 24h", opts.ObjectGCGracePeriod)
	}
	if got := objectGCBatchSizeFromEnv(); got != 500 {
		t.Fatalf("default object GC batch size = %d, want 500", got)
	}

	setEnv(t, graceKey, "45m")
	setEnv(t, batchKey, "750")
	opts, _, err = buildBackendOptionsFromEnv()
	if err != nil {
		t.Fatalf("buildBackendOptionsFromEnv overrides: %v", err)
	}
	if opts.ObjectGCGracePeriod != 45*time.Minute {
		t.Fatalf("object GC grace period = %s, want 45m", opts.ObjectGCGracePeriod)
	}
	if got := objectGCBatchSizeFromEnv(); got != 750 {
		t.Fatalf("object GC batch size = %d, want 750", got)
	}

	setEnv(t, batchKey, "5000")
	if got := objectGCBatchSizeFromEnv(); got != 5000 {
		t.Fatalf("maximum object GC batch size = %d, want 5000", got)
	}

	for _, raw := range []string{"0", "-1", "bad"} {
		setEnv(t, graceKey, raw)
		setEnv(t, batchKey, raw)
		if _, _, err = buildBackendOptionsFromEnv(); err == nil {
			t.Fatalf("buildBackendOptionsFromEnv invalid %q succeeded", raw)
		}
		setEnv(t, graceKey, "24h")
		if got := objectGCBatchSizeFromEnv(); got != 500 {
			t.Fatalf("object GC batch size for %q = %d, want 500", raw, got)
		}
	}

	setEnv(t, batchKey, "5001")
	if got := objectGCBatchSizeFromEnv(); got != 500 {
		t.Fatalf("oversized object GC batch size = %d, want fallback 500", got)
	}
}

func TestBuildBackendOptionsFromEnvMediaWorkspaceConcurrency(t *testing.T) {
	const key = "DRIVE9_EXTRACT_MAX_CONCURRENT_TASKS"
	restore := snapshotEnv(t, []string{key})
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, []string{key})

	opts, _, err := buildBackendOptionsFromEnv()
	if err != nil {
		t.Fatalf("buildBackendOptionsFromEnv default: %v", err)
	}
	if opts.ExtractMaxConcurrentTasks != 4 {
		t.Fatalf("default extract max concurrent tasks=%d, want 4", opts.ExtractMaxConcurrentTasks)
	}

	setEnv(t, key, "7")
	opts, _, err = buildBackendOptionsFromEnv()
	if err != nil {
		t.Fatalf("buildBackendOptionsFromEnv override: %v", err)
	}
	if opts.ExtractMaxConcurrentTasks != 7 {
		t.Fatalf("extract max concurrent tasks=%d, want 7", opts.ExtractMaxConcurrentTasks)
	}

	for _, raw := range []string{"0", "-1", "bad"} {
		setEnv(t, key, raw)
		if _, _, err := buildBackendOptionsFromEnv(); err == nil {
			t.Fatalf("buildBackendOptionsFromEnv accepted %q", raw)
		}
	}
}

func TestBuildMediaExtractOptionsKeepProcessLimitsWhenProvidersAreOff(t *testing.T) {
	keys := []string{
		"DRIVE9_IMAGE_EXTRACT_ENABLED",
		"DRIVE9_IMAGE_EXTRACT_MAX_SOURCE_BYTES",
		"DRIVE9_AUDIO_EXTRACT_ENABLED",
		"DRIVE9_AUDIO_EXTRACT_MAX_SOURCE_BYTES",
		"DRIVE9_VIDEO_EXTRACT_TENANT_ALLOWLIST",
		"DRIVE9_VIDEO_EXTRACT_MAX_SOURCE_BYTES",
		"DRIVE9_VIDEO_EXTRACT_MAX_PAYLOAD_BYTES",
		"DRIVE9_VIDEO_EXTRACT_TIMEOUT_SECONDS",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)
	setEnv(t, "DRIVE9_IMAGE_EXTRACT_MAX_SOURCE_BYTES", "1073741824")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_MAX_SOURCE_BYTES", "5368709120")
	setEnv(t, "DRIVE9_VIDEO_EXTRACT_TENANT_ALLOWLIST", "")
	setEnv(t, "DRIVE9_VIDEO_EXTRACT_MAX_SOURCE_BYTES", "10737418240")
	setEnv(t, "DRIVE9_VIDEO_EXTRACT_MAX_PAYLOAD_BYTES", "9437184")
	setEnv(t, "DRIVE9_VIDEO_EXTRACT_TIMEOUT_SECONDS", "420")

	opts, _, err := buildBackendOptionsFromEnv()
	if err != nil {
		t.Fatal(err)
	}
	if opts.AsyncImageExtract.Enabled || opts.AsyncAudioExtract.Enabled || opts.AsyncVideoExtract.Enabled {
		t.Fatal("video default provider enabled, want off")
	}
	if opts.AsyncImageExtract.MaxSourceBytes != 1<<30 {
		t.Fatalf("image MaxSourceBytes=%d, want 1 GiB", opts.AsyncImageExtract.MaxSourceBytes)
	}
	if opts.AsyncAudioExtract.MaxSourceBytes != 5<<30 {
		t.Fatalf("audio MaxSourceBytes=%d, want 5 GiB", opts.AsyncAudioExtract.MaxSourceBytes)
	}
	if opts.AsyncVideoExtract.MaxSourceBytes != 10<<30 {
		t.Fatalf("video MaxSourceBytes=%d, want 10 GiB", opts.AsyncVideoExtract.MaxSourceBytes)
	}
	if opts.AsyncVideoExtract.MaxProviderPayloadBytes != 9<<20 {
		t.Fatalf("video MaxProviderPayloadBytes=%d, want 9 MiB", opts.AsyncVideoExtract.MaxProviderPayloadBytes)
	}
	if opts.AsyncVideoExtract.TaskTimeout != 7*time.Minute {
		t.Fatalf("video TaskTimeout=%v, want 7m", opts.AsyncVideoExtract.TaskTimeout)
	}
}

func TestBuildMediaExtractOptionsRejectNonPositiveProcessLimits(t *testing.T) {
	keys := []string{
		"DRIVE9_IMAGE_EXTRACT_ENABLED",
		"DRIVE9_AUDIO_EXTRACT_ENABLED",
		"DRIVE9_VIDEO_EXTRACT_TENANT_ALLOWLIST",
		"DRIVE9_IMAGE_EXTRACT_MAX_SOURCE_BYTES",
		"DRIVE9_AUDIO_EXTRACT_MAX_SOURCE_BYTES",
		"DRIVE9_VIDEO_EXTRACT_MAX_SOURCE_BYTES",
		"DRIVE9_VIDEO_EXTRACT_MAX_PAYLOAD_BYTES",
	}
	for _, key := range keys[3:] {
		t.Run(key, func(t *testing.T) {
			restore := snapshotEnv(t, keys)
			t.Cleanup(func() { restoreEnv(t, restore) })
			unsetEnv(t, keys)
			setEnv(t, key, "0")
			if _, _, err := buildBackendOptionsFromEnv(); err == nil {
				t.Fatalf("buildBackendOptionsFromEnv accepted %s=0", key)
			}
		})
	}
}

func TestBuildVideoExtractOptionsUsesLegacySourceLimitAlias(t *testing.T) {
	keys := []string{
		"DRIVE9_VIDEO_EXTRACT_TENANT_ALLOWLIST",
		"DRIVE9_VIDEO_EXTRACT_MAX_SOURCE_BYTES",
		"DRIVE9_VIDEO_EXTRACT_MAX_BYTES",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)
	setEnv(t, "DRIVE9_VIDEO_EXTRACT_MAX_BYTES", "314572800")

	opts, err := buildVideoExtractOptionsFromEnv()
	if err != nil {
		t.Fatal(err)
	}
	if opts.MaxSourceBytes != 300<<20 {
		t.Fatalf("MaxSourceBytes=%d, want legacy 300 MiB alias", opts.MaxSourceBytes)
	}

	setEnv(t, "DRIVE9_VIDEO_EXTRACT_MAX_SOURCE_BYTES", "419430400")
	opts, err = buildVideoExtractOptionsFromEnv()
	if err != nil {
		t.Fatal(err)
	}
	if opts.MaxSourceBytes != 400<<20 {
		t.Fatalf("MaxSourceBytes=%d, want new 400 MiB setting to win", opts.MaxSourceBytes)
	}
}

func TestTenantWorkerOptionsFromEnvDoesNotRequireDefaultEmbedder(t *testing.T) {
	keys := []string{
		"DRIVE9_EMBED_API_BASE",
		"DRIVE9_EMBED_API_KEY",
		"DRIVE9_EMBED_MODEL",
		"DRIVE9_SEMANTIC_WORKERS",
		"DRIVE9_SEMANTIC_PER_TENANT_CONCURRENCY",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)
	setEnv(t, "DRIVE9_SEMANTIC_WORKERS", "8")
	setEnv(t, "DRIVE9_SEMANTIC_PER_TENANT_CONCURRENCY", "4")

	opts := tenantWorkerOptionsFromEnv()
	if opts.Workers != 8 {
		t.Fatalf("Workers=%d, want 8", opts.Workers)
	}
	if opts.PerTenantConcurrency != 4 {
		t.Fatalf("PerTenantConcurrency=%d, want 4", opts.PerTenantConcurrency)
	}
}

func TestBuildBackendOptionsFromEnvAudioMissingRequiredConfig(t *testing.T) {
	keys := []string{
		"DRIVE9_IMAGE_EXTRACT_ENABLED",
		"DRIVE9_AUDIO_EXTRACT_ENABLED",
		"DRIVE9_AUDIO_EXTRACT_MODE",
		"DRIVE9_AUDIO_EXTRACT_API_BASE",
		"DRIVE9_AUDIO_EXTRACT_API_KEY",
		"DRIVE9_AUDIO_EXTRACT_MODEL",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)

	if err := os.Setenv("DRIVE9_AUDIO_EXTRACT_ENABLED", "true"); err != nil {
		t.Fatal(err)
	}
	if err := os.Setenv("DRIVE9_AUDIO_EXTRACT_API_BASE", "https://example.com"); err != nil {
		t.Fatal(err)
	}
	if err := os.Setenv("DRIVE9_AUDIO_EXTRACT_API_KEY", "secret"); err != nil {
		t.Fatal(err)
	}
	if _, _, err := buildBackendOptionsFromEnv(); err == nil {
		t.Fatal("expected missing model config to fail")
	}
}

func TestBuildBackendOptionsFromEnvAudioOpenAI(t *testing.T) {
	keys := []string{
		"DRIVE9_IMAGE_EXTRACT_ENABLED",
		"DRIVE9_AUDIO_EXTRACT_ENABLED",
		"DRIVE9_AUDIO_EXTRACT_MODE",
		"DRIVE9_AUDIO_EXTRACT_API_BASE",
		"DRIVE9_AUDIO_EXTRACT_API_KEY",
		"DRIVE9_AUDIO_EXTRACT_MODEL",
		"DRIVE9_AUDIO_EXTRACT_PROMPT",
		"DRIVE9_AUDIO_EXTRACT_TIMEOUT_SECONDS",
		"DRIVE9_AUDIO_EXTRACT_MAX_BYTES",
		"DRIVE9_AUDIO_EXTRACT_MAX_TEXT_BYTES",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)

	setEnv(t, "DRIVE9_AUDIO_EXTRACT_ENABLED", "true")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_API_BASE", "https://example.com/v1")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_API_KEY", "secret")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_MODEL", "whisper-1")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_PROMPT", "transcribe in zh")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_TIMEOUT_SECONDS", "45")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_MAX_BYTES", "1234")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_MAX_TEXT_BYTES", "5678")

	opts, _, err := buildBackendOptionsFromEnv()
	if err != nil {
		t.Fatalf("buildBackendOptionsFromEnv: %v", err)
	}
	if !backend.AsyncAudioExtractWillWireRuntime(opts.AsyncAudioExtract) {
		t.Fatalf("expected audio runtime wired, got %+v", opts.AsyncAudioExtract)
	}
	if opts.AsyncAudioExtract.MaxAudioBytes != 1234 {
		t.Fatalf("MaxAudioBytes=%d, want 1234", opts.AsyncAudioExtract.MaxAudioBytes)
	}
	if opts.AsyncAudioExtract.MaxExtractTextBytes != 5678 {
		t.Fatalf("MaxExtractTextBytes=%d, want 5678", opts.AsyncAudioExtract.MaxExtractTextBytes)
	}
	if got := opts.AsyncAudioExtract.TaskTimeout.Seconds(); got != 45 {
		t.Fatalf("TaskTimeout=%v, want 45s", opts.AsyncAudioExtract.TaskTimeout)
	}
}

func TestBuildBackendOptionsFromEnvAudioQwenASR(t *testing.T) {
	keys := []string{
		"DRIVE9_IMAGE_EXTRACT_ENABLED",
		"DRIVE9_AUDIO_EXTRACT_ENABLED",
		"DRIVE9_AUDIO_EXTRACT_MODE",
		"DRIVE9_AUDIO_EXTRACT_API_BASE",
		"DRIVE9_AUDIO_EXTRACT_API_KEY",
		"DRIVE9_AUDIO_EXTRACT_MODEL",
		"DRIVE9_AUDIO_EXTRACT_PROMPT",
		"DRIVE9_AUDIO_EXTRACT_TIMEOUT_SECONDS",
		"DRIVE9_AUDIO_EXTRACT_MAX_BYTES",
		"DRIVE9_AUDIO_EXTRACT_MAX_TEXT_BYTES",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)

	setEnv(t, "DRIVE9_AUDIO_EXTRACT_ENABLED", "true")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_MODE", "qwen-asr")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_API_BASE", "https://dashscope.aliyuncs.com/compatible-mode/v1")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_API_KEY", "secret")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_MODEL", "qwen3-asr-flash")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_TIMEOUT_SECONDS", "45")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_MAX_BYTES", "1234")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_MAX_TEXT_BYTES", "5678")

	opts, _, err := buildBackendOptionsFromEnv()
	if err != nil {
		t.Fatalf("buildBackendOptionsFromEnv: %v", err)
	}
	if !backend.AsyncAudioExtractWillWireRuntime(opts.AsyncAudioExtract) {
		t.Fatalf("expected audio runtime wired, got %+v", opts.AsyncAudioExtract)
	}
	if opts.AsyncAudioExtract.MaxAudioBytes != 1234 {
		t.Fatalf("MaxAudioBytes=%d, want 1234", opts.AsyncAudioExtract.MaxAudioBytes)
	}
	if opts.AsyncAudioExtract.MaxExtractTextBytes != 5678 {
		t.Fatalf("MaxExtractTextBytes=%d, want 5678", opts.AsyncAudioExtract.MaxExtractTextBytes)
	}
	if got := opts.AsyncAudioExtract.TaskTimeout.Seconds(); got != 45 {
		t.Fatalf("TaskTimeout=%v, want 45s", opts.AsyncAudioExtract.TaskTimeout)
	}
}

func TestBuildAudioExtractOptionsFromEnvOpenAIDefaultMaxBytes(t *testing.T) {
	keys := []string{
		"DRIVE9_AUDIO_EXTRACT_ENABLED",
		"DRIVE9_AUDIO_EXTRACT_MODE",
		"DRIVE9_AUDIO_EXTRACT_API_BASE",
		"DRIVE9_AUDIO_EXTRACT_API_KEY",
		"DRIVE9_AUDIO_EXTRACT_MODEL",
		"DRIVE9_AUDIO_EXTRACT_PROMPT",
		"DRIVE9_AUDIO_EXTRACT_RESPONSE_FORMAT",
		"DRIVE9_AUDIO_EXTRACT_TIMEOUT_SECONDS",
		"DRIVE9_AUDIO_EXTRACT_MAX_BYTES",
		"DRIVE9_AUDIO_EXTRACT_MAX_TEXT_BYTES",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)

	setEnv(t, "DRIVE9_AUDIO_EXTRACT_ENABLED", "true")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_MODE", "openai")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_API_BASE", "https://example.com/v1")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_API_KEY", "secret")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_MODEL", "whisper-1")

	opts, err := buildAudioExtractOptionsFromEnv()
	if err != nil {
		t.Fatalf("buildAudioExtractOptionsFromEnv: %v", err)
	}
	if opts.MaxAudioBytes != 33554432 {
		t.Fatalf("MaxAudioBytes=%d, want 33554432", opts.MaxAudioBytes)
	}
	if opts.Protocol != backend.MediaProviderProtocolOpenAI {
		t.Fatalf("Protocol=%q, want %q", opts.Protocol, backend.MediaProviderProtocolOpenAI)
	}
}

func TestBuildAudioExtractOptionsFromEnvQwenASRDefaultMaxBytes(t *testing.T) {
	keys := []string{
		"DRIVE9_AUDIO_EXTRACT_ENABLED",
		"DRIVE9_AUDIO_EXTRACT_MODE",
		"DRIVE9_AUDIO_EXTRACT_API_BASE",
		"DRIVE9_AUDIO_EXTRACT_API_KEY",
		"DRIVE9_AUDIO_EXTRACT_MODEL",
		"DRIVE9_AUDIO_EXTRACT_PROMPT",
		"DRIVE9_AUDIO_EXTRACT_TIMEOUT_SECONDS",
		"DRIVE9_AUDIO_EXTRACT_MAX_BYTES",
		"DRIVE9_AUDIO_EXTRACT_MAX_TEXT_BYTES",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)

	setEnv(t, "DRIVE9_AUDIO_EXTRACT_ENABLED", "true")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_MODE", "qwen-asr")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_API_BASE", "https://dashscope.aliyuncs.com/compatible-mode/v1")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_API_KEY", "secret")
	setEnv(t, "DRIVE9_AUDIO_EXTRACT_MODEL", "qwen3-asr-flash")

	opts, err := buildAudioExtractOptionsFromEnv()
	if err != nil {
		t.Fatalf("buildAudioExtractOptionsFromEnv: %v", err)
	}
	if opts.MaxAudioBytes != 7340032 {
		t.Fatalf("MaxAudioBytes=%d, want 7340032", opts.MaxAudioBytes)
	}
	if opts.Protocol != backend.MediaProviderProtocolQwenASR {
		t.Fatalf("Protocol=%q, want %q", opts.Protocol, backend.MediaProviderProtocolQwenASR)
	}
}

func TestBuildAudioExtractOptionsFromEnvStubRequiresLocalProvider(t *testing.T) {
	t.Setenv("DRIVE9_AUDIO_EXTRACT_ENABLED", "true")
	t.Setenv("DRIVE9_AUDIO_EXTRACT_MODE", "stub")
	t.Setenv("DRIVE9_TENANT_PROVIDER", tenant.ProviderTiDBCloudNative)
	_, err := buildAudioExtractOptionsFromEnv()
	if err == nil || !strings.Contains(err.Error(), "DRIVE9_TENANT_PROVIDER=local") {
		t.Fatalf("error = %v, want stub rejected without local provider", err)
	}
}

func TestBuildAudioExtractOptionsFromEnvStubAllowedForLocalProvider(t *testing.T) {
	t.Setenv("DRIVE9_AUDIO_EXTRACT_ENABLED", "true")
	t.Setenv("DRIVE9_AUDIO_EXTRACT_MODE", "stub")
	t.Setenv("DRIVE9_TENANT_PROVIDER", tenant.ProviderLocal)
	opts, err := buildAudioExtractOptionsFromEnv()
	if err != nil {
		t.Fatalf("buildAudioExtractOptionsFromEnv: %v", err)
	}
	if !opts.Enabled {
		t.Fatal("expected stub audio extract enabled")
	}
	if _, ok := opts.Extractor.(stubAudioTextExtractor); !ok {
		t.Fatalf("extractor = %T, want stubAudioTextExtractor", opts.Extractor)
	}
}

func TestS3ConfigFromEnv(t *testing.T) {
	keys := []string{
		"DRIVE9_S3_DIR",
		"DRIVE9_S3_BUCKET",
		"DRIVE9_S3_REGION",
		"DRIVE9_S3_PREFIX",
		"DRIVE9_S3_ROLE_ARN",
		"DRIVE9_S3_ENDPOINT",
		"DRIVE9_S3_FORCE_PATH_STYLE",
		"DRIVE9_S3_ACCESS_KEY_ID",
		"DRIVE9_S3_SECRET_ACCESS_KEY",
		"DRIVE9_S3_SESSION_TOKEN",
		"DRIVE9_S3_ENCRYPTION_MODE",
		"DRIVE9_S3_KMS_KEY_ID",
		"DRIVE9_S3_BUCKET_KEY_ENABLED",
		"DRIVE9_S3_EXPRESS_BUCKET",
		"DRIVE9_S3_EXPRESS_PREFIX",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)

	cfg := s3ConfigFromEnv()
	if cfg.Dir != defaultS3Dir {
		t.Fatalf("default dir = %q, want %q", cfg.Dir, defaultS3Dir)
	}
	if cfg.Bucket != "" {
		t.Fatalf("default bucket = %q, want empty", cfg.Bucket)
	}
	if cfg.ExpressBucket != "" || cfg.ExpressPrefix != "" {
		t.Fatalf("default express config = (%q, %q), want empty", cfg.ExpressBucket, cfg.ExpressPrefix)
	}
	if cfg.EncryptionPolicy.Mode != meta.S3EncryptionModeNone {
		t.Fatalf("default encryption mode = %q, want none", cfg.EncryptionPolicy.Mode)
	}
	if !cfg.EncryptionPolicy.BucketKeyEnabled {
		t.Fatal("default bucket key enabled = false, want true")
	}

	setEnv(t, "DRIVE9_S3_DIR", " custom-s3 ")
	setEnv(t, "DRIVE9_S3_BUCKET", " bench-bucket ")
	setEnv(t, "DRIVE9_S3_REGION", " us-west-2 ")
	setEnv(t, "DRIVE9_S3_PREFIX", " uploads/ ")
	setEnv(t, "DRIVE9_S3_ROLE_ARN", " arn:aws:iam::123456789012:role/test ")
	setEnv(t, "DRIVE9_S3_ENDPOINT", " http://127.0.0.1:9000 ")
	setEnv(t, "DRIVE9_S3_FORCE_PATH_STYLE", " true ")
	setEnv(t, "DRIVE9_S3_ACCESS_KEY_ID", " minioadmin ")
	setEnv(t, "DRIVE9_S3_SECRET_ACCESS_KEY", " miniosecret ")
	setEnv(t, "DRIVE9_S3_SESSION_TOKEN", " session-token ")
	setEnv(t, "DRIVE9_S3_ENCRYPTION_MODE", " sse-kms ")
	setEnv(t, "DRIVE9_S3_KMS_KEY_ID", " arn:aws:kms:us-west-2:123456789012:key/test ")
	setEnv(t, "DRIVE9_S3_BUCKET_KEY_ENABLED", " false ")
	setEnv(t, "DRIVE9_S3_EXPRESS_BUCKET", " directory-bucket--az1--x-s3 ")
	setEnv(t, "DRIVE9_S3_EXPRESS_PREFIX", " append-log/ ")

	cfg = s3ConfigFromEnv()
	if cfg.Dir != "custom-s3" {
		t.Fatalf("Dir = %q, want %q", cfg.Dir, "custom-s3")
	}
	if cfg.Bucket != "bench-bucket" || cfg.Region != "us-west-2" {
		t.Fatalf("unexpected bucket/region config: %+v", cfg)
	}
	if cfg.Prefix != "uploads/" || cfg.RoleARN != "arn:aws:iam::123456789012:role/test" {
		t.Fatalf("unexpected prefix/role config: %+v", cfg)
	}
	if cfg.Endpoint != "http://127.0.0.1:9000" || !cfg.ForcePathStyle {
		t.Fatalf("unexpected endpoint config: %+v", cfg)
	}
	if cfg.AccessKeyID != "minioadmin" || cfg.SecretAccessKey != "miniosecret" || cfg.SessionToken != "session-token" {
		t.Fatalf("unexpected static credential config: %+v", cfg)
	}
	if cfg.EncryptionPolicy.Mode != meta.S3EncryptionModeSSEKMS ||
		cfg.EncryptionPolicy.KMSKeyID != "arn:aws:kms:us-west-2:123456789012:key/test" ||
		cfg.EncryptionPolicy.BucketKeyEnabled {
		t.Fatalf("unexpected encryption config: %+v", cfg.EncryptionPolicy)
	}
	if cfg.ExpressBucket != "directory-bucket--az1--x-s3" || cfg.ExpressPrefix != "append-log/" {
		t.Fatalf("unexpected express config: %+v", cfg)
	}
}

func TestS3ConfigValidateRejectsInvalidStaticCredentialCombinations(t *testing.T) {
	tests := []struct {
		name    string
		cfg     s3Config
		wantErr bool
	}{
		{
			name: "local mode ignores static credential fields",
			cfg: s3Config{
				Dir:         defaultS3Dir,
				AccessKeyID: "ak",
			},
			wantErr: false,
		},
		{
			name: "access key without secret",
			cfg: s3Config{
				Bucket:      "bucket",
				Region:      "us-east-1",
				AccessKeyID: "ak",
			},
			wantErr: true,
		},
		{
			name: "secret without access key",
			cfg: s3Config{
				Bucket:          "bucket",
				Region:          "us-east-1",
				SecretAccessKey: "sk",
			},
			wantErr: true,
		},
		{
			name: "session token without static credentials",
			cfg: s3Config{
				Bucket:       "bucket",
				Region:       "us-east-1",
				SessionToken: "token",
			},
			wantErr: true,
		},
		{
			name: "valid static credentials",
			cfg: s3Config{
				Bucket:          "bucket",
				Region:          "us-east-1",
				AccessKeyID:     "ak",
				SecretAccessKey: "sk",
				SessionToken:    "token",
			},
			wantErr: false,
		},
		{
			name: "sse kms missing key",
			cfg: s3Config{
				EncryptionPolicy: meta.S3EncryptionPolicy{Mode: meta.S3EncryptionModeSSEKMS},
			},
			wantErr: true,
		},
		{
			name: "sse kms with key in local mode",
			cfg: s3Config{
				EncryptionPolicy: meta.S3EncryptionPolicy{
					Mode:             meta.S3EncryptionModeSSEKMS,
					KMSKeyID:         "key",
					BucketKeyEnabled: true,
				},
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.cfg.validate()
			if (err != nil) != tt.wantErr {
				t.Fatalf("validate() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

type envSnapshotValue struct {
	value   string
	present bool
}

func snapshotEnv(t *testing.T, keys []string) map[string]envSnapshotValue {
	t.Helper()
	out := make(map[string]envSnapshotValue, len(keys))
	for _, key := range keys {
		value, present := os.LookupEnv(key)
		out[key] = envSnapshotValue{value: value, present: present}
	}
	return out
}

func restoreEnv(t *testing.T, snapshot map[string]envSnapshotValue) {
	t.Helper()
	for key, entry := range snapshot {
		if !entry.present {
			if err := os.Unsetenv(key); err != nil {
				t.Fatalf("unset %s: %v", key, err)
			}
			continue
		}
		if err := os.Setenv(key, entry.value); err != nil {
			t.Fatalf("restore %s: %v", key, err)
		}
	}
}

func unsetEnv(t *testing.T, keys []string) {
	t.Helper()
	for _, key := range keys {
		if err := os.Unsetenv(key); err != nil {
			t.Fatalf("unset %s: %v", key, err)
		}
	}
}

func setEnv(t *testing.T, key, value string) {
	t.Helper()
	if err := os.Setenv(key, value); err != nil {
		t.Fatalf("set %s: %v", key, err)
	}
}

func TestEnvDurationCompat(t *testing.T) {
	const canonical = "DRIVE9_TEST_DURATION_COMPAT"
	const deprecated = "DRIVE9_TEST_DURATION_COMPAT_DEPRECATED_MS"
	fallback := 5 * time.Minute

	t.Run("fallback when neither is set", func(t *testing.T) {
		if got := envDurationCompat(canonical, deprecated, fallback); got != fallback {
			t.Fatalf("expected fallback %v, got %v", fallback, got)
		}
	})
	t.Run("canonical wins when both are set", func(t *testing.T) {
		t.Setenv(canonical, "10s")
		t.Setenv(deprecated, "20s")
		if got := envDurationCompat(canonical, deprecated, fallback); got != 10*time.Second {
			t.Fatalf("expected canonical 10s, got %v", got)
		}
	})
	t.Run("deprecated name honored when canonical is unset", func(t *testing.T) {
		t.Setenv(deprecated, "20s")
		if got := envDurationCompat(canonical, deprecated, fallback); got != 20*time.Second {
			t.Fatalf("expected deprecated 20s, got %v", got)
		}
	})
}

func TestStartupEnvsSetMetaMediaAndVideoDefaults(t *testing.T) {
	keys := []string{
		"DRIVE9_MEDIA_EXTRACT_MAX_FILES",
		"DRIVE9_VIDEO_EXTRACT_MAX_FILES",
	}
	restore := snapshotEnv(t, keys)
	t.Cleanup(func() { restoreEnv(t, restore) })
	unsetEnv(t, keys)

	origMedia := meta.DefaultMaxMediaLLMFiles()
	origVideo := meta.DefaultMaxVideoLLMFiles()
	defer func() {
		meta.SetDefaultMaxMediaLLMFiles(origMedia)
		meta.SetDefaultMaxVideoLLMFiles(origVideo)
	}()

	if meta.DefaultMaxMediaLLMFiles() != 100 {
		t.Errorf("default media = %d, want 100", meta.DefaultMaxMediaLLMFiles())
	}
	if meta.DefaultMaxVideoLLMFiles() != 30 {
		t.Errorf("default video = %d, want 30", meta.DefaultMaxVideoLLMFiles())
	}

	setEnv(t, "DRIVE9_MEDIA_EXTRACT_MAX_FILES", "10000")
	setEnv(t, "DRIVE9_VIDEO_EXTRACT_MAX_FILES", "10000")
	applyQuotaDefaultsFromEnv()

	if meta.DefaultMaxMediaLLMFiles() != 10000 {
		t.Errorf("default media = %d, want 10000", meta.DefaultMaxMediaLLMFiles())
	}
	if meta.DefaultMaxVideoLLMFiles() != 10000 {
		t.Errorf("default video = %d, want 10000", meta.DefaultMaxVideoLLMFiles())
	}
}
