package handlers

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"gorm.io/gorm"

	"github.com/Wikid82/charon/backend/internal/api/middleware"
	"github.com/Wikid82/charon/backend/internal/models"
	"github.com/Wikid82/charon/backend/internal/services"
	"github.com/Wikid82/charon/backend/internal/utils"
)

type UserHandler struct {
	DB          *gorm.DB
	AuthService *services.AuthService
	MailService *services.MailService
	securitySvc *services.SecurityService
}

func NewUserHandler(db *gorm.DB, authService *services.AuthService) *UserHandler {
	return &UserHandler{
		DB:          db,
		AuthService: authService,
		MailService: services.NewMailService(db),
		securitySvc: services.NewSecurityService(db),
	}
}

func (h *UserHandler) actorFromContext(c *gin.Context) string {
	if userID, ok := c.Get("userID"); ok {
		return fmt.Sprintf("%v", userID)
	}
	return c.ClientIP()
}

func (h *UserHandler) logUserAudit(c *gin.Context, action string, user *models.User, details map[string]any) {
	if h.securitySvc == nil || user == nil {
		return
	}

	detailsJSON, err := json.Marshal(details)
	if err != nil {
		detailsJSON = []byte("{}")
	}

	_ = h.securitySvc.LogAudit(&models.SecurityAudit{
		Actor:         h.actorFromContext(c),
		Action:        action,
		EventCategory: "user",
		ResourceID:    &user.ID,
		ResourceUUID:  user.UUID,
		Details:       string(detailsJSON),
		IPAddress:     c.ClientIP(),
		UserAgent:     c.Request.UserAgent(),
	})
}

func (h *UserHandler) RegisterRoutes(r *gin.RouterGroup) {
	r.GET("/setup", h.GetSetupStatus)
	r.POST("/setup", h.Setup)
	r.GET("/profile", h.GetProfile)
	r.POST("/regenerate-api-key", h.RegenerateAPIKey)
	r.PUT("/profile", h.UpdateProfile)

	// User management (admin only)
	r.GET("/users", h.ListUsers)
	r.POST("/users", h.CreateUser)
	r.POST("/users/invite", h.InviteUser)
	r.GET("/users/:id", h.GetUser)
	r.PUT("/users/:id", h.UpdateUser)
	r.DELETE("/users/:id", h.DeleteUser)
	r.PUT("/users/:id/permissions", h.UpdateUserPermissions)

	// Invite acceptance (public)
	r.GET("/invite/validate", h.ValidateInvite)
	r.POST("/invite/accept", h.AcceptInvite)
}

// GetSetupStatus checks if the application needs initial setup (i.e., no users exist).
func (h *UserHandler) GetSetupStatus(c *gin.Context) {
	var count int64
	if err := h.DB.Model(&models.User{}).Count(&count).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check setup status"})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"setupRequired": count == 0,
	})
}

type SetupRequest struct {
	Name     string `json:"name" binding:"required"`
	Email    string `json:"email" binding:"required,email"`
	Password string `json:"password" binding:"required,min=8"`
}

func isSetupConflictError(err error) bool {
	if err == nil {
		return false
	}

	errText := strings.ToLower(err.Error())
	return strings.Contains(errText, "unique constraint failed") ||
		strings.Contains(errText, "duplicate key") ||
		strings.Contains(errText, "database is locked") ||
		strings.Contains(errText, "database table is locked")
}

