// ABOUTME: Unit tests for authentication providers including username/password token flow
// ABOUTME: Tests token caching, login flow, and error conditions with mock servers

package v2

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

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

func TestStaticTokenProvider(t *testing.T) {
	provider := NewStaticTokenProvider("test-token-123")

	req, _ := http.NewRequest("GET", "https://example.com", nil)
	ctx := context.Background()

	err := provider.Attach(ctx, req)

	require.NoError(t, err)
	assert.Equal(t, "Bearer test-token-123", req.Header.Get("Authorization"))
}

func TestStaticTokenProvider_EmptyToken(t *testing.T) {
	provider := NewStaticTokenProvider("")

	req, _ := http.NewRequest("GET", "https://example.com", nil)
	ctx := context.Background()

	err := provider.Attach(ctx, req)

	require.NoError(t, err)
	assert.Empty(t, req.Header.Get("Authorization"))
}

func TestUsernamePasswordProvider_Success(t *testing.T) {
	// Mock login server
	loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, "POST", r.Method)
		assert.Equal(t, "/api/v1/login", r.URL.Path)
		assert.Equal(t, "application/json", r.Header.Get("Content-Type"))

		// Verify request body
		var creds map[string]string
		err := json.NewDecoder(r.Body).Decode(&creds)
		require.NoError(t, err)
		assert.Equal(t, "testuser", creds["username"])
		assert.Equal(t, "testpass", creds["password"])

		// Return token
		response := map[string]string{"token": "dynamic-token-456"}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(response)
	}))
	defer loginServer.Close()

	provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil)

	req, _ := http.NewRequest("GET", "https://api.example.com", nil)
	ctx := context.Background()

	err := provider.Attach(ctx, req)

	require.NoError(t, err)
	assert.Equal(t, "Bearer dynamic-token-456", req.Header.Get("Authorization"))
}

func TestUsernamePasswordProvider_LoginFailure(t *testing.T) {
	// Mock login server that returns error
	loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusUnauthorized)
		_, _ = w.Write([]byte("Invalid credentials"))
	}))
	defer loginServer.Close()

	provider := NewUsernamePasswordProvider(loginServer.URL, "baduser", "badpass", nil)

	req, _ := http.NewRequest("GET", "https://api.example.com", nil)
	ctx := context.Background()

	err := provider.Attach(ctx, req)

	require.Error(t, err)
	assert.Contains(t, err.Error(), "login failed with status 401")
	assert.Contains(t, err.Error(), "Invalid credentials")
}

func TestUsernamePasswordProvider_TokenCaching(t *testing.T) {
	callCount := 0

	// Mock login server that tracks calls
	loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		callCount++
		response := map[string]string{"token": "cached-token-789"}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(response)
	}))
	defer loginServer.Close()

	provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil)
	ctx := context.Background()

	// First request should call login
	req1, _ := http.NewRequest("GET", "https://api.example.com", nil)
	err1 := provider.Attach(ctx, req1)
	require.NoError(t, err1)
	assert.Equal(t, "Bearer cached-token-789", req1.Header.Get("Authorization"))
	assert.Equal(t, 1, callCount)

	// Second request should use cached token (no additional login call)
	req2, _ := http.NewRequest("GET", "https://api.example.com", nil)
	err2 := provider.Attach(ctx, req2)
	require.NoError(t, err2)
	assert.Equal(t, "Bearer cached-token-789", req2.Header.Get("Authorization"))
	assert.Equal(t, 1, callCount) // Still only 1 call
}

func TestUsernamePasswordProvider_TokenExpiry(t *testing.T) {
	callCount := 0

	loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		callCount++
		response := map[string]string{"token": "refreshed-token-999"}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(response)
	}))
	defer loginServer.Close()

	provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil)

	// Manually set expired token
	provider.mu.Lock()
	provider.cachedToken = "expired-token"
	provider.tokenExpiry = time.Now().Add(-1 * time.Hour) // Already expired
	provider.mu.Unlock()

	ctx := context.Background()
	req, _ := http.NewRequest("GET", "https://api.example.com", nil)

	err := provider.Attach(ctx, req)

	require.NoError(t, err)
	assert.Equal(t, "Bearer refreshed-token-999", req.Header.Get("Authorization"))
	assert.Equal(t, 1, callCount) // New token retrieved
}

func TestUsernamePasswordProvider_InvalidateToken(t *testing.T) {
	callCount := 0

	loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		callCount++
		response := map[string]string{"token": "new-token-after-invalidation"}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(response)
	}))
	defer loginServer.Close()

	provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil)
	ctx := context.Background()

	// First request to get token
	req1, _ := http.NewRequest("GET", "https://api.example.com", nil)
	err1 := provider.Attach(ctx, req1)
	require.NoError(t, err1)
	assert.Equal(t, 1, callCount)

	// Invalidate token
	provider.InvalidateToken()

	// Next request should get new token
	req2, _ := http.NewRequest("GET", "https://api.example.com", nil)
	err2 := provider.Attach(ctx, req2)
	require.NoError(t, err2)
	assert.Equal(t, "Bearer new-token-after-invalidation", req2.Header.Get("Authorization"))
	assert.Equal(t, 2, callCount) // New login call made
}

func TestUsernamePasswordProvider_BadJSONResponse(t *testing.T) {
	// Mock server returning invalid JSON
	loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte("invalid json response"))
	}))
	defer loginServer.Close()

	provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil)

	req, _ := http.NewRequest("GET", "https://api.example.com", nil)
	ctx := context.Background()

	err := provider.Attach(ctx, req)

	require.Error(t, err)
	assert.Contains(t, err.Error(), "error parsing JSON")
}

func TestNoAuthProvider(t *testing.T) {
	provider := NewNoAuthProvider()

	req, _ := http.NewRequest("GET", "https://example.com", nil)
	ctx := context.Background()

	err := provider.Attach(ctx, req)

	require.NoError(t, err)
	assert.Empty(t, req.Header.Get("Authorization"))
}

func TestNewClientWithCredentials(t *testing.T) {
	client := NewClientWithCredentials("https://example.com", "testuser", "testpass")

	assert.Equal(t, "https://example.com", client.BaseURL)

	// Check that auth provider is UsernamePasswordProvider
	authProvider, ok := client.AuthProvider.(*UsernamePasswordProvider)
	require.True(t, ok, "AuthProvider should be UsernamePasswordProvider")
	assert.Equal(t, "testuser", authProvider.Username)
	assert.Equal(t, "testpass", authProvider.Password)
	assert.Equal(t, "https://example.com", authProvider.BaseURL)
}
