package handlers

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"strconv"
	"sync"
	"testing"
	"time"

	"github.com/Wikid82/charon/backend/internal/models"
	"github.com/Wikid82/charon/backend/internal/services"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/gorm"
)

func setupUserHandler(t *testing.T) (*UserHandler, *gorm.DB) {
	db := OpenTestDB(t)
	_ = db.AutoMigrate(&models.User{}, &models.Setting{}, &models.SecurityAudit{})
	return NewUserHandler(db), db
}

func TestMapsKeys(t *testing.T) {
	t.Parallel()

	keys := mapsKeys(map[string]any{"email": "a@example.com", "name": "Alice", "enabled": true})
	assert.Len(t, keys, 3)
	assert.Contains(t, keys, "email")
	assert.Contains(t, keys, "name")
	assert.Contains(t, keys, "enabled")
}

func TestUserHandler_actorFromContext(t *testing.T) {
	t.Parallel()

	handler, _ := setupUserHandler(t)

	rec1 := httptest.NewRecorder()
	ctx1, _ := gin.CreateTestContext(rec1)
	req1 := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
	req1.RemoteAddr = "198.51.100.10:1234"
	ctx1.Request = req1
	assert.Equal(t, "198.51.100.10", handler.actorFromContext(ctx1))

	rec2 := httptest.NewRecorder()
	ctx2, _ := gin.CreateTestContext(rec2)
	req2 := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
	ctx2.Request = req2
	ctx2.Set("userID", uint(42))
	assert.Equal(t, "42", handler.actorFromContext(ctx2))
}

func TestUserHandler_logUserAudit_NoOpBranches(t *testing.T) {
	t.Parallel()

	handler, _ := setupUserHandler(t)
	rec := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(rec)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/", http.NoBody)

	// nil user should be a no-op
	handler.logUserAudit(ctx, "noop", nil, map[string]any{"x": 1})

	// nil security service should be a no-op
	handler.securitySvc = nil
	handler.logUserAudit(ctx, "noop", &models.User{UUID: uuid.NewString(), Email: "user@example.com"}, map[string]any{"x": 1})
}

func TestUserHandler_GetSetupStatus(t *testing.T) {
	handler, db := setupUserHandler(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.GET("/setup", handler.GetSetupStatus)

	// No users -> setup required
	req, _ := http.NewRequest("GET", "/setup", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)
	assert.Contains(t, w.Body.String(), "\"setupRequired\":true")

	// Create user -> setup not required
	db.Create(&models.User{Email: "test@example.com"})
	w = httptest.NewRecorder()
	r.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)
	assert.Contains(t, w.Body.String(), "\"setupRequired\":false")
}

func TestUserHandler_Setup(t *testing.T) {
	handler, _ := setupUserHandler(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.POST("/setup", handler.Setup)

	// 1. Invalid JSON (Before setup is done)
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("POST", "/setup", bytes.NewBuffer([]byte("invalid json")))
	req.Header.Set("Content-Type", "application/json")
	r.ServeHTTP(w, req)
	assert.Equal(t, http.StatusBadRequest, w.Code)

	// 2. Valid Setup
	body := map[string]string{
		"name":     "Admin",
		"email":    "admin@example.com",
		"password": "password123",
	}
	jsonBody, _ := json.Marshal(body)
	req, _ = http.NewRequest("POST", "/setup", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w = httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)
	assert.Contains(t, w.Body.String(), "Setup completed successfully")

	// 3. Try again -> should fail (already setup)
	w = httptest.NewRecorder()
	req, _ = http.NewRequest("POST", "/setup", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	r.ServeHTTP(w, req)
	assert.Equal(t, http.StatusForbidden, w.Code)
}

func TestUserHandler_Setup_OneWayInvariant_ReentryRejectedAndSingleUser(t *testing.T) {
	handler, db := setupUserHandler(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.POST("/setup", handler.Setup)

	initialBody := map[string]string{
		"name":     "Admin",
		"email":    "admin@example.com",
		"password": "password123",
	}
	initialJSON, _ := json.Marshal(initialBody)

	firstReq := httptest.NewRequest(http.MethodPost, "/setup", bytes.NewBuffer(initialJSON))
	firstReq.Header.Set("Content-Type", "application/json")
	firstResp := httptest.NewRecorder()
	r.ServeHTTP(firstResp, firstReq)
	require.Equal(t, http.StatusCreated, firstResp.Code)

	secondBody := map[string]string{
		"name":     "Different Admin",
		"email":    "different@example.com",
		"password": "password123",
	}
	secondJSON, _ := json.Marshal(secondBody)
	secondReq := httptest.NewRequest(http.MethodPost, "/setup", bytes.NewBuffer(secondJSON))
	secondReq.Header.Set("Content-Type", "application/json")
	secondResp := httptest.NewRecorder()
	r.ServeHTTP(secondResp, secondReq)

	require.Equal(t, http.StatusForbidden, secondResp.Code)

	var userCount int64
	require.NoError(t, db.Model(&models.User{}).Count(&userCount).Error)
	assert.Equal(t, int64(1), userCount)
}

func TestUserHandler_Setup_ConcurrentAttemptInvariant(t *testing.T) {
	handler, db := setupUserHandler(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.POST("/setup", handler.Setup)

	concurrency := 6
	start := make(chan struct{})
	statuses := make(chan int, concurrency)

	var wg sync.WaitGroup
	for i := 0; i < concurrency; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			<-start

			body := map[string]string{
				"name":     "Admin",
				"email":    "admin@example.com",
				"password": "password123",
			}
			jsonBody, _ := json.Marshal(body)

			req := httptest.NewRequest(http.MethodPost, "/setup", bytes.NewBuffer(jsonBody))
			req.Header.Set("Content-Type", "application/json")
			resp := httptest.NewRecorder()
			r.ServeHTTP(resp, req)
			statuses <- resp.Code
		}()
	}

	close(start)
	wg.Wait()
	close(statuses)

	createdCount := 0
	forbiddenOrConflictCount := 0
	for status := range statuses {
		if status == http.StatusCreated {
			createdCount++
			continue
		}

		if status == http.StatusForbidden || status == http.StatusConflict {
			forbiddenOrConflictCount++
			continue
		}

		t.Fatalf("unexpected setup concurrency status: %d", status)
	}

	assert.Equal(t, 1, createdCount)
	assert.Equal(t, concurrency-1, forbiddenOrConflictCount)

	var userCount int64
	require.NoError(t, db.Model(&models.User{}).Count(&userCount).Error)
	assert.Equal(t, int64(1), userCount)
}

func TestUserHandler_Setup_ResponseSecretEchoContract(t *testing.T) {
	handler, _ := setupUserHandler(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.POST("/setup", handler.Setup)

	body := map[string]string{
		"name":     "Admin",
		"email":    "admin@example.com",
		"password": "password123",
	}
	jsonBody, _ := json.Marshal(body)

	req := httptest.NewRequest(http.MethodPost, "/setup", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	resp := httptest.NewRecorder()
	r.ServeHTTP(resp, req)
	require.Equal(t, http.StatusCreated, resp.Code)

	var payload map[string]any
	require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &payload))

	userValue, ok := payload["user"]
	require.True(t, ok)
	userMap, ok := userValue.(map[string]any)
	require.True(t, ok)

	_, hasAPIKey := userMap["api_key"]
	_, hasPassword := userMap["password"]
	_, hasPasswordHash := userMap["password_hash"]
	_, hasInviteToken := userMap["invite_token"]

	assert.False(t, hasAPIKey)
	assert.False(t, hasPassword)
	assert.False(t, hasPasswordHash)
	assert.False(t, hasInviteToken)
}

func TestUserHandler_GetProfile_SecretEchoContract(t *testing.T) {
	handler, db := setupUserHandler(t)

	user := &models.User{
		UUID:         uuid.NewString(),
		Email:        "profile@example.com",
		Name:         "Profile User",
		APIKey:       "real-secret-api-key",
		InviteToken:  "invite-secret-token",
		PasswordHash: "hashed-password-value",
	}
	require.NoError(t, db.Create(user).Error)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("userID", user.ID)
		c.Next()
	})
	r.GET("/profile", handler.GetProfile)

	req := httptest.NewRequest(http.MethodGet, "/profile", http.NoBody)
	resp := httptest.NewRecorder()
	r.ServeHTTP(resp, req)

	require.Equal(t, http.StatusOK, resp.Code)
	var payload map[string]any
	require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &payload))

	_, hasAPIKey := payload["api_key"]
	_, hasPassword := payload["password"]
	_, hasPasswordHash := payload["password_hash"]
	_, hasInviteToken := payload["invite_token"]

	assert.False(t, hasAPIKey)
	assert.False(t, hasPassword)
	assert.False(t, hasPasswordHash)
	assert.False(t, hasInviteToken)
	assert.Equal(t, "********", payload["api_key_masked"])
}

