package integration

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

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

	"github.com/treytartt/honeydue-api/internal/admin/dto"
	adminhandlers "github.com/treytartt/honeydue-api/internal/admin/handlers"
	"github.com/treytartt/honeydue-api/internal/apperrors"
	"github.com/treytartt/honeydue-api/internal/config"
	"github.com/treytartt/honeydue-api/internal/handlers"
	"github.com/treytartt/honeydue-api/internal/middleware"
	"github.com/treytartt/honeydue-api/internal/models"
	"github.com/treytartt/honeydue-api/internal/repositories"
	"github.com/treytartt/honeydue-api/internal/services"
	"github.com/treytartt/honeydue-api/internal/testutil"
	"github.com/treytartt/honeydue-api/internal/validator"
)

// ============ Security Regression Test App ============

// SecurityTestApp holds components for security regression integration testing.
type SecurityTestApp struct {
	DB                  *gorm.DB
	Router              *echo.Echo
	SubscriptionService *services.SubscriptionService
	SubscriptionRepo    *repositories.SubscriptionRepository
}

func setupSecurityTest(t *testing.T) *SecurityTestApp {
	db := testutil.SetupTestDB(t)
	testutil.SeedLookupData(t, db)

	// Create repositories
	userRepo := repositories.NewUserRepository(db)
	residenceRepo := repositories.NewResidenceRepository(db)
	taskRepo := repositories.NewTaskRepository(db)
	contractorRepo := repositories.NewContractorRepository(db)
	documentRepo := repositories.NewDocumentRepository(db)
	subscriptionRepo := repositories.NewSubscriptionRepository(db)
	notificationRepo := repositories.NewNotificationRepository(db)

	// Create config
	cfg := &config.Config{
		Security: config.SecurityConfig{
			SecretKey:            "test-secret-key-for-security-tests",
			PasswordResetExpiry:  15 * time.Minute,
			ConfirmationExpiry:   24 * time.Hour,
			MaxPasswordResetRate: 3,
		},
	}

	// Create services
	authService := services.NewAuthService(userRepo, cfg)
	residenceService := services.NewResidenceService(residenceRepo, userRepo, cfg)
	subscriptionService := services.NewSubscriptionService(subscriptionRepo, residenceRepo, taskRepo, contractorRepo, documentRepo)
	taskService := services.NewTaskService(taskRepo, residenceRepo)
	notificationService := services.NewNotificationService(notificationRepo, nil)

	// Wire up subscription service for tier limit enforcement
	residenceService.SetSubscriptionService(subscriptionService)

	// Create handlers
	authHandler := handlers.NewAuthHandler(authService, nil, nil)
	residenceHandler := handlers.NewResidenceHandler(residenceService, nil, nil, true)
	taskHandler := handlers.NewTaskHandler(taskService, nil)
	contractorHandler := handlers.NewContractorHandler(services.NewContractorService(contractorRepo, residenceRepo))
	notificationHandler := handlers.NewNotificationHandler(notificationService)
	subscriptionHandler := handlers.NewSubscriptionHandler(subscriptionService, nil)

	// Create router with real middleware
	e := echo.New()
	e.Validator = validator.NewCustomValidator()
	e.HTTPErrorHandler = apperrors.HTTPErrorHandler

	e.Use(middleware.TimezoneMiddleware())

	// Public routes
	auth := e.Group("/api/auth")
	{
		auth.POST("/register", authHandler.Register)
		auth.POST("/login", authHandler.Login)
	}

	// Protected routes
	authMiddleware := middleware.NewAuthMiddleware(db, nil)
	api := e.Group("/api")
	api.Use(authMiddleware.TokenAuth())
	{
		api.GET("/auth/me", authHandler.CurrentUser)
		api.POST("/auth/logout", authHandler.Logout)

		residences := api.Group("/residences")
		{
			residences.GET("", residenceHandler.ListResidences)
			residences.POST("", residenceHandler.CreateResidence)
			residences.GET("/:id", residenceHandler.GetResidence)
			residences.PUT("/:id", residenceHandler.UpdateResidence)
			residences.DELETE("/:id", residenceHandler.DeleteResidence)
		}

		tasks := api.Group("/tasks")
		{
			tasks.GET("", taskHandler.ListTasks)
			tasks.POST("", taskHandler.CreateTask)
			tasks.GET("/:id", taskHandler.GetTask)
			tasks.PUT("/:id", taskHandler.UpdateTask)
			tasks.DELETE("/:id", taskHandler.DeleteTask)
		}

		completions := api.Group("/completions")
		{
			completions.GET("", taskHandler.ListCompletions)
			completions.POST("", taskHandler.CreateCompletion)
			completions.GET("/:id", taskHandler.GetCompletion)
			completions.DELETE("/:id", taskHandler.DeleteCompletion)
		}

		contractors := api.Group("/contractors")
		{
			contractors.GET("", contractorHandler.ListContractors)
			contractors.POST("", contractorHandler.CreateContractor)
			contractors.GET("/:id", contractorHandler.GetContractor)
		}

		subscription := api.Group("/subscription")
		{
			subscription.GET("/", subscriptionHandler.GetSubscription)
			subscription.GET("/status/", subscriptionHandler.GetSubscriptionStatus)
			subscription.POST("/purchase/", subscriptionHandler.ProcessPurchase)
		}

		notifications := api.Group("/notifications")
		{
			notifications.GET("", notificationHandler.ListNotifications)
		}
	}

	return &SecurityTestApp{
		DB:                  db,
		Router:              e,
		SubscriptionService: subscriptionService,
		SubscriptionRepo:    subscriptionRepo,
	}
}