// Setup creates the initial admin user and configures the ACME email.
func (h *UserHandler) Setup(c *gin.Context) {
	// 1. Check if setup is allowed
	var count int64
	if err := h.DB.Model(&models.User{}).Count(&count).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check setup status"})
		return
	}

	if count > 0 {
		c.JSON(http.StatusForbidden, gin.H{"error": "Setup already completed"})
		return
	}

	// 2. Parse request
	var req SetupRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// 3. Create User
	user := models.User{
		UUID:    uuid.New().String(),
		Name:    req.Name,
		Email:   strings.ToLower(req.Email),
		Role:    models.RoleAdmin,
		Enabled: true,
		APIKey:  uuid.New().String(),
	}

	if err := user.SetPassword(req.Password); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
		return
	}

	// 4. Create Setting for ACME Email
	acmeEmailSetting := models.Setting{
		Key:      "caddy.acme_email",
		Value:    req.Email,
		Type:     "string",
		Category: "caddy",
	}

	// Transaction to ensure both succeed
	err := h.DB.Transaction(func(tx *gorm.DB) error {
		if err := tx.Create(&user).Error; err != nil {
			return err
		}
		// Use Save to update if exists (though it shouldn't in fresh setup) or create
		if err := tx.Where(models.Setting{Key: "caddy.acme_email"}).Assign(models.Setting{Value: req.Email}).FirstOrCreate(&acmeEmailSetting).Error; err != nil {
			return err
		}
		return nil
	})

	if err != nil {
		var postTxCount int64
		if countErr := h.DB.Model(&models.User{}).Count(&postTxCount).Error; countErr == nil && postTxCount > 0 {
			c.JSON(http.StatusForbidden, gin.H{"error": "Setup already completed"})
			return
		}

		if isSetupConflictError(err) {
			c.JSON(http.StatusConflict, gin.H{"error": "Setup conflict: setup already in progress or completed"})
			return
		}

		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to complete setup: " + err.Error()})
		return
	}

	c.JSON(http.StatusCreated, gin.H{
		"message": "Setup completed successfully",
		"user": gin.H{
			"id":    user.ID,
			"email": user.Email,
			"name":  user.Name,
		},
	})
}

// rejectPassthrough aborts with 403 if the caller is a passthrough user.
// Returns true if the request was rejected (caller should return).
func rejectPassthrough(c *gin.Context, action string) bool {
	if c.GetString("role") == string(models.RolePassthrough) {
		c.JSON(http.StatusForbidden, gin.H{"error": "Passthrough users cannot " + action})
		return true
	}
	return false
}

// RegenerateAPIKey generates a new API key for the authenticated user.
func (h *UserHandler) RegenerateAPIKey(c *gin.Context) {
	if rejectPassthrough(c, "manage API keys") {
		return
	}
	userID, exists := c.Get("userID")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
		return
	}

	apiKey := uuid.New().String()

	if err := h.DB.Model(&models.User{}).Where("id = ?", userID).Update("api_key", apiKey).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update API key"})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message":         "API key regenerated successfully",
		"has_api_key":     true,
		"api_key_masked":  maskSecretForResponse(apiKey),
		"api_key_updated": time.Now().UTC().Format(time.RFC3339),
	})
}

// GetProfile returns the current user's profile including API key.
func (h *UserHandler) GetProfile(c *gin.Context) {
	if rejectPassthrough(c, "access profile") {
		return
	}
	userID, exists := c.Get("userID")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
		return
	}

	var user models.User
	if err := h.DB.First(&user, userID).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"id":             user.ID,
		"email":          user.Email,
		"name":           user.Name,
		"role":           user.Role,
		"has_api_key":    strings.TrimSpace(user.APIKey) != "",
		"api_key_masked": maskSecretForResponse(user.APIKey),
	})
}

type UpdateProfileRequest struct {
	Name            string `json:"name" binding:"required"`
	Email           string `json:"email" binding:"required,email"`
	CurrentPassword string `json:"current_password"`
}

// UpdateProfile updates the authenticated user's profile.
func (h *UserHandler) UpdateProfile(c *gin.Context) {
	if rejectPassthrough(c, "update profile") {
		return
	}
	userID, exists := c.Get("userID")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
		return
	}

	var req UpdateProfileRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Get current user
	var user models.User
	if err := h.DB.First(&user, userID).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	// Check if email is already taken by another user
	req.Email = strings.ToLower(req.Email)
	var count int64
	if err := h.DB.Model(&models.User{}).Where("email = ? AND id != ?", req.Email, userID).Count(&count).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check email availability"})
		return
	}

	if count > 0 {
		c.JSON(http.StatusConflict, gin.H{"error": "Email already in use"})
		return
	}

	// If email is changing, verify password
	if req.Email != user.Email {
		if req.CurrentPassword == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Current password is required to change email"})
			return
		}
		if !user.CheckPassword(req.CurrentPassword) {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid password"})
			return
		}
	}

	if err := h.DB.Model(&models.User{}).Where("id = ?", userID).Updates(map[string]any{
		"name":  req.Name,
		"email": req.Email,
	}).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update profile"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Profile updated successfully"})
}

