package envcompat

import (
	"os"
	"testing"
	"time"
)

func TestLookupPrefersNewName(t *testing.T) {
	t.Setenv("ENVCOMPAT_NEW", "new-value")
	t.Setenv("ENVCOMPAT_LEGACY", "legacy-value")

	v, src, ok := Lookup("ENVCOMPAT_NEW", "ENVCOMPAT_LEGACY")
	if !ok || v != "new-value" || src != "ENVCOMPAT_NEW" {
		t.Fatalf("Lookup = %q,%q,%v; want new-value,ENVCOMPAT_NEW,true", v, src, ok)
	}
}

func TestLookupFallsBackToLegacy(t *testing.T) {
	os.Unsetenv("ENVCOMPAT_NEW")
	t.Setenv("ENVCOMPAT_LEGACY", "legacy-value")

	v, src, ok := Lookup("ENVCOMPAT_NEW", "ENVCOMPAT_LEGACY")
	if !ok || v != "legacy-value" || src != "ENVCOMPAT_LEGACY" {
		t.Fatalf("Lookup = %q,%q,%v; want legacy-value,ENVCOMPAT_LEGACY,true", v, src, ok)
	}
}

func TestLookupTreatsWhitespaceAsUnset(t *testing.T) {
	t.Setenv("ENVCOMPAT_NEW", "   ")
	os.Unsetenv("ENVCOMPAT_LEGACY")

	_, _, ok := Lookup("ENVCOMPAT_NEW", "ENVCOMPAT_LEGACY")
	if ok {
		t.Fatal("Lookup should treat whitespace-only values as unset")
	}
}

func TestLookupMultipleLegacyPicksFirstSet(t *testing.T) {
	t.Setenv("ENVCOMPAT_LEGACY_A", "a")
	t.Setenv("ENVCOMPAT_LEGACY_B", "b")
	os.Unsetenv("ENVCOMPAT_NEW")

	v, src, ok := Lookup("ENVCOMPAT_NEW", "ENVCOMPAT_LEGACY_A", "ENVCOMPAT_LEGACY_B")
	if !ok || v != "a" || src != "ENVCOMPAT_LEGACY_A" {
		t.Fatalf("Lookup = %q,%q,%v; want a,ENVCOMPAT_LEGACY_A,true", v, src, ok)
	}
}

func TestLookupNothingSet(t *testing.T) {
	os.Unsetenv("ENVCOMPAT_NEW")
	os.Unsetenv("ENVCOMPAT_LEGACY")

	_, _, ok := Lookup("ENVCOMPAT_NEW", "ENVCOMPAT_LEGACY")
	if ok {
		t.Fatal("Lookup should return ok=false when nothing is set")
	}
}

func TestStringReturnsFallbackWhenUnset(t *testing.T) {
	os.Unsetenv("ENVCOMPAT_MISSING")

	if got := String("ENVCOMPAT_MISSING", "default"); got != "default" {
		t.Fatalf("String = %q, want default", got)
	}
}

func TestStringReturnsLegacyValue(t *testing.T) {
	os.Unsetenv("ENVCOMPAT_STR_NEW")
	t.Setenv("ENVCOMPAT_STR_LEGACY", "from-legacy")

	got := String("ENVCOMPAT_STR_NEW", "default", "ENVCOMPAT_STR_LEGACY")
	if got != "from-legacy" {
		t.Fatalf("String = %q, want from-legacy", got)
	}
}

func TestBoolParsesLegacy(t *testing.T) {
	os.Unsetenv("ENVCOMPAT_BOOL_NEW")
	t.Setenv("ENVCOMPAT_BOOL_LEGACY", "true")

	got, err := Bool("ENVCOMPAT_BOOL_NEW", false, "ENVCOMPAT_BOOL_LEGACY")
	if err != nil || !got {
		t.Fatalf("Bool = %v,%v; want true,nil", got, err)
	}
}