// registerAndLoginSec registers and logs in a user, returns token and user ID.
func (app *SecurityTestApp) registerAndLoginSec(t *testing.T, username, email, password string) (string, uint) {
	// Register
	registerBody := map[string]string{
		"username": username,
		"email":    email,
		"password": password,
	}
	w := app.makeAuthReq(t, "POST", "/api/auth/register", registerBody, "")
	require.Equal(t, http.StatusCreated, w.Code, "Registration should succeed for %s", username)

	// Login
	loginBody := map[string]string{
		"username": username,
		"password": password,
	}
	w = app.makeAuthReq(t, "POST", "/api/auth/login", loginBody, "")
	require.Equal(t, http.StatusOK, w.Code, "Login should succeed for %s", username)

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

	token := loginResp["token"].(string)
	userMap := loginResp["user"].(map[string]interface{})
	userID := uint(userMap["id"].(float64))

	return token, userID
}

// makeAuthReq creates and sends an HTTP request through the router.
func (app *SecurityTestApp) makeAuthReq(t *testing.T, method, path string, body interface{}, token string) *httptest.ResponseRecorder {
	var reqBody []byte
	var err error
	if body != nil {
		reqBody, err = json.Marshal(body)
		require.NoError(t, err)
	}

	req := httptest.NewRequest(method, path, bytes.NewBuffer(reqBody))
	req.Header.Set("Content-Type", "application/json")
	if token != "" {
		req.Header.Set("Authorization", "Token "+token)
	}

	w := httptest.NewRecorder()
	app.Router.ServeHTTP(w, req)
	return w
}

// ============ Test 1: Path Traversal Blocked ============

// TestE2E_PathTraversal_AllMediaEndpoints_Blocked verifies that the SafeResolvePath
// function (used by all media endpoints) blocks path traversal attempts.
// A document with a traversal URL like ../../../etc/passwd cannot be used to read
// arbitrary files from the filesystem.
func TestE2E_PathTraversal_AllMediaEndpoints_Blocked(t *testing.T) {
	// Test the SafeResolvePath function that guards all three media endpoints:
	//   ServeDocument, ServeDocumentImage, ServeCompletionImage
	// Each calls resolveFilePath -> SafeResolvePath to validate containment.

	traversalPaths := []struct {
		name string
		url  string
	}{
		{"simple dotdot", "../../../etc/passwd"},
		{"nested dotdot", "../../etc/shadow"},
		{"embedded dotdot", "images/../../../../../../etc/passwd"},
		{"deep traversal", "a/b/c/../../../../etc/passwd"},
		{"uploads prefix with dotdot", "../../../etc/passwd"},
	}

	for _, tt := range traversalPaths {
		t.Run(tt.name, func(t *testing.T) {
			// SafeResolvePath must reject all traversal attempts
			_, err := services.SafeResolvePath("/var/uploads", tt.url)
			assert.Error(t, err, "Path traversal should be blocked for: %s", tt.url)
		})
	}

	// Verify that a legitimate path still works
	t.Run("legitimate_path_allowed", func(t *testing.T) {
		result, err := services.SafeResolvePath("/var/uploads", "documents/file.pdf")
		assert.NoError(t, err, "Legitimate path should be allowed")
		assert.Equal(t, "/var/uploads/documents/file.pdf", result)
	})

	// Verify absolute paths are blocked
	t.Run("absolute_path_blocked", func(t *testing.T) {
		_, err := services.SafeResolvePath("/var/uploads", "/etc/passwd")
		assert.Error(t, err, "Absolute paths should be blocked")
	})

	// Verify empty paths are blocked
	t.Run("empty_path_blocked", func(t *testing.T) {
		_, err := services.SafeResolvePath("/var/uploads", "")
		assert.Error(t, err, "Empty paths should be blocked")
	})
}

