package handlers

import (
	"net/http"
	"strconv"
	"strings"

	"github.com/labstack/echo/v4"
	"github.com/rs/zerolog/log"
	"gorm.io/gorm"

	"github.com/treytartt/honeydue-api/internal/admin/dto"
	"github.com/treytartt/honeydue-api/internal/models"
)

// escapeLikeWildcards escapes SQL LIKE wildcards (%, _) in user input
// to prevent wildcard injection in LIKE queries.
func escapeLikeWildcards(s string) string {
	s = strings.ReplaceAll(s, "\\", "\\\\")
	s = strings.ReplaceAll(s, "%", "\\%")
	s = strings.ReplaceAll(s, "_", "\\_")
	return s
}

// AdminUserHandler handles admin user management endpoints
type AdminUserHandler struct {
	db *gorm.DB
}

// NewAdminUserHandler creates a new admin user handler
func NewAdminUserHandler(db *gorm.DB) *AdminUserHandler {
	return &AdminUserHandler{db: db}
}

// List handles GET /api/admin/users
func (h *AdminUserHandler) List(c echo.Context) error {
	var filters dto.UserFilters
	if err := c.Bind(&filters); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid request body"})
	}

	var users []models.User
	var total int64

	query := h.db.Model(&models.User{}).Preload("Profile")

	// Apply search
	if filters.Search != "" {
		search := "%" + escapeLikeWildcards(filters.Search) + "%"
		query = query.Where(
			"username ILIKE ? OR email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?",
			search, search, search, search,
		)
	}

	// Apply filters
	if filters.IsActive != nil {
		query = query.Where("is_active = ?", *filters.IsActive)
	}
	if filters.IsStaff != nil {
		query = query.Where("is_staff = ?", *filters.IsStaff)
	}
	if filters.IsSuperuser != nil {
		query = query.Where("is_superuser = ?", *filters.IsSuperuser)
	}

	// Get total count
	query.Count(&total)

	// Apply sorting (allowlist prevents SQL injection via sort_by parameter)
	sortBy := filters.GetSafeSortBy([]string{
		"id", "username", "email", "first_name", "last_name",
		"date_joined", "last_login", "is_active", "is_staff", "is_superuser",
	}, "date_joined")
	query = query.Order(sortBy + " " + filters.GetSortDir())

	// Apply pagination
	query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())

	if err := query.Find(&users).Error; err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch users"})
	}

	// Batch COUNT queries for residence and task counts instead of N+1
	userIDs := make([]uint, len(users))
	for i, user := range users {
		userIDs[i] = user.ID
	}

	type countResult struct {
		OwnerID uint
		Count   int64
	}

	residenceCounts := make(map[uint]int64)
	taskCounts := make(map[uint]int64)

	if len(userIDs) > 0 {
		var resCounts []countResult
		h.db.Model(&models.Residence{}).
			Select("owner_id, COUNT(*) as count").
			Where("owner_id IN ?", userIDs).
			Group("owner_id").
			Scan(&resCounts)
		for _, rc := range resCounts {
			residenceCounts[rc.OwnerID] = rc.Count
		}

		var tskCounts []struct {
			CreatedByID uint
			Count       int64
		}
		h.db.Model(&models.Task{}).
			Select("created_by_id, COUNT(*) as count").
			Where("created_by_id IN ?", userIDs).
			Group("created_by_id").
			Scan(&tskCounts)
		for _, tc := range tskCounts {
			taskCounts[tc.CreatedByID] = tc.Count
		}
	}

	// Build response
	responses := make([]dto.UserResponse, len(users))
	for i, user := range users {
		responses[i] = h.toUserResponseWithCounts(&user, int(residenceCounts[user.ID]), int(taskCounts[user.ID]))
	}

	return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
}

