package mymiddleware

import (
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	echo "github.com/labstack/echo/v4"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// generateTestToken creates a JWT token for testing (without signature verification)
func generateTestToken(email string, claims map[string]interface{}) (string, error) {
	// Create JWT header (base64 encoded)
	header := map[string]interface{}{
		"alg": "HS256",
		"typ": "JWT",
	}
	headerBytes, _ := json.Marshal(header)
	headerEncoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(headerBytes)

	// Create the Claims
	payload := map[string]interface{}{
		"email": email,
		"exp":   time.Now().Add(time.Hour * 24).Unix(), // expires in 24 hours
		"iat":   time.Now().Unix(),                     // issued at
	}

	// Add additional claims
	for key, value := range claims {
		payload[key] = value
	}

	// Encode payload
	payloadBytes, _ := json.Marshal(payload)
	payloadEncoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(payloadBytes)

	// Create a simple JWT without valid signature (since we're not verifying it)
	signature := "fake-signature"

	return headerEncoded + "." + payloadEncoded + "." + signature, nil
}

func TestJWTMiddleware_ValidToken(t *testing.T) {
	// Create test token
	tokenString, err := generateTestToken("test@example.com", map[string]interface{}{
		"role":      "admin",
		"user_name": "testuser",
	})
	require.NoError(t, err)

	// Setup Echo
	e := echo.New()

	// Configure JWT middleware
	config := DefaultJWTConfig()

	// Add middleware
	e.Use(JWTWithConfig(config))

	// Add test route
	e.GET("/test", func(c echo.Context) error {
		email, ok := GetUserEmailFromContext(c)
		if !ok {
			return echo.NewHTTPError(http.StatusUnauthorized, "no email")
		}

		claims, ok := GetUserClaimsFromContext(c)
		if !ok {
			return echo.NewHTTPError(http.StatusUnauthorized, "no claims")
		}

		return c.JSON(http.StatusOK, map[string]interface{}{
			"email":  email,
			"claims": claims,
		})
	})

	// Create request with JWT token
	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	req.Header.Set("Authorization", "Bearer "+tokenString)
	rec := httptest.NewRecorder()

	// Execute request
	e.ServeHTTP(rec, req)

	// Assert response
	assert.Equal(t, http.StatusOK, rec.Code)

	var response map[string]interface{}
	err = json.Unmarshal(rec.Body.Bytes(), &response)
	require.NoError(t, err)

	assert.Equal(t, "test@example.com", response["email"])

	claims, ok := response["claims"].(map[string]interface{})
	require.True(t, ok)
	assert.Equal(t, "test@example.com", claims["email"])
	assert.Equal(t, "admin", claims["role"])
	assert.Equal(t, "testuser", claims["user_name"])
}

func TestJWTMiddleware_InvalidToken(t *testing.T) {
	// Setup Echo
	e := echo.New()

	// Configure JWT middleware
	config := DefaultJWTConfig()

	// Add middleware
	e.Use(JWTWithConfig(config))

	// Add test route
	e.GET("/test", func(c echo.Context) error {
		return c.JSON(http.StatusOK, map[string]string{"message": "success"})
	})

	// Test cases
	testCases := []struct {
		name           string
		authorization  string
		expectedStatus int
	}{
		{
			name:           "No Authorization Header",
			authorization:  "",
			expectedStatus: http.StatusUnauthorized,
		},
		{
			name:           "Invalid Token Format - Not JWT",
			authorization:  "Bearer invalid-token",
			expectedStatus: http.StatusUnauthorized,
		},
		{
			name:           "Missing Bearer Prefix",
			authorization:  "invalid-token",
			expectedStatus: http.StatusUnauthorized,
		},
		{
			name:           "Invalid JWT Structure - Only 2 Parts",
			authorization:  "Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX25hbWUiOiJ0ZXN0In0",
			expectedStatus: http.StatusUnauthorized,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, "/test", nil)
			if tc.authorization != "" {
				req.Header.Set("Authorization", tc.authorization)
			}
			rec := httptest.NewRecorder()

			e.ServeHTTP(rec, req)

			assert.Equal(t, tc.expectedStatus, rec.Code)
		})
	}
}