func TestUserHandler_ListUsers_SecretEchoContract(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	user := &models.User{
		UUID:         uuid.NewString(),
		Email:        "user@example.com",
		Name:         "User",
		Role:         "user",
		APIKey:       "raw-api-key",
		InviteToken:  "raw-invite-token",
		PasswordHash: "raw-password-hash",
	}
	require.NoError(t, db.Create(user).Error)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.GET("/users", handler.ListUsers)

	req := httptest.NewRequest(http.MethodGet, "/users", http.NoBody)
	resp := httptest.NewRecorder()
	r.ServeHTTP(resp, req)

	require.Equal(t, http.StatusOK, resp.Code)
	var users []map[string]any
	require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &users))
	require.Len(t, users, 1)

	_, hasAPIKey := users[0]["api_key"]
	_, hasPassword := users[0]["password"]
	_, hasPasswordHash := users[0]["password_hash"]
	_, hasInviteToken := users[0]["invite_token"]

	assert.False(t, hasAPIKey)
	assert.False(t, hasPassword)
	assert.False(t, hasPasswordHash)
	assert.False(t, hasInviteToken)
}

func TestUserHandler_Setup_DBError(t *testing.T) {
	// Can't easily mock DB error with sqlite memory unless we close it or something.
	// But we can try to insert duplicate email if we had a unique constraint and pre-seeded data,
	// but Setup checks if ANY user exists first.
	// So if we have a user, it returns Forbidden.
	// If we don't, it tries to create.
	// If we want Create to fail, maybe invalid data that passes binding but fails DB constraint?
	// User model has validation?
	// Let's try empty password if allowed by binding but rejected by DB?
	// Or very long string?
}

func TestUserHandler_RegenerateAPIKey(t *testing.T) {
	handler, db := setupUserHandler(t)

	user := &models.User{Email: "api@example.com"}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("userID", user.ID)
		c.Next()
	})
	r.POST("/api-key", handler.RegenerateAPIKey)

	req, _ := http.NewRequest("POST", "/api-key", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var resp map[string]any
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to unmarshal response")
	assert.Equal(t, "API key regenerated successfully", resp["message"])
	assert.Equal(t, "********", resp["api_key_masked"])

	// Verify DB
	var updatedUser models.User
	db.First(&updatedUser, user.ID)
	assert.NotEmpty(t, updatedUser.APIKey)
}

func TestUserHandler_GetProfile(t *testing.T) {
	handler, db := setupUserHandler(t)

	user := &models.User{
		Email:  "profile@example.com",
		Name:   "Profile User",
		APIKey: "existing-key",
	}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("userID", user.ID)
		c.Next()
	})
	r.GET("/profile", handler.GetProfile)

	req, _ := http.NewRequest("GET", "/profile", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var resp models.User
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to unmarshal response")
	assert.Equal(t, user.Email, resp.Email)
	// APIKey is not exposed in JSON (json:"-" tag), so it should be empty in response
	assert.Empty(t, resp.APIKey, "APIKey should not be exposed in profile response")
}

