package antigravity

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestGetUserAgent(t *testing.T) {
	ua := GetUserAgent()
	assert.Contains(t, ua, "antigravity/")
	assert.Contains(t, ua, "windows/amd64")
}

func TestBaseURLs(t *testing.T) {
	assert.NotEmpty(t, BaseURLs)
	assert.GreaterOrEqual(t, len(BaseURLs), 2)
	assert.Contains(t, BaseURLs, antigravityProdBaseURL)
	assert.Contains(t, BaseURLs, antigravityDailyBaseURL)
}

func TestForwardBaseURLs(t *testing.T) {
	urls := ForwardBaseURLs()
	assert.NotEmpty(t, urls)

	// daily should be first if it exists
	if len(urls) >= 2 {
		assert.Equal(t, antigravityDailyBaseURL, urls[0])
	}
}

func TestForwardBaseURLsEmpty(t *testing.T) {
	// Temporarily set BaseURLs to empty
	originalURLs := BaseURLs
	BaseURLs = []string{}
	defer func() { BaseURLs = originalURLs }()

	urls := ForwardBaseURLs()
	assert.Empty(t, urls)
}

func TestURLAvailability(t *testing.T) {
	ua := NewURLAvailability(URLAvailabilityTTL)

	t.Run("mark unavailable", func(t *testing.T) {
		url := "http://example.com"
		ua.MarkUnavailable(url)
		assert.False(t, ua.IsAvailable(url))
	})

	t.Run("mark success", func(t *testing.T) {
		url := "http://example.com"
		ua.MarkSuccess(url)
		assert.True(t, ua.IsAvailable(url))
	})

	t.Run("expired unavailable", func(t *testing.T) {
		// Create with very short TTL
		ua := NewURLAvailability(1 * time.Nanosecond)
		url := "http://expired.com"
		ua.MarkUnavailable(url)
		time.Sleep(10 * time.Millisecond)
		assert.True(t, ua.IsAvailable(url))
	})

	t.Run("get available URLs", func(t *testing.T) {
		ua := NewURLAvailability(URLAvailabilityTTL)
		baseURLs := []string{"http://a.com", "http://b.com", "http://c.com"}

		// Mark one as unavailable
		ua.MarkUnavailable("http://b.com")

		available := ua.GetAvailableURLsWithBase(baseURLs)
		assert.Contains(t, available, "http://a.com")
		assert.Contains(t, available, "http://c.com")
		assert.NotContains(t, available, "http://b.com")
	})

	t.Run("last success priority", func(t *testing.T) {
		ua := NewURLAvailability(URLAvailabilityTTL)
		baseURLs := []string{"http://a.com", "http://b.com"}

		ua.MarkSuccess("http://b.com")
		available := ua.GetAvailableURLsWithBase(baseURLs)

		// b.com should be first
		assert.Equal(t, "http://b.com", available[0])
	})
}

func TestSessionStore(t *testing.T) {
	store := NewSessionStore()
	defer store.Stop()

	t.Run("set and get", func(t *testing.T) {
		session := &OAuthSession{
			State:        "state123",
			CodeVerifier: "verifier456",
			CreatedAt:    time.Now(),
		}
		store.Set("session1", session)

		got, ok := store.Get("session1")
		assert.True(t, ok)
		assert.Equal(t, "state123", got.State)
	})

	t.Run("get non-existent", func(t *testing.T) {
		_, ok := store.Get("nonexistent")
		assert.False(t, ok)
	})

	t.Run("expired session", func(t *testing.T) {
		expiredSession := &OAuthSession{
			State:        "expired",
			CodeVerifier: "verifier",
			CreatedAt:    time.Now().Add(-2 * SessionTTL),
		}
		store.Set("expired", expiredSession)

		_, ok := store.Get("expired")
		assert.False(t, ok)
	})

	t.Run("delete", func(t *testing.T) {
		session := &OAuthSession{
			State:     "to_delete",
			CreatedAt: time.Now(),
		}
		store.Set("to_delete", session)
		store.Delete("to_delete")

		_, ok := store.Get("to_delete")
		assert.False(t, ok)
	})
}

func TestGenerateFunctions(t *testing.T) {
	t.Run("random bytes", func(t *testing.T) {
		b1, err := GenerateRandomBytes(32)
		assert.NoError(t, err)
		assert.Equal(t, 32, len(b1))

		b2, _ := GenerateRandomBytes(32)
		assert.NotEqual(t, b1, b2)
	})

	t.Run("state", func(t *testing.T) {
		state, err := GenerateState()
		assert.NoError(t, err)
		assert.NotEmpty(t, state)

		state2, _ := GenerateState()
		assert.NotEqual(t, state, state2)
	})

	t.Run("session ID", func(t *testing.T) {
		id, err := GenerateSessionID()
		assert.NoError(t, err)
		assert.NotEmpty(t, id)
		assert.Equal(t, 32, len(id)) // 16 bytes * 2 hex chars
	})

	t.Run("code verifier", func(t *testing.T) {
		verifier, err := GenerateCodeVerifier()
		assert.NoError(t, err)
		assert.NotEmpty(t, verifier)
	})

	t.Run("code challenge", func(t *testing.T) {
		challenge := GenerateCodeChallenge("test_verifier")
		assert.NotEmpty(t, challenge)

		// Deterministic
		challenge2 := GenerateCodeChallenge("test_verifier")
		assert.Equal(t, challenge, challenge2)
	})
}

func TestBase64URLEncode(t *testing.T) {
	tests := []struct {
		input    []byte
		expected string
	}{
		{[]byte("hello"), "aGVsbG8"},
		{[]byte("test"), "dGVzdA"},
		{[]byte{}, ""},
	}

	for _, tt := range tests {
		result := base64URLEncode(tt.input)
		assert.Equal(t, tt.expected, result)
		assert.NotContains(t, result, "=")
	}
}

func TestBuildAuthorizationURL(t *testing.T) {
	url := BuildAuthorizationURL("test_state", "test_challenge")

	assert.Contains(t, url, AuthorizeURL)
	assert.Contains(t, url, "client_id="+ClientID)
	assert.Contains(t, url, "state=test_state")
	assert.Contains(t, url, "code_challenge=test_challenge")
	assert.Contains(t, url, "code_challenge_method=S256")
	assert.Contains(t, url, "redirect_uri=")
	assert.Contains(t, url, "response_type=code")
}

func TestConstants(t *testing.T) {
	assert.NotEmpty(t, ClientID)
	assert.NotEmpty(t, RedirectURI)
	assert.NotEmpty(t, AuthorizeURL)
	assert.NotEmpty(t, TokenURL)
	assert.NotEmpty(t, Scopes)
	assert.Equal(t, 30*time.Minute, SessionTTL)
	assert.Equal(t, 5*time.Minute, URLAvailabilityTTL)
}