// Get handles GET /api/admin/users/:id
func (h *AdminUserHandler) Get(c echo.Context) error {
	id, err := strconv.ParseUint(c.Param("id"), 10, 32)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid user ID"})
	}

	var user models.User
	if err := h.db.Preload("Profile").Preload("OwnedResidences").First(&user, id).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "User not found"})
		}
		return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user"})
	}

	// Build detailed response
	response := dto.UserDetailResponse{
		UserResponse: h.toUserResponse(&user),
	}

	// Add residences
	for _, res := range user.OwnedResidences {
		response.Residences = append(response.Residences, dto.ResidenceSummary{
			ID:       res.ID,
			Name:     res.Name,
			IsOwner:  true,
			IsActive: res.IsActive,
		})
	}

	// Add timezone from notification preferences
	var prefs models.NotificationPreference
	if err := h.db.Where("user_id = ?", user.ID).First(&prefs).Error; err == nil {
		response.Timezone = prefs.Timezone
	}

	return c.JSON(http.StatusOK, response)
}

// Create handles POST /api/admin/users
func (h *AdminUserHandler) Create(c echo.Context) error {
	var req dto.CreateUserRequest
	if err := c.Bind(&req); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid request body"})
	}

	// Check if username exists
	var count int64
	h.db.Model(&models.User{}).Where("username = ?", req.Username).Count(&count)
	if count > 0 {
		return c.JSON(http.StatusConflict, map[string]interface{}{"error": "Username already exists"})
	}

	// Check if email exists
	h.db.Model(&models.User{}).Where("email = ?", req.Email).Count(&count)
	if count > 0 {
		return c.JSON(http.StatusConflict, map[string]interface{}{"error": "Email already exists"})
	}

	user := models.User{
		Username:  req.Username,
		Email:     req.Email,
		FirstName: req.FirstName,
		LastName:  req.LastName,
		IsActive:  true,
		IsStaff:   false,
	}

	if req.IsActive != nil {
		user.IsActive = *req.IsActive
	}
	if req.IsStaff != nil {
		user.IsStaff = *req.IsStaff
	}
	if req.IsSuperuser != nil {
		user.IsSuperuser = *req.IsSuperuser
	}

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

	if err := h.db.Create(&user).Error; err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create user"})
	}

	// Create profile with phone number
	profile := models.UserProfile{
		UserID:      user.ID,
		PhoneNumber: req.PhoneNumber,
	}
	if err := h.db.Create(&profile).Error; err != nil {
		log.Warn().Err(err).Uint("user_id", user.ID).Msg("Failed to create user profile")
	}

	// Reload with profile
	h.db.Preload("Profile").First(&user, user.ID)
	return c.JSON(http.StatusCreated, h.toUserResponse(&user))
}

// Update handles PUT /api/admin/users/:id
func (h *AdminUserHandler) Update(c echo.Context) error {
	id, err := strconv.ParseUint(c.Param("id"), 10, 32)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid user ID"})
	}

	var user models.User
	if err := h.db.First(&user, id).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "User not found"})
		}
		return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user"})
	}

	var req dto.UpdateUserRequest
	if err := c.Bind(&req); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid request body"})
	}

	// Check username uniqueness if changing
	if req.Username != nil && *req.Username != user.Username {
		var count int64
		h.db.Model(&models.User{}).Where("username = ? AND id != ?", *req.Username, id).Count(&count)
		if count > 0 {
			return c.JSON(http.StatusConflict, map[string]interface{}{"error": "Username already exists"})
		}
		user.Username = *req.Username
	}

	// Check email uniqueness if changing
	if req.Email != nil && *req.Email != user.Email {
		var count int64
		h.db.Model(&models.User{}).Where("email = ? AND id != ?", *req.Email, id).Count(&count)
		if count > 0 {
			return c.JSON(http.StatusConflict, map[string]interface{}{"error": "Email already exists"})
		}
		user.Email = *req.Email
	}

	if req.FirstName != nil {
		user.FirstName = *req.FirstName
	}
	if req.LastName != nil {
		user.LastName = *req.LastName
	}
	if req.IsActive != nil {
		user.IsActive = *req.IsActive
	}
	if req.IsStaff != nil {
		user.IsStaff = *req.IsStaff
	}
	if req.IsSuperuser != nil {
		user.IsSuperuser = *req.IsSuperuser
	}
	if req.Password != nil {
		if err := user.SetPassword(*req.Password); err != nil {
			return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to hash password"})
		}
	}

	if err := h.db.Save(&user).Error; err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update user"})
	}

	// Update profile fields if provided
	if req.PhoneNumber != nil || req.Verified != nil {
		var profile models.UserProfile
		if err := h.db.Where("user_id = ?", user.ID).First(&profile).Error; err == nil {
			if req.PhoneNumber != nil {
				profile.PhoneNumber = *req.PhoneNumber
			}
			if req.Verified != nil {
				profile.Verified = *req.Verified
			}
			h.db.Save(&profile)
		} else {
			// Create profile if it doesn't exist
			profile = models.UserProfile{UserID: user.ID}
			if req.PhoneNumber != nil {
				profile.PhoneNumber = *req.PhoneNumber
			}
			if req.Verified != nil {
				profile.Verified = *req.Verified
			}
			h.db.Create(&profile)
		}
	}

	h.db.Preload("Profile").First(&user, id)
	return c.JSON(http.StatusOK, h.toUserResponse(&user))
}