func TestUserHandler_RegisterRoutes(t *testing.T) {
	handler, _ := setupUserHandler(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	api := r.Group("/api")
	handler.RegisterRoutes(api)

	routes := r.Routes()
	expectedRoutes := map[string]string{
		"/api/setup":              "GET,POST",
		"/api/profile":            "GET",
		"/api/regenerate-api-key": "POST",
	}

	for path := range expectedRoutes {
		found := false
		for _, route := range routes {
			if route.Path == path {
				found = true
				break
			}
		}
		assert.True(t, found, "Route %s not found", path)
	}
}

func TestUserHandler_Errors(t *testing.T) {
	handler, db := setupUserHandler(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()

	// Middleware to simulate missing userID
	r.GET("/profile-no-auth", func(c *gin.Context) {
		// No userID set
		handler.GetProfile(c)
	})
	r.POST("/api-key-no-auth", func(c *gin.Context) {
		// No userID set
		handler.RegenerateAPIKey(c)
	})

	// Middleware to simulate non-existent user
	r.GET("/profile-not-found", func(c *gin.Context) {
		c.Set("userID", uint(99999))
		handler.GetProfile(c)
	})
	r.POST("/api-key-not-found", func(c *gin.Context) {
		c.Set("userID", uint(99999))
		handler.RegenerateAPIKey(c)
	})

	// Test Unauthorized
	req, _ := http.NewRequest("GET", "/profile-no-auth", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	assert.Equal(t, http.StatusUnauthorized, w.Code)

	req, _ = http.NewRequest("POST", "/api-key-no-auth", http.NoBody)
	w = httptest.NewRecorder()
	r.ServeHTTP(w, req)
	assert.Equal(t, http.StatusUnauthorized, w.Code)

	// Test Not Found (GetProfile)
	req, _ = http.NewRequest("GET", "/profile-not-found", http.NoBody)
	w = httptest.NewRecorder()
	r.ServeHTTP(w, req)
	assert.Equal(t, http.StatusNotFound, w.Code)

	// Test DB Error (RegenerateAPIKey) - Hard to mock DB error on update with sqlite memory,
	// but we can try to update a non-existent user which GORM Update might not treat as error unless we check RowsAffected.
	// The handler code: if err := h.DB.Model(&models.User{}).Where("id = ?", userID).Update("api_key", apiKey).Error; err != nil
	// Update on non-existent record usually returns nil error in GORM unless configured otherwise.
	// However, let's see if we can force an error by closing DB? No, shared DB.
	// We can drop the table?
	_ = db.Migrator().DropTable(&models.User{})
	req, _ = http.NewRequest("POST", "/api-key-not-found", http.NoBody)
	w = httptest.NewRecorder()
	r.ServeHTTP(w, req)
	// If table missing, Update should fail
	assert.Equal(t, http.StatusInternalServerError, w.Code)
}

func TestUserHandler_UpdateProfile(t *testing.T) {
	handler, db := setupUserHandler(t)

	// Create user
	user := &models.User{
		UUID:   uuid.NewString(),
		Email:  "test@example.com",
		Name:   "Test User",
		APIKey: uuid.NewString(),
	}
	_ = user.SetPassword("password123")
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("userID", user.ID)
		c.Next()
	})
	r.PUT("/profile", handler.UpdateProfile)

	// 1. Success - Name only
	t.Run("Success Name Only", func(t *testing.T) {
		body := map[string]string{
			"name":  "Updated Name",
			"email": "test@example.com",
		}
		jsonBody, _ := json.Marshal(body)
		req := httptest.NewRequest("PUT", "/profile", bytes.NewBuffer(jsonBody))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code)
		var updatedUser models.User
		db.First(&updatedUser, user.ID)
		assert.Equal(t, "Updated Name", updatedUser.Name)
	})

	// 2. Success - Email change with password
	t.Run("Success Email Change", func(t *testing.T) {
		body := map[string]string{
			"name":             "Updated Name",
			"email":            "newemail@example.com",
			"current_password": "password123",
		}
		jsonBody, _ := json.Marshal(body)
		req := httptest.NewRequest("PUT", "/profile", bytes.NewBuffer(jsonBody))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code)
		var updatedUser models.User
		db.First(&updatedUser, user.ID)
		assert.Equal(t, "newemail@example.com", updatedUser.Email)
	})

	// 3. Fail - Email change without password
	t.Run("Fail Email Change No Password", func(t *testing.T) {
		// Reset email
		db.Model(user).Update("email", "test@example.com")

		body := map[string]string{
			"name":  "Updated Name",
			"email": "another@example.com",
		}
		jsonBody, _ := json.Marshal(body)
		req := httptest.NewRequest("PUT", "/profile", bytes.NewBuffer(jsonBody))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		assert.Equal(t, http.StatusBadRequest, w.Code)
	})

	// 4. Fail - Email change wrong password
	t.Run("Fail Email Change Wrong Password", func(t *testing.T) {
		body := map[string]string{
			"name":             "Updated Name",
			"email":            "another@example.com",
			"current_password": "wrongpassword",
		}
		jsonBody, _ := json.Marshal(body)
		req := httptest.NewRequest("PUT", "/profile", bytes.NewBuffer(jsonBody))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		assert.Equal(t, http.StatusUnauthorized, w.Code)
	})

	// 5. Fail - Email already in use
	t.Run("Fail Email In Use", func(t *testing.T) {
		// Create another user
		otherUser := &models.User{
			UUID:   uuid.NewString(),
			Email:  "other@example.com",
			Name:   "Other User",
			APIKey: uuid.NewString(),
		}
		db.Create(otherUser)

		body := map[string]string{
			"name":  "Updated Name",
			"email": "other@example.com",
		}
		jsonBody, _ := json.Marshal(body)
		req := httptest.NewRequest("PUT", "/profile", bytes.NewBuffer(jsonBody))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		assert.Equal(t, http.StatusConflict, w.Code)
	})
}

func TestUserHandler_UpdateProfile_Errors(t *testing.T) {
	handler, _ := setupUserHandler(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()

	// 1. Unauthorized (no userID)
	r.PUT("/profile-no-auth", handler.UpdateProfile)
	req, _ := http.NewRequest("PUT", "/profile-no-auth", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	assert.Equal(t, http.StatusUnauthorized, w.Code)

	// Middleware for subsequent tests
	r.Use(func(c *gin.Context) {
		c.Set("userID", uint(999)) // Non-existent ID
		c.Next()
	})
	r.PUT("/profile", handler.UpdateProfile)

	// 2. BindJSON error
	req, _ = http.NewRequest("PUT", "/profile", bytes.NewBufferString("invalid"))
	req.Header.Set("Content-Type", "application/json")
	w = httptest.NewRecorder()
	r.ServeHTTP(w, req)
	assert.Equal(t, http.StatusBadRequest, w.Code)

	// 3. User not found
	body := map[string]string{"name": "New Name", "email": "new@example.com"}
	jsonBody, _ := json.Marshal(body)
	req, _ = http.NewRequest("PUT", "/profile", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w = httptest.NewRecorder()
	r.ServeHTTP(w, req)
	assert.Equal(t, http.StatusNotFound, w.Code)
}

// ============= User Management Tests (Admin functions) =============

func setupUserHandlerWithProxyHosts(t *testing.T) (*UserHandler, *gorm.DB) {
	db := OpenTestDB(t)
	_ = db.AutoMigrate(&models.User{}, &models.Setting{}, &models.ProxyHost{}, &models.SecurityAudit{})
	return NewUserHandler(db), db
}

func TestUserHandler_ListUsers_NonAdmin(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "user")
		c.Next()
	})
	r.GET("/users", handler.ListUsers)

	req := httptest.NewRequest("GET", "/users", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

func TestUserHandler_ListUsers_Admin(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create users with unique API keys
	user1 := &models.User{UUID: uuid.NewString(), Email: "user1@example.com", Name: "User 1", APIKey: uuid.NewString()}
	user2 := &models.User{UUID: uuid.NewString(), Email: "user2@example.com", Name: "User 2", APIKey: uuid.NewString()}
	db.Create(user1)
	db.Create(user2)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.GET("/users", handler.ListUsers)

	req := httptest.NewRequest("GET", "/users", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var users []map[string]any
	err := json.Unmarshal(w.Body.Bytes(), &users)
	require.NoError(t, err, "Failed to unmarshal response")
	assert.Len(t, users, 2)
}

func TestUserHandler_CreateUser_NonAdmin(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "user")
		c.Next()
	})
	r.POST("/users", handler.CreateUser)

	body := map[string]any{
		"email":    "new@example.com",
		"name":     "New User",
		"password": "password123",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

func TestUserHandler_CreateUser_Admin(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", uint(99))
		c.Next()
	})
	r.POST("/users", handler.CreateUser)

	body := map[string]any{
		"email":    "newuser@example.com",
		"name":     "New User",
		"password": "password123",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)
	handler.securitySvc.Flush()

	var audit models.SecurityAudit
	require.NoError(t, db.Where("action = ? AND event_category = ?", "user_create", "user").First(&audit).Error)
	assert.Equal(t, "99", audit.Actor)
}