// ListUsers returns all users (admin only).
func (h *UserHandler) ListUsers(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	var users []models.User
	if err := h.DB.Preload("PermittedHosts").Find(&users).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch users"})
		return
	}

	// Return users with safe fields only
	result := make([]gin.H, len(users))
	for i, u := range users {
		result[i] = gin.H{
			"id":              u.ID,
			"uuid":            u.UUID,
			"email":           u.Email,
			"name":            u.Name,
			"role":            u.Role,
			"enabled":         u.Enabled,
			"last_login":      u.LastLogin,
			"invite_status":   u.InviteStatus,
			"invited_at":      u.InvitedAt,
			"permission_mode": u.PermissionMode,
			"created_at":      u.CreatedAt,
			"updated_at":      u.UpdatedAt,
		}
	}

	c.JSON(http.StatusOK, result)
}

// CreateUserRequest represents the request body for creating a user.
type CreateUserRequest struct {
	Email          string `json:"email" binding:"required,email"`
	Name           string `json:"name" binding:"required"`
	Password       string `json:"password" binding:"required,min=8"`
	Role           string `json:"role"`
	PermissionMode string `json:"permission_mode"`
	PermittedHosts []uint `json:"permitted_hosts"`
}

// CreateUser creates a new user with a password (admin only).
func (h *UserHandler) CreateUser(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	var req CreateUserRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Default role to "user"
	if req.Role == "" {
		req.Role = string(models.RoleUser)
	}

	if !models.UserRole(req.Role).IsValid() {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid role"})
		return
	}

	// Default permission mode to "allow_all"
	if req.PermissionMode == "" {
		req.PermissionMode = "allow_all"
	}

	// Check if email already exists
	var count int64
	if err := h.DB.Model(&models.User{}).Where("email = ?", strings.ToLower(req.Email)).Count(&count).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check email"})
		return
	}
	if count > 0 {
		c.JSON(http.StatusConflict, gin.H{"error": "Email already in use"})
		return
	}

	user := models.User{
		UUID:           uuid.New().String(),
		Email:          strings.ToLower(req.Email),
		Name:           req.Name,
		Role:           models.UserRole(req.Role),
		Enabled:        true,
		APIKey:         uuid.New().String(),
		PermissionMode: models.PermissionMode(req.PermissionMode),
	}

	if err := user.SetPassword(req.Password); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
		return
	}

	err := h.DB.Transaction(func(tx *gorm.DB) error {
		if err := tx.Create(&user).Error; err != nil {
			return err
		}

		// Add permitted hosts if specified
		if len(req.PermittedHosts) > 0 {
			var hosts []models.ProxyHost
			if err := tx.Where("id IN ?", req.PermittedHosts).Find(&hosts).Error; err != nil {
				return err
			}
			if err := tx.Model(&user).Association("PermittedHosts").Replace(hosts); err != nil {
				return err
			}
		}

		return nil
	})

	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user: " + err.Error()})
		return
	}

	h.logUserAudit(c, "user_create", &user, map[string]any{
		"target_email": user.Email,
		"target_role":  user.Role,
	})

	c.JSON(http.StatusCreated, gin.H{
		"id":    user.ID,
		"uuid":  user.UUID,
		"email": user.Email,
		"name":  user.Name,
		"role":  user.Role,
	})
}

// InviteUserRequest represents the request body for inviting a user.
type InviteUserRequest struct {
	Email          string `json:"email" binding:"required,email"`
	Role           string `json:"role"`
	PermissionMode string `json:"permission_mode"`
	PermittedHosts []uint `json:"permitted_hosts"`
}