func TestJWTMiddleware_MissingUserName(t *testing.T) {
	// Create token without user_name claim using our custom function
	// but we'll manually create it without user_name
	header := map[string]interface{}{
		"alg": "HS256",
		"typ": "JWT",
	}
	headerBytes, _ := json.Marshal(header)
	headerEncoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(headerBytes)

	payload := map[string]interface{}{
		"role": "admin",
		"exp":  time.Now().Add(time.Hour * 24).Unix(),
	}
	payloadBytes, _ := json.Marshal(payload)
	payloadEncoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(payloadBytes)

	tokenString := headerEncoded + "." + payloadEncoded + ".fake-signature"

	// Setup Echo
	e := echo.New()

	// Configure JWT middleware
	config := DefaultJWTConfig()

	// Add middleware
	e.Use(JWTWithConfig(config))

	// Add test route
	e.GET("/test", func(c echo.Context) error {
		return c.JSON(http.StatusOK, map[string]string{"message": "success"})
	})

	// Create request with JWT token
	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	req.Header.Set("Authorization", "Bearer "+tokenString)
	rec := httptest.NewRecorder()

	// Execute request
	e.ServeHTTP(rec, req)

	// Should return 401 because user_name is missing
	assert.Equal(t, http.StatusUnauthorized, rec.Code)
}

func TestJWTMiddleware_Skipper(t *testing.T) {
	// Setup Echo
	e := echo.New()

	// Configure JWT middleware with skipper
	config := DefaultJWTConfig()
	config.Skipper = func(c echo.Context) bool {
		return strings.HasPrefix(c.Path(), "/public")
	}

	// Add middleware
	e.Use(JWTWithConfig(config))

	// Add test routes
	e.GET("/public/health", func(c echo.Context) error {
		return c.JSON(http.StatusOK, map[string]string{"message": "public endpoint"})
	})
	e.GET("/private/data", func(c echo.Context) error {
		return c.JSON(http.StatusOK, map[string]string{"message": "private endpoint"})
	})

	// Test public endpoint (should work without token)
	req := httptest.NewRequest(http.MethodGet, "/public/health", nil)
	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)

	// Test private endpoint (should require token)
	req = httptest.NewRequest(http.MethodGet, "/private/data", nil)
	rec = httptest.NewRecorder()
	e.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusUnauthorized, rec.Code)
}

func TestJWTPayloadParsingUnsafe(t *testing.T) {
	// Test the parseJWTPayloadUnsafe function directly
	testCases := []struct {
		name           string
		token          string
		expectError    bool
		expectedClaims map[string]interface{}
	}{
		{
			name: "Valid JWT Token",
			token: func() string {
				token, _ := generateTestToken("test@example.com", map[string]interface{}{
					"role": "admin",
				})
				return token
			}(),
			expectError: false,
			expectedClaims: map[string]interface{}{
				"email": "test@example.com",
				"role":  "admin",
			},
		},
		{
			name:        "Invalid Token - Wrong Format",
			token:       "invalid.token",
			expectError: true,
		},
		{
			name:        "Invalid Token - Not 3 Parts",
			token:       "header.payload",
			expectError: true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			claims, err := parseJWTPayloadUnsafe(tc.token)

			if tc.expectError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				for key, expectedValue := range tc.expectedClaims {
					assert.Equal(t, expectedValue, claims[key])
				}
			}
		})
	}
}

// Example of how to use the JWT middleware in your application
func ExampleJWT() {
	e := echo.New()

	// Configure JWT middleware
	jwtConfig := DefaultJWTConfig()
	jwtConfig.Skipper = func(c echo.Context) bool {
		// Skip JWT validation for health check and swagger endpoints
		path := c.Path()
		return path == "/ping" || strings.HasPrefix(path, "/swagger/")
	}

	// Apply JWT middleware
	e.Use(JWTWithConfig(jwtConfig))

	// Protected route example
	e.GET("/api/user/profile", func(c echo.Context) error {
		userName, ok := GetUserEmailFromContext(c)
		if !ok {
			return echo.NewHTTPError(http.StatusUnauthorized, "user not found")
		}

		return c.JSON(http.StatusOK, map[string]string{
			"user_name": userName,
			"message":   "Profile data for " + userName,
		})
	})
}