// ============ Test 2: SQL Injection in Admin Sort ============

// TestE2E_SQLInjection_AdminSort_Blocked verifies that the admin user list endpoint
// uses the allowlist-based sort column sanitization and does not execute injected SQL.
func TestE2E_SQLInjection_AdminSort_Blocked(t *testing.T) {
	db := testutil.SetupTestDB(t)

	// Create admin user handler which uses the sort_by parameter
	adminUserHandler := adminhandlers.NewAdminUserHandler(db)

	// Create a couple of test users to have data to sort
	testutil.CreateTestUser(t, db, "alice", "alice@test.com", "Password123")
	testutil.CreateTestUser(t, db, "bob", "bob@test.com", "Password123")

	// Set up a minimal Echo instance with the admin handler
	e := echo.New()
	e.Validator = validator.NewCustomValidator()
	e.HTTPErrorHandler = apperrors.HTTPErrorHandler
	e.GET("/api/admin/users", adminUserHandler.List)

	injections := []struct {
		name   string
		sortBy string
	}{
		{"DROP TABLE", "created_at; DROP TABLE auth_user; --"},
		{"UNION SELECT", "id UNION SELECT password FROM auth_user"},
		{"subquery", "(SELECT password FROM auth_user LIMIT 1)"},
		{"OR 1=1", "created_at OR 1=1"},
		{"semicolon", "created_at;"},
		{"single quotes", "name'; DROP TABLE auth_user; --"},
	}

	for _, tt := range injections {
		t.Run(tt.name, func(t *testing.T) {
			path := fmt.Sprintf("/api/admin/users?sort_by=%s", tt.sortBy)
			w := testutil.MakeRequest(e, "GET", path, nil, "")

			// Handler should return 200 (using safe default sort), NOT 500
			assert.Equal(t, http.StatusOK, w.Code,
				"Admin user list should succeed with safe default sort, not crash from injection: %s", tt.sortBy)

			// Parse response to verify valid paginated data
			var resp map[string]interface{}
			err := json.Unmarshal(w.Body.Bytes(), &resp)
			assert.NoError(t, err, "Response should be valid JSON")

			// Verify the auth_user table still exists (not dropped)
			var count int64
			dbErr := db.Model(&models.User{}).Count(&count).Error
			assert.NoError(t, dbErr, "auth_user table should still exist after injection attempt")
			assert.GreaterOrEqual(t, count, int64(2), "Users should still be in the database")
		})
	}

	// Verify the DTO allowlist directly
	t.Run("DTO_GetSafeSortBy_rejects_injection", func(t *testing.T) {
		p := dto.PaginationParams{SortBy: "created_at; DROP TABLE auth_user; --"}
		result := p.GetSafeSortBy([]string{"id", "username", "email", "date_joined"}, "date_joined")
		assert.Equal(t, "date_joined", result, "Injection should fall back to default column")
	})
}

// ============ Test 3: IAP Invalid Receipt Does Not Grant Pro ============