// generateSecureToken creates a cryptographically secure random token.
func generateSecureToken(length int) (string, error) {
	bytes := make([]byte, length)
	if _, err := rand.Read(bytes); err != nil {
		return "", err
	}
	return hex.EncodeToString(bytes), nil
}

// InviteUser creates a new user with an invite token and sends an email (admin only).
func (h *UserHandler) InviteUser(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	inviterID, _ := c.Get("userID")

	var req InviteUserRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Default role to "user"
	if req.Role == "" {
		req.Role = string(models.RoleUser)
	}

	if !models.UserRole(req.Role).IsValid() {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid role"})
		return
	}

	// Default permission mode to "allow_all"
	if req.PermissionMode == "" {
		req.PermissionMode = "allow_all"
	}

	// Check if email already exists
	var existingUser models.User
	if err := h.DB.Where("email = ?", strings.ToLower(req.Email)).First(&existingUser).Error; err == nil {
		c.JSON(http.StatusConflict, gin.H{"error": "Email already in use"})
		return
	}

	// Generate invite token
	inviteToken, err := generateSecureToken(32)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate invite token"})
		return
	}

	// Set invite expiration (48 hours)
	inviteExpires := time.Now().Add(48 * time.Hour)
	invitedAt := time.Now()
	inviterIDUint := inviterID.(uint)

	user := models.User{
		UUID:           uuid.New().String(),
		Email:          strings.ToLower(req.Email),
		Role:           models.UserRole(req.Role),
		Enabled:        false, // Disabled until invite is accepted
		APIKey:         uuid.New().String(),
		PermissionMode: models.PermissionMode(req.PermissionMode),
		InviteToken:    inviteToken,
		InviteExpires:  &inviteExpires,
		InvitedAt:      &invitedAt,
		InvitedBy:      &inviterIDUint,
		InviteStatus:   "pending",
	}

	err = h.DB.Transaction(func(tx *gorm.DB) error {
		if txErr := tx.Create(&user).Error; txErr != nil {
			return txErr
		}

		// Explicitly disable user (bypass GORM's default:true)
		if txErr := tx.Model(&user).Update("enabled", false).Error; txErr != nil {
			return txErr
		}

		// Add permitted hosts if specified
		if len(req.PermittedHosts) > 0 {
			var hosts []models.ProxyHost
			if findErr := tx.Where("id IN ?", req.PermittedHosts).Find(&hosts).Error; findErr != nil {
				return findErr
			}
			if assocErr := tx.Model(&user).Association("PermittedHosts").Replace(hosts); assocErr != nil {
				return assocErr
			}
		}

		return nil
	})

	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user: " + err.Error()})
		return
	}

	h.logUserAudit(c, "user_invite", &user, map[string]any{
		"target_email":  user.Email,
		"target_role":   user.Role,
		"invite_status": user.InviteStatus,
	})

	// Send invite email asynchronously (non-blocking)
	// Capture the generated invite URL from configured public URL only.
	inviteURL := ""
	baseURL, hasConfiguredPublicURL := utils.GetConfiguredPublicURL(h.DB)
	if hasConfiguredPublicURL {
		inviteURL = fmt.Sprintf("%s/accept-invite?token=%s", strings.TrimSuffix(baseURL, "/"), inviteToken)
	}

	// Only mark as sent when SMTP is configured AND invite URL is usable.
	emailSent := false
	if h.MailService.IsConfigured() && hasConfiguredPublicURL {
		emailSent = true
		userEmail := user.Email
		userToken := inviteToken
		appName := getAppName(h.DB)

		go func() {
			// userEmail validated as RFC 5321 format; rejectCRLF + net/mail.ParseAddress in mail_service.go cover this path.
			if err := h.MailService.SendInvite(userEmail, userToken, appName, baseURL); err != nil {
				// Log failure but don't block response
				middleware.GetRequestLogger(c).WithField("user_email", sanitizeForLog(userEmail)).WithField("error", sanitizeForLog(err.Error())).Error("Failed to send invite email")
			}
		}()
	}

	c.JSON(http.StatusCreated, gin.H{
		"id":                  user.ID,
		"uuid":                user.UUID,
		"email":               user.Email,
		"role":                user.Role,
		"invite_token_masked": maskSecretForResponse(inviteToken),
		"invite_url":          redactInviteURL(inviteURL),
		"email_sent":          emailSent,
		"expires_at":          inviteExpires,
	})
}