func TestBoolInvalidReturnsFallbackAndError(t *testing.T) {
	t.Setenv("ENVCOMPAT_BOOL_BAD", "maybe")

	got, err := Bool("ENVCOMPAT_BOOL_BAD", true)
	if err == nil {
		t.Fatalf("expected error, got %v", got)
	}
	if got != true {
		t.Fatalf("Bool fallback = %v, want true", got)
	}
}

func TestDurationParsesLegacy(t *testing.T) {
	os.Unsetenv("ENVCOMPAT_DUR_NEW")
	t.Setenv("ENVCOMPAT_DUR_LEGACY", "30s")

	got, err := Duration("ENVCOMPAT_DUR_NEW", time.Minute, "ENVCOMPAT_DUR_LEGACY")
	if err != nil || got != 30*time.Second {
		t.Fatalf("Duration = %v,%v; want 30s,nil", got, err)
	}
}

func TestDurationInvalidReturnsError(t *testing.T) {
	t.Setenv("ENVCOMPAT_DUR_BAD", "not-a-duration")

	if _, err := Duration("ENVCOMPAT_DUR_BAD", time.Minute); err == nil {
		t.Fatal("expected error for invalid duration")
	}
}

func TestIntParsesLegacy(t *testing.T) {
	os.Unsetenv("ENVCOMPAT_INT_NEW")
	t.Setenv("ENVCOMPAT_INT_LEGACY", "42")

	got, err := Int("ENVCOMPAT_INT_NEW", 0, "ENVCOMPAT_INT_LEGACY")
	if err != nil || got != 42 {
		t.Fatalf("Int = %v,%v; want 42,nil", got, err)
	}
}

func TestIntInvalidReturnsError(t *testing.T) {
	t.Setenv("ENVCOMPAT_INT_BAD", "not-a-number")

	if _, err := Int("ENVCOMPAT_INT_BAD", 0); err == nil {
		t.Fatal("expected error for invalid int")
	}
}

func TestWarnLegacySkipsWhenPreferredSet(t *testing.T) {
	orig := Registry
	t.Cleanup(func() { Registry = orig })

	Registry = []Alias{
		{New: "ENVCOMPAT_WARN_NEW_A", Legacy: []string{"ENVCOMPAT_WARN_LEGACY_A"}},
		{New: "ENVCOMPAT_WARN_NEW_B", Legacy: []string{"ENVCOMPAT_WARN_LEGACY_B"}},
	}

	t.Setenv("ENVCOMPAT_WARN_LEGACY_A", "x")
	os.Unsetenv("ENVCOMPAT_WARN_NEW_A")
	t.Setenv("ENVCOMPAT_WARN_NEW_B", "y")
	t.Setenv("ENVCOMPAT_WARN_LEGACY_B", "z")

	var reported []string
	WarnLegacy(func(legacy, newName string) {
		reported = append(reported, legacy+"->"+newName)
	})

	want := []string{"ENVCOMPAT_WARN_LEGACY_A->ENVCOMPAT_WARN_NEW_A"}
	if len(reported) != 1 || reported[0] != want[0] {
		t.Fatalf("WarnLegacy reported = %v, want %v", reported, want)
	}
}

func TestWarnLegacySilentWhenNothingLegacy(t *testing.T) {
	orig := Registry
	t.Cleanup(func() { Registry = orig })

	Registry = []Alias{
		{New: "ENVCOMPAT_SILENT_NEW", Legacy: []string{"ENVCOMPAT_SILENT_LEGACY"}},
	}
	os.Unsetenv("ENVCOMPAT_SILENT_NEW")
	os.Unsetenv("ENVCOMPAT_SILENT_LEGACY")

	var calls int
	WarnLegacy(func(string, string) { calls++ })

	if calls != 0 {
		t.Fatalf("WarnLegacy should be silent when no legacy set, got %d calls", calls)
	}
}

func TestWarnLegacyOnceIsIdempotent(t *testing.T) {
	// WarnLegacyOnce wraps WarnLegacy in sync.Once; calling twice must not
	// double-fire. This is a smoke test that does not assert log output.
	WarnLegacyOnce(nil)
	WarnLegacyOnce(nil)
}