// Delete handles DELETE /api/admin/users/:id
func (h *AdminUserHandler) Delete(c echo.Context) error {
	id, err := strconv.ParseUint(c.Param("id"), 10, 32)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid user ID"})
	}

	var user models.User
	if err := h.db.First(&user, id).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "User not found"})
		}
		return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user"})
	}

	// Soft delete - just deactivate
	user.IsActive = false
	if err := h.db.Save(&user).Error; err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete user"})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{"message": "User deactivated successfully"})
}

// BulkDelete handles DELETE /api/admin/users/bulk
func (h *AdminUserHandler) BulkDelete(c echo.Context) error {
	var req dto.BulkDeleteRequest
	if err := c.Bind(&req); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid request body"})
	}

	// Soft delete - deactivate all
	if err := h.db.Model(&models.User{}).Where("id IN ?", req.IDs).Update("is_active", false).Error; err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete users"})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{"message": "Users deactivated successfully", "count": len(req.IDs)})
}

// toUserResponseWithCounts converts a User model to UserResponse DTO with pre-computed counts
func (h *AdminUserHandler) toUserResponseWithCounts(user *models.User, residenceCount, taskCount int) dto.UserResponse {
	response := h.buildUserResponse(user)
	response.ResidenceCount = residenceCount
	response.TaskCount = taskCount
	return response
}

// toUserResponse converts a User model to UserResponse DTO (fetches counts individually)
func (h *AdminUserHandler) toUserResponse(user *models.User) dto.UserResponse {
	response := h.buildUserResponse(user)

	// Get counts individually (used for single-user views)
	var residenceCount, taskCount int64
	h.db.Model(&models.Residence{}).Where("owner_id = ?", user.ID).Count(&residenceCount)
	h.db.Model(&models.Task{}).Where("created_by_id = ?", user.ID).Count(&taskCount)
	response.ResidenceCount = int(residenceCount)
	response.TaskCount = int(taskCount)

	return response
}

// buildUserResponse creates the base UserResponse without counts
func (h *AdminUserHandler) buildUserResponse(user *models.User) dto.UserResponse {
	response := dto.UserResponse{
		ID:          user.ID,
		Username:    user.Username,
		Email:       user.Email,
		FirstName:   user.FirstName,
		LastName:    user.LastName,
		IsActive:    user.IsActive,
		IsStaff:     user.IsStaff,
		IsSuperuser: user.IsSuperuser,
		DateJoined:  user.DateJoined.Format("2006-01-02T15:04:05Z"),
	}

	if user.LastLogin != nil {
		lastLogin := user.LastLogin.Format("2006-01-02T15:04:05Z")
		response.LastLogin = &lastLogin
	}

	if user.Profile != nil {
		response.Verified = user.Profile.Verified
		if user.Profile.PhoneNumber != "" {
			response.PhoneNumber = &user.Profile.PhoneNumber
		}
	}

	return response
}