func TestUserHandler_CreateUser_InvalidJSON(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users", handler.CreateUser)

	req := httptest.NewRequest("POST", "/users", bytes.NewBufferString("invalid"))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestUserHandler_CreateUser_DuplicateEmail(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	existing := &models.User{UUID: uuid.NewString(), Email: "existing@example.com", Name: "Existing"}
	db.Create(existing)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users", handler.CreateUser)

	body := map[string]any{
		"email":    "existing@example.com",
		"name":     "New User",
		"password": "password123",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusConflict, w.Code)
}

func TestUserHandler_CreateUser_WithPermittedHosts(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	host := &models.ProxyHost{Name: "Host 1", DomainNames: "host1.example.com", Enabled: true}
	db.Create(host)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users", handler.CreateUser)

	body := map[string]any{
		"email":           "withhosts@example.com",
		"name":            "User With Hosts",
		"password":        "password123",
		"permission_mode": "deny_all",
		"permitted_hosts": []uint{host.ID},
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)
}

func TestUserHandler_GetUser_NonAdmin(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "user")
		c.Next()
	})
	r.GET("/users/:id", handler.GetUser)

	req := httptest.NewRequest("GET", "/users/1", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

func TestUserHandler_GetUser_InvalidID(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.GET("/users/:id", handler.GetUser)

	req := httptest.NewRequest("GET", "/users/invalid", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestUserHandler_GetUser_NotFound(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.GET("/users/:id", handler.GetUser)

	req := httptest.NewRequest("GET", "/users/999", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
}

func TestUserHandler_GetUser_Success(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	user := &models.User{UUID: uuid.NewString(), Email: "getuser@example.com", Name: "Get User"}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.GET("/users/:id", handler.GetUser)

	req := httptest.NewRequest("GET", "/users/1", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestUserHandler_UpdateUser_NonAdmin(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "user")
		c.Next()
	})
	r.PUT("/users/:id", handler.UpdateUser)

	body := map[string]any{"name": "Updated"}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("PUT", "/users/1", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

func TestUserHandler_UpdateUser_InvalidID(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.PUT("/users/:id", handler.UpdateUser)

	body := map[string]any{"name": "Updated"}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("PUT", "/users/invalid", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestUserHandler_UpdateUser_InvalidJSON(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create user first
	user := &models.User{UUID: uuid.NewString(), Email: "toupdate@example.com", Name: "To Update", APIKey: uuid.NewString()}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.PUT("/users/:id", handler.UpdateUser)

	req := httptest.NewRequest("PUT", "/users/1", bytes.NewBufferString("invalid"))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestUserHandler_UpdateUser_NotFound(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.PUT("/users/:id", handler.UpdateUser)

	body := map[string]any{"name": "Updated"}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("PUT", "/users/999", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
}

func TestUserHandler_UpdateUser_Success(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	user := &models.User{UUID: uuid.NewString(), Email: "update@example.com", Name: "Original", Role: "user"}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", uint(11))
		c.Next()
	})
	r.PUT("/users/:id", handler.UpdateUser)

	body := map[string]any{
		"name":    "Updated Name",
		"enabled": true,
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("PUT", "/users/1", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	handler.securitySvc.Flush()

	var audit models.SecurityAudit
	require.NoError(t, db.Where("action = ? AND event_category = ?", "user_update", "user").First(&audit).Error)
	assert.Equal(t, user.UUID, audit.ResourceUUID)
}

func TestUserHandler_UpdateUser_PasswordReset(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	user := &models.User{UUID: uuid.NewString(), Email: "reset@example.com", Name: "Reset User", Role: "user"}
	require.NoError(t, user.SetPassword("oldpassword123"))
	lockUntil := time.Now().Add(10 * time.Minute)
	user.FailedLoginAttempts = 4
	user.LockedUntil = &lockUntil
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.PUT("/users/:id", handler.UpdateUser)

	body := map[string]any{
		"password": "newpassword123",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("PUT", "/users/1", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var updated models.User
	db.First(&updated, user.ID)
	assert.True(t, updated.CheckPassword("newpassword123"))
	assert.False(t, updated.CheckPassword("oldpassword123"))
	assert.Equal(t, 0, updated.FailedLoginAttempts)
	assert.Nil(t, updated.LockedUntil)
}

func TestUserHandler_DeleteUser_NonAdmin(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "user")
		c.Next()
	})
	r.DELETE("/users/:id", handler.DeleteUser)

	req := httptest.NewRequest("DELETE", "/users/1", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

func TestUserHandler_DeleteUser_InvalidID(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.DELETE("/users/:id", handler.DeleteUser)

	req := httptest.NewRequest("DELETE", "/users/invalid", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestUserHandler_DeleteUser_NotFound(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", uint(1)) // Current user ID (different from target)
		c.Next()
	})
	r.DELETE("/users/:id", handler.DeleteUser)

	req := httptest.NewRequest("DELETE", "/users/999", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
}

func TestUserHandler_DeleteUser_Success(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	user := &models.User{UUID: uuid.NewString(), Email: "delete@example.com", Name: "Delete Me"}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", uint(999)) // Different user
		c.Next()
	})
	r.DELETE("/users/:id", handler.DeleteUser)

	req := httptest.NewRequest("DELETE", "/users/1", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	handler.securitySvc.Flush()

	var audit models.SecurityAudit
	require.NoError(t, db.Where("action = ? AND event_category = ?", "user_delete", "user").First(&audit).Error)
	assert.Equal(t, user.UUID, audit.ResourceUUID)
}

func TestUserHandler_DeleteUser_CannotDeleteSelf(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	user := &models.User{UUID: uuid.NewString(), Email: "self@example.com", Name: "Self"}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", user.ID) // Same user
		c.Next()
	})
	r.DELETE("/users/:id", handler.DeleteUser)

	req := httptest.NewRequest("DELETE", "/users/1", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

func TestUserHandler_UpdateUserPermissions_NonAdmin(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "user")
		c.Next()
	})
	r.PUT("/users/:id/permissions", handler.UpdateUserPermissions)

	body := map[string]any{"permission_mode": "allow_all"}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("PUT", "/users/1/permissions", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

func TestUserHandler_UpdateUserPermissions_InvalidID(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.PUT("/users/:id/permissions", handler.UpdateUserPermissions)

	body := map[string]any{"permission_mode": "allow_all"}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("PUT", "/users/invalid/permissions", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestUserHandler_UpdateUserPermissions_InvalidJSON(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create a user first
	user := &models.User{
		UUID:    uuid.NewString(),
		APIKey:  uuid.NewString(),
		Email:   "perms-invalid@example.com",
		Name:    "Perms Invalid Test",
		Role:    "user",
		Enabled: true,
	}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.PUT("/users/:id/permissions", handler.UpdateUserPermissions)

	req := httptest.NewRequest("PUT", "/users/"+strconv.FormatUint(uint64(user.ID), 10)+"/permissions", bytes.NewBufferString("invalid"))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestUserHandler_UpdateUserPermissions_NotFound(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.PUT("/users/:id/permissions", handler.UpdateUserPermissions)

	body := map[string]any{"permission_mode": "allow_all"}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("PUT", "/users/999/permissions", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
}

func TestUserHandler_UpdateUserPermissions_Success(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	host := &models.ProxyHost{Name: "Host 1", DomainNames: "host1.example.com", Enabled: true}
	db.Create(host)

	user := &models.User{
		UUID:           uuid.NewString(),
		Email:          "perms@example.com",
		Name:           "Perms User",
		PermissionMode: models.PermissionModeAllowAll,
	}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.PUT("/users/:id/permissions", handler.UpdateUserPermissions)

	body := map[string]any{
		"permission_mode": "deny_all",
		"permitted_hosts": []uint{host.ID},
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("PUT", "/users/1/permissions", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestUserHandler_ValidateInvite_MissingToken(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.GET("/invite/validate", handler.ValidateInvite)

	req := httptest.NewRequest("GET", "/invite/validate", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestUserHandler_ValidateInvite_InvalidToken(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.GET("/invite/validate", handler.ValidateInvite)

	req := httptest.NewRequest("GET", "/invite/validate?token=invalidtoken", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
}

func TestUserHandler_ValidateInvite_ExpiredToken(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	expiredTime := time.Now().Add(-24 * time.Hour) // Expired yesterday
	user := &models.User{
		UUID:          uuid.NewString(),
		Email:         "expired@example.com",
		Name:          "Expired Invite",
		InviteToken:   "expiredtoken123",
		InviteExpires: &expiredTime,
		InviteStatus:  "pending",
	}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.GET("/invite/validate", handler.ValidateInvite)

	req := httptest.NewRequest("GET", "/invite/validate?token=expiredtoken123", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusGone, w.Code)
}

func TestUserHandler_ValidateInvite_AlreadyAccepted(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	expiresAt := time.Now().Add(24 * time.Hour)
	user := &models.User{
		UUID:          uuid.NewString(),
		Email:         "accepted@example.com",
		Name:          "Accepted Invite",
		InviteToken:   "acceptedtoken123",
		InviteExpires: &expiresAt,
		InviteStatus:  "accepted",
	}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.GET("/invite/validate", handler.ValidateInvite)

	req := httptest.NewRequest("GET", "/invite/validate?token=acceptedtoken123", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusConflict, w.Code)
}

func TestUserHandler_ValidateInvite_Success(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	expiresAt := time.Now().Add(24 * time.Hour)
	user := &models.User{
		UUID:          uuid.NewString(),
		Email:         "valid@example.com",
		Name:          "Valid Invite",
		InviteToken:   "validtoken123",
		InviteExpires: &expiresAt,
		InviteStatus:  "pending",
	}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.GET("/invite/validate", handler.ValidateInvite)

	req := httptest.NewRequest("GET", "/invite/validate?token=validtoken123", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var resp map[string]any
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to unmarshal response")
	assert.Equal(t, "valid@example.com", resp["email"])
}

func TestUserHandler_AcceptInvite_InvalidJSON(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.POST("/invite/accept", handler.AcceptInvite)

	req := httptest.NewRequest("POST", "/invite/accept", bytes.NewBufferString("invalid"))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestUserHandler_AcceptInvite_InvalidToken(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.POST("/invite/accept", handler.AcceptInvite)

	body := map[string]string{
		"token":    "invalidtoken",
		"name":     "Test User",
		"password": "password123",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/invite/accept", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
}

func TestUserHandler_AcceptInvite_Success(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	expiresAt := time.Now().Add(24 * time.Hour)
	user := &models.User{
		UUID:          uuid.NewString(),
		Email:         "accept@example.com",
		Name:          "Accept User",
		InviteToken:   "accepttoken123",
		InviteExpires: &expiresAt,
		InviteStatus:  "pending",
	}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.POST("/invite/accept", handler.AcceptInvite)

	body := map[string]string{
		"token":    "accepttoken123",
		"password": "newpassword123",
		"name":     "Accepted User",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/invite/accept", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	handler.securitySvc.Flush()

	// Verify user was updated
	var updated models.User
	db.First(&updated, user.ID)
	assert.Equal(t, "accepted", updated.InviteStatus)
	assert.True(t, updated.Enabled)

	var audit models.SecurityAudit
	require.NoError(t, db.Where("action = ? AND event_category = ?", "user_invite_accept", "user").First(&audit).Error)
	assert.Equal(t, user.UUID, audit.ResourceUUID)
}

func TestGenerateSecureToken(t *testing.T) {
	token, err := generateSecureToken(32)
	assert.NoError(t, err)
	assert.Len(t, token, 64) // 32 bytes = 64 hex chars
	assert.Regexp(t, "^[a-f0-9]+$", token)

	// Ensure uniqueness
	token2, err := generateSecureToken(32)
	assert.NoError(t, err)
	assert.NotEqual(t, token, token2)
}

func TestUserHandler_InviteUser_NonAdmin(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "user")
		c.Set("userID", uint(1))
		c.Next()
	})
	r.POST("/users/invite", handler.InviteUser)

	body := map[string]string{"email": "invitee@example.com"}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/invite", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

func TestUserHandler_InviteUser_InvalidJSON(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", uint(1))
		c.Next()
	})
	r.POST("/users/invite", handler.InviteUser)

	req := httptest.NewRequest("POST", "/users/invite", bytes.NewBufferString("invalid"))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestUserHandler_InviteUser_DuplicateEmail(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create existing user
	existingUser := &models.User{
		UUID:   uuid.NewString(),
		APIKey: uuid.NewString(),
		Email:  "existing@example.com",
	}
	db.Create(existingUser)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", uint(1))
		c.Next()
	})
	r.POST("/users/invite", handler.InviteUser)

	body := map[string]string{"email": "existing@example.com"}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/invite", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusConflict, w.Code)
}

func TestUserHandler_InviteUser_Success(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create admin user
	admin := &models.User{
		UUID:   uuid.NewString(),
		APIKey: uuid.NewString(),
		Email:  "admin@example.com",
		Role:   "admin",
	}
	db.Create(admin)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", admin.ID)
		c.Next()
	})
	r.POST("/users/invite", handler.InviteUser)

	body := map[string]any{
		"email": "newinvite@example.com",
		"role":  "user",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/invite", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)
	handler.securitySvc.Flush()

	var resp map[string]any
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to unmarshal response")
	assert.Equal(t, "********", resp["invite_token_masked"])
	assert.Equal(t, "", resp["invite_url"])
	// email_sent is false because no SMTP is configured
	assert.Equal(t, false, resp["email_sent"].(bool))

	// Verify user was created
	var user models.User
	db.Where("email = ?", "newinvite@example.com").First(&user)
	assert.Equal(t, "pending", user.InviteStatus)
	assert.False(t, user.Enabled)

	var audit models.SecurityAudit
	require.NoError(t, db.Where("action = ? AND event_category = ?", "user_invite", "user").First(&audit).Error)
	assert.Equal(t, user.UUID, audit.ResourceUUID)
}

func TestUserHandler_InviteUser_WithPermittedHosts(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create admin user
	admin := &models.User{
		UUID:   uuid.NewString(),
		APIKey: uuid.NewString(),
		Email:  "admin-perm@example.com",
		Role:   "admin",
	}
	db.Create(admin)

	// Create proxy host
	host := &models.ProxyHost{
		UUID:        uuid.NewString(),
		Name:        "Test Host",
		DomainNames: "test.example.com",
	}
	db.Create(host)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", admin.ID)
		c.Next()
	})
	r.POST("/users/invite", handler.InviteUser)

	body := map[string]any{
		"email":           "invitee-perms@example.com",
		"permission_mode": "deny_all",
		"permitted_hosts": []uint{host.ID},
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/invite", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	// Verify user has permitted hosts
	var user models.User
	db.Preload("PermittedHosts").Where("email = ?", "invitee-perms@example.com").First(&user)
	assert.Len(t, user.PermittedHosts, 1)
	assert.Equal(t, models.PermissionModeDenyAll, user.PermissionMode)
}

func TestUserHandler_InviteUser_WithSMTPConfigured(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create admin user
	admin := &models.User{
		UUID:   uuid.NewString(),
		APIKey: uuid.NewString(),
		Email:  "admin-smtp@example.com",
		Role:   "admin",
	}
	db.Create(admin)

	// Configure SMTP settings to trigger email code path and getAppName call
	smtpSettings := []models.Setting{
		{Key: "smtp_host", Value: "smtp.example.com", Type: "string", Category: "smtp"},
		{Key: "smtp_port", Value: "587", Type: "integer", Category: "smtp"},
		{Key: "smtp_username", Value: "user@example.com", Type: "string", Category: "smtp"},
		{Key: "smtp_password", Value: "password", Type: "string", Category: "smtp"},
		{Key: "smtp_from_address", Value: "noreply@example.com", Type: "string", Category: "smtp"},
		{Key: "app_name", Value: "TestApp", Type: "string", Category: "app"},
	}
	for _, setting := range smtpSettings {
		db.Create(&setting)
	}

	// Reinitialize mail service to pick up new settings
	handler.MailService = services.NewMailService(db)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", admin.ID)
		c.Next()
	})
	r.POST("/users/invite", handler.InviteUser)

	body := map[string]any{
		"email": "smtp-test@example.com",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/invite", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	// Verify user was created
	var user models.User
	db.Where("email = ?", "smtp-test@example.com").First(&user)
	assert.Equal(t, "pending", user.InviteStatus)
	assert.False(t, user.Enabled)

	// Note: email_sent will be false because we can't actually send email in tests,
	// but the code path through IsConfigured() and getAppName() is still executed
	var resp map[string]any
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to unmarshal response")
	assert.Equal(t, "********", resp["invite_token_masked"])
	assert.Equal(t, "", resp["invite_url"])
	assert.Equal(t, false, resp["email_sent"].(bool))
}

func TestUserHandler_InviteUser_WithSMTPAndConfiguredPublicURL_IncludesInviteURL(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	admin := &models.User{
		UUID:   uuid.NewString(),
		APIKey: uuid.NewString(),
		Email:  "admin-publicurl@example.com",
		Role:   "admin",
	}
	db.Create(admin)

	settings := []models.Setting{
		{Key: "smtp_host", Value: "smtp.example.com", Type: "string", Category: "smtp"},
		{Key: "smtp_port", Value: "587", Type: "integer", Category: "smtp"},
		{Key: "smtp_username", Value: "user@example.com", Type: "string", Category: "smtp"},
		{Key: "smtp_password", Value: "password", Type: "string", Category: "smtp"},
		{Key: "smtp_from_address", Value: "noreply@example.com", Type: "string", Category: "smtp"},
		{Key: "app.public_url", Value: "https://charon.example.com", Type: "string", Category: "app"},
	}
	for _, setting := range settings {
		db.Create(&setting)
	}

	handler.MailService = services.NewMailService(db)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", admin.ID)
		c.Next()
	})
	r.POST("/users/invite", handler.InviteUser)

	body := map[string]any{
		"email": "smtp-public-url@example.com",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/invite", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	var resp map[string]any
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to unmarshal response")
	assert.Equal(t, "********", resp["invite_token_masked"])
	assert.Equal(t, "[REDACTED]", resp["invite_url"])
	assert.Equal(t, true, resp["email_sent"].(bool))
}

func TestUserHandler_InviteUser_WithSMTPAndMalformedPublicURL_DoesNotExposeInviteURL(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	admin := &models.User{
		UUID:   uuid.NewString(),
		APIKey: uuid.NewString(),
		Email:  "admin-malformed-publicurl@example.com",
		Role:   "admin",
	}
	db.Create(admin)

	settings := []models.Setting{
		{Key: "smtp_host", Value: "smtp.example.com", Type: "string", Category: "smtp"},
		{Key: "smtp_port", Value: "587", Type: "integer", Category: "smtp"},
		{Key: "smtp_username", Value: "user@example.com", Type: "string", Category: "smtp"},
		{Key: "smtp_password", Value: "password", Type: "string", Category: "smtp"},
		{Key: "smtp_from_address", Value: "noreply@example.com", Type: "string", Category: "smtp"},
		{Key: "app.public_url", Value: "https://charon.example.com/path", Type: "string", Category: "app"},
	}
	for _, setting := range settings {
		db.Create(&setting)
	}

	handler.MailService = services.NewMailService(db)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", admin.ID)
		c.Next()
	})
	r.POST("/users/invite", handler.InviteUser)

	body := map[string]any{
		"email": "smtp-malformed-url@example.com",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/invite", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	var resp map[string]any
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to unmarshal response")
	assert.Equal(t, "********", resp["invite_token_masked"])
	assert.Equal(t, "", resp["invite_url"])
	assert.Equal(t, false, resp["email_sent"].(bool))
}

func TestUserHandler_InviteUser_WithSMTPConfigured_DefaultAppName(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create admin user
	admin := &models.User{
		UUID:   uuid.NewString(),
		APIKey: uuid.NewString(),
		Email:  "admin-smtp-default@example.com",
		Role:   "admin",
	}
	db.Create(admin)

	// Configure SMTP settings WITHOUT app_name to trigger default "Charon" path
	smtpSettings := []models.Setting{
		{Key: "smtp_host", Value: "smtp.example.com", Type: "string", Category: "smtp"},
		{Key: "smtp_port", Value: "587", Type: "integer", Category: "smtp"},
		{Key: "smtp_username", Value: "user@example.com", Type: "string", Category: "smtp"},
		{Key: "smtp_password", Value: "password", Type: "string", Category: "smtp"},
		{Key: "smtp_from_address", Value: "noreply@example.com", Type: "string", Category: "smtp"},
		// Intentionally NOT setting app_name to test default path
	}
	for _, setting := range smtpSettings {
		db.Create(&setting)
	}

	// Reinitialize mail service to pick up new settings
	handler.MailService = services.NewMailService(db)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", admin.ID)
		c.Next()
	})
	r.POST("/users/invite", handler.InviteUser)

	body := map[string]any{
		"email": "smtp-test-default@example.com",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/invite", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	// Verify user was created
	var user models.User
	db.Where("email = ?", "smtp-test-default@example.com").First(&user)
	assert.Equal(t, "pending", user.InviteStatus)
	assert.False(t, user.Enabled)

	var resp map[string]any
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to unmarshal response")
	assert.Equal(t, "********", resp["invite_token_masked"])
}

// Note: TestGetBaseURL and TestGetAppName have been removed as these internal helper
// functions have been refactored into the utils package. URL functionality is tested
// via integration tests and the utils package should have its own unit tests.

func TestUserHandler_AcceptInvite_ExpiredToken(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create user with expired invite
	expired := time.Now().Add(-24 * time.Hour)
	user := &models.User{
		UUID:          uuid.NewString(),
		APIKey:        uuid.NewString(),
		Email:         "expired-invite@example.com",
		InviteToken:   "expiredtoken123",
		InviteExpires: &expired,
		InviteStatus:  "pending",
	}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.POST("/invite/accept", handler.AcceptInvite)

	body := map[string]string{
		"token":    "expiredtoken123",
		"name":     "Expired User",
		"password": "password123",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/invite/accept", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusGone, w.Code)
}

func TestUserHandler_AcceptInvite_AlreadyAccepted(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	expires := time.Now().Add(24 * time.Hour)
	user := &models.User{
		UUID:          uuid.NewString(),
		APIKey:        uuid.NewString(),
		Email:         "accepted-already@example.com",
		InviteToken:   "acceptedtoken123",
		InviteExpires: &expires,
		InviteStatus:  "accepted",
	}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.POST("/invite/accept", handler.AcceptInvite)

	body := map[string]string{
		"token":    "acceptedtoken123",
		"name":     "Already Accepted",
		"password": "password123",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/invite/accept", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusConflict, w.Code)
}

// ============= Priority 1: Zero Coverage Functions =============

// PreviewInviteURL Tests
func TestUserHandler_PreviewInviteURL_NonAdmin(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "user")
		c.Next()
	})
	r.POST("/users/preview-invite-url", handler.PreviewInviteURL)

	body := map[string]string{"email": "test@example.com"}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/preview-invite-url", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
	assert.Contains(t, w.Body.String(), "Admin access required")
}

func TestUserHandler_PreviewInviteURL_InvalidJSON(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users/preview-invite-url", handler.PreviewInviteURL)

	req := httptest.NewRequest("POST", "/users/preview-invite-url", bytes.NewBufferString("invalid"))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestUserHandler_PreviewInviteURL_Success_Unconfigured(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users/preview-invite-url", handler.PreviewInviteURL)

	body := map[string]string{"email": "test@example.com"}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/preview-invite-url", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var resp map[string]any
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to unmarshal response")

	assert.Equal(t, false, resp["is_configured"].(bool))
	assert.Equal(t, true, resp["warning"].(bool))
	assert.Contains(t, resp["warning_message"].(string), "not configured")
	// When unconfigured, base_url and preview_url must be empty (CodeQL go/email-injection remediation)
	assert.Equal(t, "", resp["base_url"].(string), "base_url must be empty when public_url is not configured")
	assert.Equal(t, "", resp["preview_url"].(string), "preview_url must be empty when public_url is not configured")
	assert.Equal(t, "test@example.com", resp["email"].(string))
}

func TestUserHandler_PreviewInviteURL_Success_Configured(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create public_url setting
	publicURLSetting := &models.Setting{
		Key:      "app.public_url",
		Value:    "https://charon.example.com",
		Type:     "string",
		Category: "app",
	}
	db.Create(publicURLSetting)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users/preview-invite-url", handler.PreviewInviteURL)

	body := map[string]string{"email": "test@example.com"}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/preview-invite-url", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var resp map[string]any
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to unmarshal response")

	assert.Equal(t, true, resp["is_configured"].(bool))
	assert.Equal(t, false, resp["warning"].(bool))
	assert.Contains(t, resp["preview_url"].(string), "https://charon.example.com")
	assert.Contains(t, resp["preview_url"].(string), "SAMPLE_TOKEN_PREVIEW")
	assert.Equal(t, "https://charon.example.com", resp["base_url"].(string))
	assert.Equal(t, "test@example.com", resp["email"].(string))
}

// getAppName Tests
func TestGetAppName_Default(t *testing.T) {
	_, db := setupUserHandlerWithProxyHosts(t)

	appName := getAppName(db)

	assert.Equal(t, "Charon", appName)
}

func TestGetAppName_FromSettings(t *testing.T) {
	_, db := setupUserHandlerWithProxyHosts(t)

	// Create app_name setting
	appNameSetting := &models.Setting{
		Key:      "app_name",
		Value:    "MyCustomApp",
		Type:     "string",
		Category: "app",
	}
	db.Create(appNameSetting)

	appName := getAppName(db)

	assert.Equal(t, "MyCustomApp", appName)
}

func TestGetAppName_EmptyValue(t *testing.T) {
	_, db := setupUserHandlerWithProxyHosts(t)

	// Create app_name setting with empty value
	appNameSetting := &models.Setting{
		Key:      "app_name",
		Value:    "",
		Type:     "string",
		Category: "app",
	}
	db.Create(appNameSetting)

	appName := getAppName(db)

	// Should return default when value is empty
	assert.Equal(t, "Charon", appName)
}

// ============= Priority 2: Error Paths =============

func TestUserHandler_UpdateUser_EmailConflict(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create two users
	user1 := &models.User{
		UUID:   uuid.NewString(),
		APIKey: uuid.NewString(),
		Email:  "user1@example.com",
		Name:   "User 1",
	}
	user2 := &models.User{
		UUID:   uuid.NewString(),
		APIKey: uuid.NewString(),
		Email:  "user2@example.com",
		Name:   "User 2",
	}
	db.Create(user1)
	db.Create(user2)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.PUT("/users/:id", handler.UpdateUser)

	// Try to update user1's email to user2's email
	body := map[string]string{
		"email": "user2@example.com",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("PUT", "/users/1", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusConflict, w.Code)
	assert.Contains(t, w.Body.String(), "Email already in use")
}

// ============= Priority 3: Edge Cases and Defaults =============

func TestUserHandler_CreateUser_EmailNormalization(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users", handler.CreateUser)

	// Create user with mixed-case email
	body := map[string]any{
		"email":    "User@Example.COM",
		"name":     "Test User",
		"password": "password123",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	// Verify email is stored lowercase
	var user models.User
	db.Where("email = ?", "user@example.com").First(&user)
	assert.Equal(t, "user@example.com", user.Email)
}

func TestUserHandler_InviteUser_EmailNormalization(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create admin user
	admin := &models.User{
		UUID:   uuid.NewString(),
		APIKey: uuid.NewString(),
		Email:  "admin@example.com",
		Role:   "admin",
	}
	db.Create(admin)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", admin.ID)
		c.Next()
	})
	r.POST("/users/invite", handler.InviteUser)

	// Invite user with mixed-case email
	body := map[string]any{
		"email": "Invite@Example.COM",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/invite", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	// Verify email is stored lowercase
	var user models.User
	db.Where("email = ?", "invite@example.com").First(&user)
	assert.Equal(t, "invite@example.com", user.Email)
}

func TestUserHandler_CreateUser_DefaultPermissionMode(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users", handler.CreateUser)

	// Create user without specifying permission_mode
	body := map[string]any{
		"email":    "defaultperms@example.com",
		"name":     "Default Perms User",
		"password": "password123",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	// Verify permission_mode defaults to "allow_all"
	var user models.User
	db.Where("email = ?", "defaultperms@example.com").First(&user)
	assert.Equal(t, models.PermissionModeAllowAll, user.PermissionMode)
}

func TestUserHandler_InviteUser_DefaultPermissionMode(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create admin user
	admin := &models.User{
		UUID:   uuid.NewString(),
		APIKey: uuid.NewString(),
		Email:  "admin@example.com",
		Role:   "admin",
	}
	db.Create(admin)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", admin.ID)
		c.Next()
	})
	r.POST("/users/invite", handler.InviteUser)

	// Invite user without specifying permission_mode
	body := map[string]any{
		"email": "defaultinvite@example.com",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/invite", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	// Verify permission_mode defaults to "allow_all"
	var user models.User
	db.Where("email = ?", "defaultinvite@example.com").First(&user)
	assert.Equal(t, models.PermissionModeAllowAll, user.PermissionMode)
}

func TestUserHandler_CreateUser_DefaultRole(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users", handler.CreateUser)

	// Create user without specifying role
	body := map[string]any{
		"email":    "defaultrole@example.com",
		"name":     "Default Role User",
		"password": "password123",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	// Verify role defaults to "user"
	var user models.User
	db.Where("email = ?", "defaultrole@example.com").First(&user)
	assert.Equal(t, "user", user.Role)
}

func TestUserHandler_InviteUser_DefaultRole(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create admin user
	admin := &models.User{
		UUID:   uuid.NewString(),
		APIKey: uuid.NewString(),
		Email:  "admin@example.com",
		Role:   "admin",
	}
	db.Create(admin)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", admin.ID)
		c.Next()
	})
	r.POST("/users/invite", handler.InviteUser)

	// Invite user without specifying role
	body := map[string]any{
		"email": "defaultroleinvite@example.com",
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/invite", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	// Verify role defaults to "user"
	var user models.User
	db.Where("email = ?", "defaultroleinvite@example.com").First(&user)
	assert.Equal(t, "user", user.Role)
}

// TestUserHandler_PreviewInviteURL_Unconfigured_DoesNotUseRequestHost verifies that
// when app.public_url is not configured, the preview does NOT use request Host header.
// This prevents host header injection attacks (CodeQL go/email-injection remediation).
func TestUserHandler_PreviewInviteURL_Unconfigured_DoesNotUseRequestHost(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users/preview-invite-url", handler.PreviewInviteURL)

	body := map[string]string{"email": "test@example.com"}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users/preview-invite-url", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	// Set malicious Host and X-Forwarded-Proto headers
	req.Host = "evil.example.com"
	req.Header.Set("X-Forwarded-Proto", "https")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var resp map[string]any
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to unmarshal response")

	// Response must NOT contain the malicious host
	responseJSON := w.Body.String()
	assert.NotContains(t, responseJSON, "evil.example.com", "Malicious Host header must not appear in response")
	// Verify base_url and preview_url are empty
	assert.Equal(t, "", resp["base_url"].(string))
	assert.Equal(t, "", resp["preview_url"].(string))
}

// ============= Priority 4: Integration Edge Cases =============

func TestUserHandler_CreateUser_EmptyPermittedHosts(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users", handler.CreateUser)

	// Create user with deny_all mode but empty permitted_hosts
	body := map[string]any{
		"email":           "emptyhosts@example.com",
		"name":            "Empty Hosts User",
		"password":        "password123",
		"permission_mode": "deny_all",
		"permitted_hosts": []uint{},
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	// Verify user was created with deny_all mode and no permitted hosts
	var user models.User
	db.Preload("PermittedHosts").Where("email = ?", "emptyhosts@example.com").First(&user)
	assert.Equal(t, models.PermissionModeDenyAll, user.PermissionMode)
	assert.Len(t, user.PermittedHosts, 0)
}

func TestUserHandler_CreateUser_NonExistentPermittedHosts(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users", handler.CreateUser)

	// Create user with non-existent host IDs
	body := map[string]any{
		"email":           "nonexistenthosts@example.com",
		"name":            "Non-Existent Hosts User",
		"password":        "password123",
		"permission_mode": "deny_all",
		"permitted_hosts": []uint{999, 1000},
	}
	jsonBody, _ := json.Marshal(body)
	req := httptest.NewRequest("POST", "/users", bytes.NewBuffer(jsonBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	// Verify user was created but no hosts were associated (non-existent IDs are ignored)
	var user models.User
	db.Preload("PermittedHosts").Where("email = ?", "nonexistenthosts@example.com").First(&user)
	assert.Len(t, user.PermittedHosts, 0)
}

// ============= ResendInvite Tests =============

func TestResendInvite_NonAdmin(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "user")
		c.Next()
	})
	r.POST("/users/:id/resend-invite", handler.ResendInvite)

	req := httptest.NewRequest("POST", "/users/1/resend-invite", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
	assert.Contains(t, w.Body.String(), "Admin access required")
}

func TestResendInvite_InvalidID(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users/:id/resend-invite", handler.ResendInvite)

	req := httptest.NewRequest("POST", "/users/invalid/resend-invite", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
	assert.Contains(t, w.Body.String(), "Invalid user ID")
}

func TestResendInvite_UserNotFound(t *testing.T) {
	handler, _ := setupUserHandlerWithProxyHosts(t)
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users/:id/resend-invite", handler.ResendInvite)

	req := httptest.NewRequest("POST", "/users/999/resend-invite", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
	assert.Contains(t, w.Body.String(), "User not found")
}

func TestResendInvite_UserNotPending(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create user with accepted invite (not pending)
	user := &models.User{
		UUID:         uuid.NewString(),
		APIKey:       uuid.NewString(),
		Email:        "accepted-user@example.com",
		Name:         "Accepted User",
		InviteStatus: "accepted",
		Enabled:      true,
	}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users/:id/resend-invite", handler.ResendInvite)

	req := httptest.NewRequest("POST", "/users/"+strconv.FormatUint(uint64(user.ID), 10)+"/resend-invite", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
	assert.Contains(t, w.Body.String(), "does not have a pending invite")
}

func TestResendInvite_Success(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create user with pending invite
	expires := time.Now().Add(24 * time.Hour)
	user := &models.User{
		UUID:          uuid.NewString(),
		APIKey:        uuid.NewString(),
		Email:         "pending-user@example.com",
		Name:          "Pending User",
		InviteStatus:  "pending",
		InviteToken:   "oldtoken123",
		InviteExpires: &expires,
		Enabled:       false,
	}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users/:id/resend-invite", handler.ResendInvite)

	req := httptest.NewRequest("POST", "/users/"+strconv.FormatUint(uint64(user.ID), 10)+"/resend-invite", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var resp map[string]any
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to unmarshal response")
	assert.Equal(t, "********", resp["invite_token_masked"])
	assert.Equal(t, "pending-user@example.com", resp["email"])
	assert.Equal(t, false, resp["email_sent"].(bool)) // No SMTP configured

	// Verify token was updated in DB
	var updatedUser models.User
	db.First(&updatedUser, user.ID)
	assert.NotEqual(t, "oldtoken123", updatedUser.InviteToken)
	assert.NotEmpty(t, updatedUser.InviteToken)
}

func TestResendInvite_WithExpiredInvite(t *testing.T) {
	handler, db := setupUserHandlerWithProxyHosts(t)

	// Create user with expired pending invite
	expired := time.Now().Add(-24 * time.Hour)
	user := &models.User{
		UUID:          uuid.NewString(),
		APIKey:        uuid.NewString(),
		Email:         "expired-pending@example.com",
		Name:          "Expired Pending User",
		InviteStatus:  "pending",
		InviteToken:   "expiredtoken",
		InviteExpires: &expired,
		Enabled:       false,
	}
	db.Create(user)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Next()
	})
	r.POST("/users/:id/resend-invite", handler.ResendInvite)

	req := httptest.NewRequest("POST", "/users/"+strconv.FormatUint(uint64(user.ID), 10)+"/resend-invite", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	// Should succeed - resend should work even if previous invite expired
	assert.Equal(t, http.StatusOK, w.Code)

	var resp map[string]any
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err, "Failed to unmarshal response")
	assert.Equal(t, "********", resp["invite_token_masked"])

	// Verify new expiration is in the future
	var updatedUser models.User
	db.First(&updatedUser, user.ID)
	assert.True(t, updatedUser.InviteExpires.After(time.Now()))
}

// ===== Additional coverage for uncovered utility functions =====

func TestIsSetupConflictError(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		expected bool
	}{
		{"nil error", nil, false},
		{"unique constraint failed", errors.New("UNIQUE constraint failed: users.email"), true},
		{"duplicate key", errors.New("duplicate key value violates unique constraint"), true},
		{"database is locked", errors.New("database is locked"), true},
		{"database table is locked", errors.New("database table is locked"), true},
		{"case insensitive", errors.New("UNIQUE CONSTRAINT FAILED"), true},
		{"unrelated error", errors.New("connection refused"), false},
		{"empty error", errors.New(""), false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isSetupConflictError(tt.err)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestMaskSecretForResponse(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{"non-empty secret", "my-secret-key", "********"},
		{"empty string", "", ""},
		{"whitespace only", "   ", ""},
		{"single char", "x", "********"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := maskSecretForResponse(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestRedactInviteURL(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{"non-empty url", "https://example.com/invite/abc123", "[REDACTED]"},
		{"empty string", "", ""},
		{"whitespace only", "   ", ""},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := redactInviteURL(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}