// PreviewInviteURLRequest represents the request for previewing an invite URL.
type PreviewInviteURLRequest struct {
	Email string `json:"email" binding:"required,email"`
}

// PreviewInviteURL returns what the invite URL would look like with current settings.
func (h *UserHandler) PreviewInviteURL(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	var req PreviewInviteURLRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	baseURL, isConfigured := utils.GetConfiguredPublicURL(h.DB)
	// Generate a sample token for preview (not stored)
	sampleToken := "SAMPLE_TOKEN_PREVIEW"
	inviteURL := ""
	if isConfigured {
		inviteURL = fmt.Sprintf("%s/accept-invite?token=%s", strings.TrimSuffix(baseURL, "/"), sampleToken)
	}

	warningMessage := ""
	if !isConfigured {
		warningMessage = "Application URL not configured. The invite link may not be accessible from external networks."
	}

	c.JSON(http.StatusOK, gin.H{
		"preview_url":     inviteURL,
		"base_url":        baseURL,
		"is_configured":   isConfigured,
		"email":           req.Email,
		"warning":         !isConfigured,
		"warning_message": warningMessage,
	})
}

// getAppName retrieves the application name from settings or returns a default.
func getAppName(db *gorm.DB) string {
	var setting models.Setting
	if err := db.Where("key = ?", "app_name").First(&setting).Error; err == nil && setting.Value != "" {
		return setting.Value
	}
	return "Charon"
}

// GetUser returns a single user by ID (admin only).
func (h *UserHandler) GetUser(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	idParam := c.Param("id")
	id, err := strconv.ParseUint(idParam, 10, 32)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
		return
	}

	var user models.User
	if err := h.DB.Preload("PermittedHosts").First(&user, id).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	// Build permitted host IDs list
	permittedHostIDs := make([]uint, len(user.PermittedHosts))
	for i, host := range user.PermittedHosts {
		permittedHostIDs[i] = host.ID
	}

	c.JSON(http.StatusOK, gin.H{
		"id":              user.ID,
		"uuid":            user.UUID,
		"email":           user.Email,
		"name":            user.Name,
		"role":            user.Role,
		"enabled":         user.Enabled,
		"last_login":      user.LastLogin,
		"invite_status":   user.InviteStatus,
		"invited_at":      user.InvitedAt,
		"permission_mode": user.PermissionMode,
		"permitted_hosts": permittedHostIDs,
		"created_at":      user.CreatedAt,
		"updated_at":      user.UpdatedAt,
	})
}

// UpdateUserRequest represents the request body for updating a user.
type UpdateUserRequest struct {
	Name     string  `json:"name"`
	Email    string  `json:"email"`
	Password *string `json:"password" binding:"omitempty,min=8"`
	Role     string  `json:"role"`
	Enabled  *bool   `json:"enabled"`
}

