package middlewares

import (
	"GoMembership/internal/config"
	"GoMembership/internal/constants"
	"GoMembership/internal/models"
	"GoMembership/pkg/logger"
	"encoding/json"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/stretchr/testify/assert"
)

func setupTestEnvironment() {
	cwd, err := os.Getwd()
	if err != nil {
		log.Fatalf("Failed to get current working directory: %v", err)
	}
	configFilePath := filepath.Join(cwd, "..", "..", "configs", "config.json")
	templateHTMLPath := filepath.Join(cwd, "..", "..", "templates", "html")
	templateMailPath := filepath.Join(cwd, "..", "..", "templates", "email")

	if err := os.Setenv("TEMPLATE_MAIL_PATH", templateMailPath); err != nil {
		log.Fatalf("Error setting environment variable: %v", err)
	}
	if err := os.Setenv("TEMPLATE_HTML_PATH", templateHTMLPath); err != nil {
		log.Fatalf("Error setting environment variable: %v", err)
	}
	if err := os.Setenv("CONFIG_FILE_PATH", configFilePath); err != nil {
		log.Fatalf("Error setting environment variable: %v", err)
	}
	config.LoadConfig()
	logger.Info.Printf("Config: %#v", config.CFG)
}

func TestAuthMiddleware(t *testing.T) {
	gin.SetMode(gin.TestMode)

	setupTestEnvironment()

	tests := []struct {
		name            string
		setupAuth       func(r *http.Request)
		expectedStatus  int
		expectNewCookie bool
		expectedUserID  uint
	}{
		{
			name: "Valid Token",
			setupAuth: func(r *http.Request) {
				user := models.User{ID: 123, RoleID: constants.Roles.Member}
				token, _ := GenerateToken(config.Auth.JWTSecret, &user, "")
				r.AddCookie(&http.Cookie{Name: "jwt", Value: token})
			},
			expectedStatus: http.StatusOK,
			expectedUserID: 123,
		},
		{
			name:           "Missing Cookie",
			setupAuth:      func(r *http.Request) {},
			expectedStatus: http.StatusUnauthorized,
			expectedUserID: 0,
		},
		{
			name: "Invalid Token",
			setupAuth: func(r *http.Request) {
				r.AddCookie(&http.Cookie{Name: "jwt", Value: "InvalidToken"})
			},
			expectedStatus: http.StatusUnauthorized,
			expectedUserID: 0,
		},
		{
			name: "Expired Token with Valid Session",
			setupAuth: func(r *http.Request) {
				sessionID := "test-session"
				token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
					"user_id":    123,
					"role_id":    constants.Roles.Member,
					"session_id": sessionID,
					"exp":        time.Now().Add(-time.Hour).Unix(), // Expired 1 hour ago
				})
				tokenString, _ := token.SignedString([]byte(config.Auth.JWTSecret))
				r.AddCookie(&http.Cookie{Name: "jwt", Value: tokenString})
				UpdateSession(sessionID, 123) // Add a valid session
			},
			expectedStatus:  http.StatusOK,
			expectNewCookie: true,
			expectedUserID:  123,
		},
		{
			name: "Expired Token with Expired Session",
			setupAuth: func(r *http.Request) {
				sessionID := "expired-session"
				token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
					"user_id":    123,
					"role_id":    constants.Roles.Member,
					"session_id": sessionID,
					"exp":        time.Now().Add(-time.Hour).Unix(), // Expired 1 hour ago
				})
				tokenString, _ := token.SignedString([]byte(config.Auth.JWTSecret))
				r.AddCookie(&http.Cookie{Name: "jwt", Value: tokenString})
				// Don't add a session, simulating an expired session
			},
			expectedStatus: http.StatusUnauthorized,
			expectedUserID: 0,
		},
		{
			name: "Invalid Signature",
			setupAuth: func(r *http.Request) {
				token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
					"user_id":    123,
					"session_id": "some-session",
					"exp":        time.Now().Add(time.Hour).Unix(),
				})
				tokenString, _ := token.SignedString([]byte("wrong_secret"))
				r.AddCookie(&http.Cookie{Name: "jwt", Value: tokenString})
			},
			expectedStatus: http.StatusUnauthorized,
			expectedUserID: 0,
		},
		{
			name: "Invalid Signing Method",
			setupAuth: func(r *http.Request) {
				token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{
					"user_id":    123,
					"session_id": "some-session",
					"role_id":    constants.Roles.Member,
					"exp":        time.Now().Add(time.Hour).Unix(),
				})
				tokenString, _ := token.SignedString([]byte(config.Auth.JWTSecret))
				r.AddCookie(&http.Cookie{Name: "jwt", Value: tokenString})
			},
			expectedStatus: http.StatusUnauthorized,
			expectedUserID: 0,
		},
	}

	for _, tt := range tests {
		logger.Error.Print("==============================================================")
		logger.Error.Printf("Testing : %v", tt.name)
		logger.Error.Print("==============================================================")
		t.Run(tt.name, func(t *testing.T) {
			// Setup
			r := gin.New()
			r.Use(AuthMiddleware())
			r.GET("/test", func(c *gin.Context) {
				userID, exists := c.Get("user_id")
				if exists {
					c.JSON(http.StatusOK, gin.H{"user_id": userID})
				} else {
					c.JSON(http.StatusUnauthorized, gin.H{"user_id": 0})
				}
			})

			req, _ := http.NewRequest(http.MethodGet, "/test", nil)
			tt.setupAuth(req)

			w := httptest.NewRecorder()
			r.ServeHTTP(w, req)

			assert.Equal(t, tt.expectedStatus, w.Code)

			if tt.expectedStatus == http.StatusOK {
				var response map[string]uint
				err := json.Unmarshal(w.Body.Bytes(), &response)
				assert.NoError(t, err)
				assert.Equal(t, tt.expectedUserID, response["user_id"])

				// Check if a new cookie was set
				cookies := w.Result().Cookies()
				if tt.expectNewCookie {
					assert.GreaterOrEqual(t, len(cookies), 1)
					assert.Equal(t, "jwt", cookies[0].Name)
					assert.NotEmpty(t, cookies[0].Value)
				} else {
					assert.Equal(t, 0, len(cookies), "Unexpected cookie set")
				}
			} else {
				assert.Equal(t, 0, len(w.Result().Cookies()))
			}
		})
	}
}