// TestE2E_IAP_InvalidReceipt_NoPro verifies that submitting a purchase with
// garbage receipt data does NOT upgrade the user to Pro tier.
func TestE2E_IAP_InvalidReceipt_NoPro(t *testing.T) {
	app := setupSecurityTest(t)
	token, userID := app.registerAndLoginSec(t, "iapuser", "iap@test.com", "Password123")

	// Create initial subscription (free tier)
	sub := &models.UserSubscription{UserID: userID, Tier: models.TierFree}
	require.NoError(t, app.DB.Create(sub).Error)

	// Submit a purchase with garbage receipt data
	purchaseBody := map[string]interface{}{
		"platform":     "ios",
		"receipt_data": "GARBAGE_RECEIPT_DATA_THAT_IS_NOT_VALID",
	}
	w := app.makeAuthReq(t, "POST", "/api/subscription/purchase/", purchaseBody, token)

	// The purchase should fail (Apple client is nil in test environment)
	assert.NotEqual(t, http.StatusOK, w.Code,
		"Purchase with garbage receipt should NOT succeed")

	// Verify user is still on free tier
	updatedSub, err := app.SubscriptionRepo.GetOrCreate(userID)
	require.NoError(t, err)
	assert.Equal(t, models.TierFree, updatedSub.Tier,
		"User should remain on free tier after invalid receipt submission")
}

// ============ Test 4: Completion Transaction Atomicity ============