// UpdateUser updates an existing user (admin only for management fields, self-service for name/password).
func (h *UserHandler) UpdateUser(c *gin.Context) {
	currentRole := c.GetString("role")
	currentUserIDRaw, exists := c.Get("userID")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
		return
	}
	currentUserID, ok := currentUserIDRaw.(uint)
	if !ok {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid session"})
		return
	}

	idParam := c.Param("id")
	id, err := strconv.ParseUint(idParam, 10, 32)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
		return
	}

	var user models.User
	if findErr := h.DB.First(&user, id).Error; findErr != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	var req UpdateUserRequest
	if bindErr := c.ShouldBindJSON(&req); bindErr != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": bindErr.Error()})
		return
	}

	isSelf := uint(id) == currentUserID
	isCallerAdmin := currentRole == string(models.RoleAdmin)

	// Non-admin users can only update their own name and password via this endpoint.
	// Email changes require password verification and must go through PUT /user/profile.
	if !isCallerAdmin {
		if !isSelf {
			c.JSON(http.StatusForbidden, gin.H{"error": "Admin access required"})
			return
		}
		if req.Email != "" {
			c.JSON(http.StatusForbidden, gin.H{"error": "Email changes must be made via your profile settings"})
			return
		}
		if req.Role != "" || req.Enabled != nil {
			c.JSON(http.StatusForbidden, gin.H{"error": "Cannot modify role or enabled status"})
			return
		}
	}

	updates := make(map[string]any)

	if req.Name != "" {
		updates["name"] = req.Name
	}

	if req.Email != "" {
		email := strings.ToLower(req.Email)
		var count int64
		if dbErr := h.DB.Model(&models.User{}).Where("email = ? AND id != ?", email, id).Count(&count).Error; dbErr == nil && count > 0 {
			c.JSON(http.StatusConflict, gin.H{"error": "Email already in use"})
			return
		}
		updates["email"] = email
	}

	needsSessionInvalidation := false

	if req.Role != "" {
		newRole := models.UserRole(req.Role)
		if !newRole.IsValid() {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid role"})
			return
		}

		if newRole != user.Role {
			// Self-demotion prevention
			if isSelf {
				c.JSON(http.StatusForbidden, gin.H{"error": "Cannot change your own role"})
				return
			}

			updates["role"] = string(newRole)
			needsSessionInvalidation = true
		}
	}

	if req.Password != nil {
		if hashErr := user.SetPassword(*req.Password); hashErr != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
			return
		}
		updates["password_hash"] = user.PasswordHash
		updates["failed_login_attempts"] = 0
		updates["locked_until"] = nil
	}

	if req.Enabled != nil && *req.Enabled != user.Enabled {
		// Prevent self-disable
		if isSelf && !*req.Enabled {
			c.JSON(http.StatusForbidden, gin.H{"error": "Cannot disable your own account"})
			return
		}

		updates["enabled"] = *req.Enabled
		if !*req.Enabled {
			needsSessionInvalidation = true
		}
	}

	// Wrap the last-admin checks and the actual update in a transaction to prevent
	// race conditions: two concurrent requests could both read adminCount==2
	// and both proceed, leaving zero admins.
	err = h.DB.Transaction(func(tx *gorm.DB) error {
		// Re-fetch user inside transaction for consistent state
		if txErr := tx.First(&user, id).Error; txErr != nil {
			return txErr
		}

		// Last-admin protection for role demotion
		if newRoleStr, ok := updates["role"]; ok {
			newRole := models.UserRole(newRoleStr.(string))
			if user.Role == models.RoleAdmin && newRole != models.RoleAdmin {
				var adminCount int64
				// Policy: count only enabled admins. This is stricter than "WHERE role = ?"
				// because a disabled admin cannot act; treating them as non-existent
				// prevents leaving the system with only disabled admins.
				tx.Model(&models.User{}).Where("role = ? AND enabled = ?", models.RoleAdmin, true).Count(&adminCount)
				if adminCount <= 1 {
					return fmt.Errorf("cannot demote the last admin")
				}
			}
		}

		// Last-admin protection for disabling
		if enabledVal, ok := updates["enabled"]; ok {
			if enabled, isBool := enabledVal.(bool); isBool && !enabled {
				if user.Role == models.RoleAdmin {
					var adminCount int64
					// Policy: count only enabled admins (same rationale as above).
					tx.Model(&models.User{}).Where("role = ? AND enabled = ?", models.RoleAdmin, true).Count(&adminCount)
					if adminCount <= 1 {
						return fmt.Errorf("cannot disable the last admin")
					}
				}
			}
		}

		if len(updates) > 0 {
			if txErr := tx.Model(&user).Updates(updates).Error; txErr != nil {
				return txErr
			}
		}

		return nil
	})

	if err != nil {
		errMsg := err.Error()
		if errMsg == "cannot demote the last admin" || errMsg == "cannot disable the last admin" {
			c.JSON(http.StatusForbidden, gin.H{"error": "Cannot" + errMsg[len("cannot"):]})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update user"})
		return
	}

	if len(updates) > 0 {
		if needsSessionInvalidation && h.AuthService != nil {
			if invErr := h.AuthService.InvalidateSessions(user.ID); invErr != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to invalidate sessions"})
				return
			}
		}

		h.logUserAudit(c, "user_update", &user, map[string]any{
			"target_email": user.Email,
			"target_role":  user.Role,
			"fields":       mapsKeys(updates),
		})
	}

	c.JSON(http.StatusOK, gin.H{"message": "User updated successfully"})
}

