package traefikoidc

import (
	"crypto/sha256"
	"encoding/base64"
	"strings"
	"testing"
	"time"

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

// TestGenerateNonce tests the nonce generation for OIDC flows
func TestGenerateNonce(t *testing.T) {
	t.Run("basic generation", func(t *testing.T) {
		nonce, err := generateNonce()

		require.NoError(t, err)
		assert.NotEmpty(t, nonce)

		// 32 bytes base64 URL encoded should produce 44 characters (with potential padding)
		// but typically 43 characters without padding
		assert.GreaterOrEqual(t, len(nonce), 43, "nonce should be at least 43 characters")
	})

	t.Run("nonce is base64 URL encoded", func(t *testing.T) {
		nonce, err := generateNonce()

		require.NoError(t, err)

		// Should be valid base64 URL encoding
		_, err = base64.URLEncoding.DecodeString(nonce)
		assert.NoError(t, err, "nonce should be valid base64 URL encoding")
	})

	t.Run("multiple generations produce different values", func(t *testing.T) {
		nonce1, err1 := generateNonce()
		nonce2, err2 := generateNonce()

		require.NoError(t, err1)
		require.NoError(t, err2)

		assert.NotEqual(t, nonce1, nonce2, "consecutive generations should produce different nonces")
	})

	t.Run("nonce has sufficient entropy", func(t *testing.T) {
		// Generate multiple nonces and verify they're all unique
		nonces := make(map[string]bool)
		iterations := 100

		for i := 0; i < iterations; i++ {
			nonce, err := generateNonce()
			require.NoError(t, err)

			// Check for duplicates
			assert.False(t, nonces[nonce], "nonce should be unique across multiple generations")
			nonces[nonce] = true
		}

		assert.Len(t, nonces, iterations, "all nonces should be unique")
	})

	t.Run("nonce length is consistent", func(t *testing.T) {
		nonce1, err1 := generateNonce()
		nonce2, err2 := generateNonce()

		require.NoError(t, err1)
		require.NoError(t, err2)

		assert.Equal(t, len(nonce1), len(nonce2), "nonce length should be consistent")
	})
}

// TestGenerateCodeVerifier tests the PKCE code verifier generation
func TestGenerateCodeVerifier(t *testing.T) {
	t.Run("basic generation", func(t *testing.T) {
		verifier, err := generateCodeVerifier()

		require.NoError(t, err)
		assert.NotEmpty(t, verifier)

		// RFC 7636 requires 43-128 characters for code verifier
		// With 32 bytes base64 raw URL encoded, we get 43 characters
		assert.Len(t, verifier, 43, "code verifier should be 43 characters (32 bytes base64 encoded)")
	})

	t.Run("verifier is base64 URL encoded", func(t *testing.T) {
		verifier, err := generateCodeVerifier()

		require.NoError(t, err)

		// Should be valid base64 URL encoding
		_, err = base64.RawURLEncoding.DecodeString(verifier)
		assert.NoError(t, err, "verifier should be valid base64 URL encoding")
	})

	t.Run("multiple generations produce different values", func(t *testing.T) {
		verifier1, err1 := generateCodeVerifier()
		verifier2, err2 := generateCodeVerifier()

		require.NoError(t, err1)
		require.NoError(t, err2)

		assert.NotEqual(t, verifier1, verifier2, "consecutive generations should produce different verifiers")
	})

	t.Run("verifier contains only URL-safe characters", func(t *testing.T) {
		verifier, err := generateCodeVerifier()

		require.NoError(t, err)

		// Base64 URL encoding should only contain A-Z, a-z, 0-9, -, _
		for _, char := range verifier {
			validChar := (char >= 'A' && char <= 'Z') ||
				(char >= 'a' && char <= 'z') ||
				(char >= '0' && char <= '9') ||
				char == '-' || char == '_'
			assert.True(t, validChar, "verifier should only contain URL-safe characters")
		}
	})

	t.Run("no padding characters", func(t *testing.T) {
		verifier, err := generateCodeVerifier()

		require.NoError(t, err)

		// Raw URL encoding should not have padding
		assert.False(t, strings.Contains(verifier, "="), "verifier should not contain padding")
	})
}

// TestDeriveCodeChallenge tests the PKCE code challenge derivation
func TestDeriveCodeChallenge(t *testing.T) {
	t.Run("basic derivation", func(t *testing.T) {
		verifier := "test-verifier-value-1234567890abcdefghij"
		challenge := deriveCodeChallenge(verifier)

		assert.NotEmpty(t, challenge)
		assert.NotEqual(t, verifier, challenge, "challenge should be different from verifier")
	})

	t.Run("challenge is SHA256 hash", func(t *testing.T) {
		verifier := "test-code-verifier"

		// Manually compute expected challenge
		hasher := sha256.New()
		hasher.Write([]byte(verifier))
		expectedHash := hasher.Sum(nil)
		expectedChallenge := base64.RawURLEncoding.EncodeToString(expectedHash)

		challenge := deriveCodeChallenge(verifier)

		assert.Equal(t, expectedChallenge, challenge, "challenge should match SHA256 hash")
	})

	t.Run("same verifier produces same challenge", func(t *testing.T) {
		verifier := "consistent-verifier-12345"

		challenge1 := deriveCodeChallenge(verifier)
		challenge2 := deriveCodeChallenge(verifier)

		assert.Equal(t, challenge1, challenge2, "same verifier should always produce same challenge")
	})

	t.Run("different verifiers produce different challenges", func(t *testing.T) {
		verifier1 := "verifier-one"
		verifier2 := "verifier-two"

		challenge1 := deriveCodeChallenge(verifier1)
		challenge2 := deriveCodeChallenge(verifier2)

		assert.NotEqual(t, challenge1, challenge2, "different verifiers should produce different challenges")
	})

	t.Run("challenge is base64 URL encoded", func(t *testing.T) {
		verifier := "test-verifier"
		challenge := deriveCodeChallenge(verifier)

		// Should be valid base64 URL encoding
		_, err := base64.RawURLEncoding.DecodeString(challenge)
		assert.NoError(t, err, "challenge should be valid base64 URL encoding")
	})

	t.Run("challenge length is correct", func(t *testing.T) {
		verifier := "some-random-verifier"
		challenge := deriveCodeChallenge(verifier)

		// SHA256 produces 32 bytes, which when base64 encoded becomes 43 characters
		assert.Len(t, challenge, 43, "SHA256 hash should produce 43-character base64 string")
	})

	t.Run("no padding in challenge", func(t *testing.T) {
		verifier := "test-verifier-no-padding"
		challenge := deriveCodeChallenge(verifier)

		assert.False(t, strings.Contains(challenge, "="), "challenge should not contain padding")
	})

	t.Run("empty verifier produces valid challenge", func(t *testing.T) {
		verifier := ""
		challenge := deriveCodeChallenge(verifier)

		assert.NotEmpty(t, challenge, "even empty verifier should produce a challenge")
		assert.Len(t, challenge, 43, "challenge should still be 43 characters")
	})
}

// TestPKCEFlowIntegration tests the complete PKCE flow
func TestPKCEFlowIntegration(t *testing.T) {
	t.Run("complete PKCE flow", func(t *testing.T) {
		// Step 1: Generate code verifier
		verifier, err := generateCodeVerifier()
		require.NoError(t, err)

		// Step 2: Derive code challenge
		challenge := deriveCodeChallenge(verifier)

		// Verify challenge was derived from verifier
		expectedChallenge := deriveCodeChallenge(verifier)
		assert.Equal(t, expectedChallenge, challenge)

		// Verify verifier can be used to recreate challenge
		rechallenge := deriveCodeChallenge(verifier)
		assert.Equal(t, challenge, rechallenge, "verifier should consistently produce same challenge")
	})

	t.Run("multiple PKCE flows are independent", func(t *testing.T) {
		// Flow 1
		verifier1, err1 := generateCodeVerifier()
		require.NoError(t, err1)
		challenge1 := deriveCodeChallenge(verifier1)

		// Flow 2
		verifier2, err2 := generateCodeVerifier()
		require.NoError(t, err2)
		challenge2 := deriveCodeChallenge(verifier2)

		// Flows should be independent
		assert.NotEqual(t, verifier1, verifier2)
		assert.NotEqual(t, challenge1, challenge2)

		// Each flow should be internally consistent
		assert.Equal(t, challenge1, deriveCodeChallenge(verifier1))
		assert.Equal(t, challenge2, deriveCodeChallenge(verifier2))
	})

	t.Run("RFC 7636 compliance", func(t *testing.T) {
		verifier, err := generateCodeVerifier()
		require.NoError(t, err)

		challenge := deriveCodeChallenge(verifier)

		// RFC 7636 Section 4.2:
		// - code_verifier: high-entropy cryptographic random string
		// - Minimum length: 43 characters
		// - Maximum length: 128 characters
		// - Character set: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
		assert.GreaterOrEqual(t, len(verifier), 43, "verifier should be at least 43 characters")
		assert.LessOrEqual(t, len(verifier), 128, "verifier should be at most 128 characters")

		// RFC 7636 Section 4.2:
		// - code_challenge = BASE64URL(SHA256(code_verifier))
		assert.NotEmpty(t, challenge)
		assert.Len(t, challenge, 43, "S256 challenge should be 43 characters")
	})
}

// TestTokenCacheCleanupAndClose tests the no-op Cleanup and Close methods
func TestTokenCacheCleanupAndClose(t *testing.T) {
	cache := NewTokenCache()
	require.NotNil(t, cache)

	t.Run("cleanup is safe to call", func(t *testing.T) {
		// Should not panic
		assert.NotPanics(t, func() {
			cache.Cleanup()
		})
	})

	t.Run("close is safe to call", func(t *testing.T) {
		// Should not panic
		assert.NotPanics(t, func() {
			cache.Close()
		})
	})

	t.Run("multiple cleanup calls are safe", func(t *testing.T) {
		assert.NotPanics(t, func() {
			cache.Cleanup()
			cache.Cleanup()
			cache.Cleanup()
		})
	})

	t.Run("multiple close calls are safe", func(t *testing.T) {
		assert.NotPanics(t, func() {
			cache.Close()
			cache.Close()
			cache.Close()
		})
	})

	t.Run("operations work after cleanup", func(t *testing.T) {
		cache.Cleanup()

		// Should still work
		testClaims := map[string]interface{}{"sub": "user123"}
		cache.Set("token1", testClaims, 1*time.Minute)

		claims, found := cache.Get("token1")
		assert.True(t, found)
		assert.Equal(t, testClaims, claims)
	})

	t.Run("operations work after close", func(t *testing.T) {
		cache.Close()

		// Should still work (close is a no-op)
		testClaims := map[string]interface{}{"sub": "user456"}
		cache.Set("token2", testClaims, 1*time.Minute)

		claims, found := cache.Get("token2")
		assert.True(t, found)
		assert.Equal(t, testClaims, claims)
	})
}

// TestCreateStringMap tests the createStringMap utility function
func TestCreateStringMap(t *testing.T) {
	t.Run("empty slice", func(t *testing.T) {
		result := createStringMap([]string{})
		assert.Empty(t, result)
	})

	t.Run("single item", func(t *testing.T) {
		result := createStringMap([]string{"key1"})
		assert.Len(t, result, 1)
		_, exists := result["key1"]
		assert.True(t, exists)
	})

	t.Run("multiple items", func(t *testing.T) {
		result := createStringMap([]string{"key1", "key2", "key3"})
		assert.Len(t, result, 3)

		for _, key := range []string{"key1", "key2", "key3"} {
			_, exists := result[key]
			assert.True(t, exists, "key %s should exist", key)
		}
	})

	t.Run("duplicate items", func(t *testing.T) {
		result := createStringMap([]string{"key1", "key2", "key1", "key3", "key2"})
		// Map should only contain unique keys
		assert.Len(t, result, 3)

		for _, key := range []string{"key1", "key2", "key3"} {
			_, exists := result[key]
			assert.True(t, exists, "key %s should exist", key)
		}
	})
}