// TestE2E_CompletionTransaction_Atomic verifies that creating a task completion
// updates both the completion record and the task's NextDueDate together (P1-5/P1-6).
func TestE2E_CompletionTransaction_Atomic(t *testing.T) {
	app := setupSecurityTest(t)
	token, _ := app.registerAndLoginSec(t, "atomicuser", "atomic@test.com", "Password123")

	// Create a residence
	residenceBody := map[string]interface{}{"name": "Atomic Test House"}
	w := app.makeAuthReq(t, "POST", "/api/residences", residenceBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var residenceResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &residenceResp)
	residenceData := residenceResp["data"].(map[string]interface{})
	residenceID := residenceData["id"].(float64)

	// Create a one-time task with a due date
	dueDate := time.Now().Add(7 * 24 * time.Hour).Format("2006-01-02")
	taskBody := map[string]interface{}{
		"residence_id": uint(residenceID),
		"title":        "One-Time Atomic Task",
		"due_date":     dueDate,
	}
	w = app.makeAuthReq(t, "POST", "/api/tasks", taskBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var taskResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskResp)
	taskData := taskResp["data"].(map[string]interface{})
	taskID := taskData["id"].(float64)

	// Verify task has a next_due_date before completion
	w = app.makeAuthReq(t, "GET", "/api/tasks/"+formatID(taskID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var taskBefore map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskBefore)
	assert.NotNil(t, taskBefore["next_due_date"], "Task should have next_due_date before completion")

	// Create completion
	completionBody := map[string]interface{}{
		"task_id": uint(taskID),
		"notes":   "Completed for atomicity test",
	}
	w = app.makeAuthReq(t, "POST", "/api/completions", completionBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var completionResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &completionResp)
	completionData := completionResp["data"].(map[string]interface{})
	completionID := completionData["id"].(float64)
	assert.NotZero(t, completionID, "Completion should be created with valid ID")

	// Verify task is now completed (next_due_date should be nil for one-time task)
	w = app.makeAuthReq(t, "GET", "/api/tasks/"+formatID(taskID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	var taskAfter map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskAfter)
	assert.Nil(t, taskAfter["next_due_date"],
		"One-time task should have nil next_due_date after completion (atomic update)")
	assert.Equal(t, "completed_tasks", taskAfter["kanban_column"],
		"Task should be in completed column after completion")

	// Verify completion record exists
	w = app.makeAuthReq(t, "GET", "/api/completions/"+formatID(completionID), nil, token)
	assert.Equal(t, http.StatusOK, w.Code, "Completion record should exist")
}

// ============ Test 5: Delete Completion Recalculates NextDueDate ============

// TestE2E_DeleteCompletion_RecalculatesNextDueDate verifies that deleting a completion
// on a recurring task recalculates NextDueDate back to the correct value (P1-7).
func TestE2E_DeleteCompletion_RecalculatesNextDueDate(t *testing.T) {
	app := setupSecurityTest(t)
	token, _ := app.registerAndLoginSec(t, "recuruser", "recur@test.com", "Password123")

	// Create a residence
	residenceBody := map[string]interface{}{"name": "Recurring Test House"}
	w := app.makeAuthReq(t, "POST", "/api/residences", residenceBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var residenceResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &residenceResp)
	residenceData := residenceResp["data"].(map[string]interface{})
	residenceID := residenceData["id"].(float64)

	// Get the "Weekly" frequency ID from the database
	var weeklyFreq models.TaskFrequency
	err := app.DB.Where("name = ?", "Weekly").First(&weeklyFreq).Error
	require.NoError(t, err, "Weekly frequency should exist from seed data")

	// Create a recurring (weekly) task with a due date
	dueDate := time.Now().Add(-1 * 24 * time.Hour).Format("2006-01-02")
	taskBody := map[string]interface{}{
		"residence_id": uint(residenceID),
		"title":        "Weekly Recurring Task",
		"frequency_id": weeklyFreq.ID,
		"due_date":     dueDate,
	}
	w = app.makeAuthReq(t, "POST", "/api/tasks", taskBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var taskResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskResp)
	taskData := taskResp["data"].(map[string]interface{})
	taskID := taskData["id"].(float64)

	// Record original next_due_date
	w = app.makeAuthReq(t, "GET", "/api/tasks/"+formatID(taskID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)
	var taskOriginal map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskOriginal)
	originalNextDueDate := taskOriginal["next_due_date"]
	require.NotNil(t, originalNextDueDate, "Recurring task should have initial next_due_date")

	// Create a completion (should advance NextDueDate by 7 days from completion date)
	completionBody := map[string]interface{}{
		"task_id": uint(taskID),
		"notes":   "Weekly completion",
	}
	w = app.makeAuthReq(t, "POST", "/api/completions", completionBody, token)
	require.Equal(t, http.StatusCreated, w.Code)

	var completionResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &completionResp)
	completionData := completionResp["data"].(map[string]interface{})
	completionID := completionData["id"].(float64)

	// Verify NextDueDate advanced
	w = app.makeAuthReq(t, "GET", "/api/tasks/"+formatID(taskID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)
	var taskAfterCompletion map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskAfterCompletion)
	advancedNextDueDate := taskAfterCompletion["next_due_date"]
	assert.NotNil(t, advancedNextDueDate, "Recurring task should still have next_due_date after completion")
	assert.NotEqual(t, originalNextDueDate, advancedNextDueDate,
		"NextDueDate should have advanced after completion")

	// Delete the completion
	w = app.makeAuthReq(t, "DELETE", "/api/completions/"+formatID(completionID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)

	// Verify NextDueDate was recalculated back to original due date
	w = app.makeAuthReq(t, "GET", "/api/tasks/"+formatID(taskID), nil, token)
	require.Equal(t, http.StatusOK, w.Code)
	var taskAfterDelete map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &taskAfterDelete)
	restoredNextDueDate := taskAfterDelete["next_due_date"]

	// After deleting the only completion, NextDueDate should be restored to the original DueDate
	assert.NotNil(t, restoredNextDueDate, "NextDueDate should be restored after deleting the only completion")
	assert.Equal(t, originalNextDueDate, restoredNextDueDate,
		"NextDueDate should be recalculated back to original due date after completion deletion")
}

// ============ Test 6: Tier Limits Enforced ============

// TestE2E_TierLimits_Enforced verifies that a free-tier user cannot exceed the
// configured property limit.
func TestE2E_TierLimits_Enforced(t *testing.T) {
	app := setupSecurityTest(t)
	token, userID := app.registerAndLoginSec(t, "tieruser", "tier@test.com", "Password123")

	// Enable global limitations
	app.DB.Where("1=1").Delete(&models.SubscriptionSettings{})
	settings := &models.SubscriptionSettings{EnableLimitations: true}
	require.NoError(t, app.DB.Create(settings).Error)

	// Set free tier limit to 1 property
	one := 1
	app.DB.Where("tier = ?", models.TierFree).Delete(&models.TierLimits{})
	freeLimits := &models.TierLimits{
		Tier:            models.TierFree,
		PropertiesLimit: &one,
	}
	require.NoError(t, app.DB.Create(freeLimits).Error)

	// Ensure user is on free tier
	sub, err := app.SubscriptionRepo.GetOrCreate(userID)
	require.NoError(t, err)
	require.Equal(t, models.TierFree, sub.Tier)

	// First residence should succeed
	residenceBody := map[string]interface{}{"name": "First Property"}
	w := app.makeAuthReq(t, "POST", "/api/residences", residenceBody, token)
	require.Equal(t, http.StatusCreated, w.Code, "First residence should be allowed within limit")

	// Second residence should be blocked
	residenceBody2 := map[string]interface{}{"name": "Second Property (over limit)"}
	w = app.makeAuthReq(t, "POST", "/api/residences", residenceBody2, token)
	assert.Equal(t, http.StatusForbidden, w.Code,
		"Second residence should be blocked by tier limit")

	// Verify error response
	var errResp map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &errResp)
	require.NoError(t, err)
	assert.Contains(t, fmt.Sprintf("%v", errResp), "limit",
		"Error response should reference the limit")
}