func mapsKeys(values map[string]any) []string {
	keys := make([]string, 0, len(values))
	for key := range values {
		keys = append(keys, key)
	}
	return keys
}

// DeleteUser deletes a user (admin only).
func (h *UserHandler) DeleteUser(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	currentUserIDRaw, _ := c.Get("userID")
	currentUserID, _ := currentUserIDRaw.(uint)

	idParam := c.Param("id")
	id, err := strconv.ParseUint(idParam, 10, 32)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
		return
	}

	// Prevent self-deletion
	if uint(id) == currentUserID {
		c.JSON(http.StatusForbidden, gin.H{"error": "Cannot delete your own account"})
		return
	}

	var user models.User
	if findErr := h.DB.First(&user, id).Error; findErr != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	// Clear associations first
	if err := h.DB.Model(&user).Association("PermittedHosts").Clear(); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear user associations"})
		return
	}

	if err := h.DB.Delete(&user).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete user"})
		return
	}

	h.logUserAudit(c, "user_delete", &user, map[string]any{
		"target_email": user.Email,
		"target_role":  user.Role,
	})

	c.JSON(http.StatusOK, gin.H{"message": "User deleted successfully"})
}

// UpdateUserPermissionsRequest represents the request body for updating user permissions.
type UpdateUserPermissionsRequest struct {
	PermissionMode string `json:"permission_mode" binding:"required,oneof=allow_all deny_all"`
	PermittedHosts []uint `json:"permitted_hosts"`
}

// ResendInvite regenerates and resends an invitation to a pending user (admin only).
func (h *UserHandler) ResendInvite(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	idParam := c.Param("id")
	id, err := strconv.ParseUint(idParam, 10, 32)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
		return
	}

	var user models.User
	if findErr := h.DB.First(&user, id).Error; findErr != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	// Verify user has a pending invite
	if user.InviteStatus != "pending" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "User does not have a pending invite"})
		return
	}

	// Generate new invite token
	inviteToken, err := generateSecureToken(32)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate invite token"})
		return
	}

	// Set new invite expiration (48 hours)
	inviteExpires := time.Now().Add(48 * time.Hour)

	// Update user with new token
	if err := h.DB.Model(&user).Updates(map[string]any{
		"invite_token":   inviteToken,
		"invite_expires": inviteExpires,
	}).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update invite token"})
		return
	}

	// Try to send invite email
	emailSent := false
	if h.MailService.IsConfigured() {
		baseURL, ok := utils.GetConfiguredPublicURL(h.DB)
		if ok {
			appName := getAppName(h.DB)
			// userEmail validated as RFC 5321 format; rejectCRLF + net/mail.ParseAddress in mail_service.go cover this path.
			if err := h.MailService.SendInvite(user.Email, inviteToken, appName, baseURL); err == nil {
				emailSent = true
			}
		}
	}

	c.JSON(http.StatusOK, gin.H{
		"id":                  user.ID,
		"uuid":                user.UUID,
		"email":               user.Email,
		"role":                user.Role,
		"invite_token_masked": maskSecretForResponse(inviteToken),
		"email_sent":          emailSent,
		"expires_at":          inviteExpires,
	})
}