// ============ Test 7: Auth Assertion -- No Panics on Missing User ============

// TestE2E_AuthAssertion_NoPanics verifies that all protected endpoints return
// 401 Unauthorized (not 500 panic) when no auth token is provided.
func TestE2E_AuthAssertion_NoPanics(t *testing.T) {
	app := setupSecurityTest(t)

	// Make requests to protected endpoints WITHOUT any token.
	endpoints := []struct {
		name   string
		method string
		path   string
	}{
		{"ListTasks", "GET", "/api/tasks"},
		{"CreateTask", "POST", "/api/tasks"},
		{"GetTask", "GET", "/api/tasks/1"},
		{"ListResidences", "GET", "/api/residences"},
		{"CreateResidence", "POST", "/api/residences"},
		{"GetResidence", "GET", "/api/residences/1"},
		{"ListCompletions", "GET", "/api/completions"},
		{"CreateCompletion", "POST", "/api/completions"},
		{"ListContractors", "GET", "/api/contractors"},
		{"CreateContractor", "POST", "/api/contractors"},
		{"GetSubscription", "GET", "/api/subscription/"},
		{"SubscriptionStatus", "GET", "/api/subscription/status/"},
		{"ProcessPurchase", "POST", "/api/subscription/purchase/"},
		{"ListNotifications", "GET", "/api/notifications"},
		{"CurrentUser", "GET", "/api/auth/me"},
	}

	for _, ep := range endpoints {
		t.Run(ep.name, func(t *testing.T) {
			w := app.makeAuthReq(t, ep.method, ep.path, nil, "")
			assert.Equal(t, http.StatusUnauthorized, w.Code,
				"Endpoint %s %s should return 401, not panic with 500", ep.method, ep.path)
		})
	}

	// Also test with an invalid token (should be 401, not 500)
	t.Run("InvalidToken", func(t *testing.T) {
		w := app.makeAuthReq(t, "GET", "/api/tasks", nil, "completely-invalid-token-xyz")
		assert.Equal(t, http.StatusUnauthorized, w.Code,
			"Invalid token should return 401, not panic")
	})
}

// ============ Test 8: Notification Limit Capped ============

// TestE2E_NotificationLimit_Capped verifies that the notification list endpoint
// caps the limit parameter to 200 even if the client requests more.
func TestE2E_NotificationLimit_Capped(t *testing.T) {
	app := setupSecurityTest(t)
	token, userID := app.registerAndLoginSec(t, "notifuser", "notif@test.com", "Password123")

	// Create 210 notifications directly in the database
	for i := 0; i < 210; i++ {
		notification := &models.Notification{
			UserID:           userID,
			NotificationType: models.NotificationTaskCompleted,
			Title:            fmt.Sprintf("Test Notification %d", i),
			Body:             fmt.Sprintf("Body for notification %d", i),
		}
		require.NoError(t, app.DB.Create(notification).Error)
	}

	// Request with limit=999 (should be capped to 200 by the handler)
	w := app.makeAuthReq(t, "GET", "/api/notifications?limit=999", nil, token)
	require.Equal(t, http.StatusOK, w.Code)

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

	count := int(notifResp["count"].(float64))
	assert.LessOrEqual(t, count, 200,
		"Notification count should be capped at 200 even when requesting limit=999")

	results := notifResp["results"].([]interface{})
	assert.LessOrEqual(t, len(results), 200,
		"Notification results should have at most 200 items")
}