func maskSecretForResponse(value string) string {
	if strings.TrimSpace(value) == "" {
		return ""
	}

	return "********"
}

func redactInviteURL(inviteURL string) string {
	if strings.TrimSpace(inviteURL) == "" {
		return ""
	}

	return "[REDACTED]"
}

// UpdateUserPermissions updates a user's permission mode and host exceptions (admin only).
func (h *UserHandler) UpdateUserPermissions(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	idParam := c.Param("id")
	id, err := strconv.ParseUint(idParam, 10, 32)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
		return
	}

	var user models.User
	if findErr := h.DB.First(&user, id).Error; findErr != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	var req UpdateUserPermissionsRequest
	if bindErr := c.ShouldBindJSON(&req); bindErr != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": bindErr.Error()})
		return
	}

	err = h.DB.Transaction(func(tx *gorm.DB) error {
		// Update permission mode
		if txErr := tx.Model(&user).Update("permission_mode", req.PermissionMode).Error; txErr != nil {
			return txErr
		}

		// Update permitted hosts
		var hosts []models.ProxyHost
		if len(req.PermittedHosts) > 0 {
			if findErr := tx.Where("id IN ?", req.PermittedHosts).Find(&hosts).Error; findErr != nil {
				return findErr
			}
		}

		if assocErr := tx.Model(&user).Association("PermittedHosts").Replace(hosts); assocErr != nil {
			return assocErr
		}

		return nil
	})

	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update permissions: " + err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Permissions updated successfully"})
}

// ValidateInvite validates an invite token (public endpoint).
func (h *UserHandler) ValidateInvite(c *gin.Context) {
	token := c.Query("token")
	if token == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Token required"})
		return
	}

	var user models.User
	if err := h.DB.Where("invite_token = ?", token).First(&user).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Invalid or expired invite token"})
		return
	}

	// Check if token is expired
	if user.InviteExpires != nil && user.InviteExpires.Before(time.Now()) {
		c.JSON(http.StatusGone, gin.H{"error": "Invite token has expired"})
		return
	}

	// Check if already accepted
	if user.InviteStatus != "pending" {
		c.JSON(http.StatusConflict, gin.H{"error": "Invite has already been accepted"})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"valid": true,
		"email": user.Email,
	})
}

// AcceptInviteRequest represents the request body for accepting an invite.
type AcceptInviteRequest struct {
	Token    string `json:"token" binding:"required"`
	Name     string `json:"name" binding:"required"`
	Password string `json:"password" binding:"required,min=8"`
}

// AcceptInvite accepts an invitation and sets the user's password (public endpoint).
func (h *UserHandler) AcceptInvite(c *gin.Context) {
	var req AcceptInviteRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	var user models.User
	if err := h.DB.Where("invite_token = ?", req.Token).First(&user).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Invalid or expired invite token"})
		return
	}

	// Check if token is expired
	if user.InviteExpires != nil && user.InviteExpires.Before(time.Now()) {
		// Mark as expired
		h.DB.Model(&user).Update("invite_status", "expired")
		c.JSON(http.StatusGone, gin.H{"error": "Invite token has expired"})
		return
	}

	// Check if already accepted
	if user.InviteStatus != "pending" {
		c.JSON(http.StatusConflict, gin.H{"error": "Invite has already been accepted"})
		return
	}

	// Set password and activate user
	if err := user.SetPassword(req.Password); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to set password"})
		return
	}

	if err := h.DB.Model(&user).Updates(map[string]any{
		"name":           req.Name,
		"password_hash":  user.PasswordHash,
		"enabled":        true,
		"invite_token":   "",  // Clear token
		"invite_expires": nil, // Clear expiration
		"invite_status":  "accepted",
	}).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to accept invite"})
		return
	}

	h.logUserAudit(c, "user_invite_accept", &user, map[string]any{
		"target_email":  user.Email,
		"invite_status": "accepted",
	})

	c.JSON(http.StatusOK, gin.H{
		"message": "Invite accepted successfully",
		"email":   user.Email,
	})
}
