package user

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

	"github.com/gin-gonic/gin"

	"security-center-backend/internal/model"
	"security-center-backend/internal/pkg/response"
	"security-center-backend/internal/service/audit"
	"security-center-backend/internal/service/jwt"
	"security-center-backend/internal/service/rbac"
)

type Handler struct {
	service *rbac.Service
	audit   *audit.Service
}

func NewHandler(service *rbac.Service, auditLogger *audit.Service) *Handler {
	return &Handler{service: service, audit: auditLogger}
}

func (h *Handler) List(c *gin.Context) {
	page := parseQueryInt(c, "page", 1)
	pageSize := parseQueryInt(c, "page_size", 20)
	status := model.UserStatus(strings.ToLower(strings.TrimSpace(c.Query("status"))))
	roleID := parseQueryUint(c, "role_id")
	query := c.Query("query")

	result, err := h.service.ListUsers(c.Request.Context(), rbac.ListUsersInput{
		Page:     page,
		PageSize: pageSize,
		Query:    query,
		Status:   status,
		RoleID:   roleID,
	})
	if err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}
	payload := response.PaginatedResponse{
		Items:      result.Items,
		Total:      result.Total,
		Page:       page,
		PageSize:   pageSize,
		TotalPages: calculateTotalPages(result.Total, pageSize),
	}
	response.Success(c, payload)
}

func (h *Handler) Get(c *gin.Context) {
	id, err := parseUintParam(c, "id")
	if err != nil {
		response.Error(c, http.StatusBadRequest, err.Error())
		return
	}
	user, err := h.service.GetUser(c.Request.Context(), id)
	if err != nil {
		response.Error(c, http.StatusNotFound, err.Error())
		return
	}
	response.Success(c, user)
}

type updateUserRequest struct {
	Name          string           `json:"name"`
	Status        model.UserStatus `json:"status"`
	DefaultRoleID *uint64          `json:"default_role_id"`
}

func (h *Handler) Update(c *gin.Context) {
	id, err := parseUintParam(c, "id")
	if err != nil {
		response.Error(c, http.StatusBadRequest, err.Error())
		return
	}
	var req updateUserRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.Error(c, http.StatusBadRequest, "invalid request body")
		return
	}
	updated, err := h.service.UpdateUser(c.Request.Context(), id, rbac.UpdateUserInput{
		Name:          req.Name,
		Status:        req.Status,
		DefaultRoleID: req.DefaultRoleID,
	})
	if err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}
	response.Success(c, updated)
	h.logAudit(c, "user.update", strconv.FormatUint(id, 10), map[string]interface{}{"status": updated.Status, "name": updated.Name})
}

type assignRolesRequest struct {
	RoleIDs       []uint64 `json:"role_ids" binding:"required"`
	DefaultRoleID *uint64  `json:"default_role_id"`
}

func (h *Handler) AssignRoles(c *gin.Context) {
	id, err := parseUintParam(c, "id")
	if err != nil {
		response.Error(c, http.StatusBadRequest, err.Error())
		return
	}
	var req assignRolesRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.Error(c, http.StatusBadRequest, "invalid request body")
		return
	}
	if err := h.service.AssignRoles(c.Request.Context(), id, rbac.AssignRolesInput{
		RoleIDs:       req.RoleIDs,
		DefaultRoleID: req.DefaultRoleID,
	}); err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}
	response.Success(c, gin.H{"status": "roles_assigned"})
	h.logAudit(c, "user.assign_roles", strconv.FormatUint(id, 10), map[string]interface{}{"role_ids": req.RoleIDs})
}

func (h *Handler) RemoveRole(c *gin.Context) {
	id, err := parseUintParam(c, "id")
	if err != nil {
		response.Error(c, http.StatusBadRequest, err.Error())
		return
	}
	roleID, err := parseUintParam(c, "roleId")
	if err != nil {
		response.Error(c, http.StatusBadRequest, err.Error())
		return
	}
	if err := h.service.RemoveRole(c.Request.Context(), id, roleID); err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}
	response.Success(c, gin.H{"status": "role_removed"})
	h.logAudit(c, "user.remove_role", strconv.FormatUint(id, 10), map[string]interface{}{"role_id": roleID})
}

type linkEmployeeRequest struct {
	EmployeeID uint64 `json:"employee_id" binding:"required"`
	Reason     string `json:"reason"`
}

func (h *Handler) LinkEmployee(c *gin.Context) {
	id, err := parseUintParam(c, "id")
	if err != nil {
		response.Error(c, http.StatusBadRequest, err.Error())
		return
	}
	var req linkEmployeeRequest
	if err := c.ShouldBindJSON(&req); err != nil || req.EmployeeID == 0 {
		response.Error(c, http.StatusBadRequest, "invalid request body")
		return
	}
	result, err := h.service.LinkEmployee(c.Request.Context(), id, rbac.LinkEmployeeInput{
		EmployeeID: req.EmployeeID,
		Reason:     req.Reason,
	})
	if err != nil {
		response.Error(c, http.StatusBadRequest, err.Error())
		return
	}
	response.Success(c, result.User)
	h.logAudit(c, result.Action, strconv.FormatUint(id, 10), map[string]interface{}{
		"old_employee_id": oldUint64Value(result.OldEmployeeID),
		"new_employee_id": oldUint64Value(result.NewEmployeeID),
		"old_status":      result.OldStatus,
		"new_status":      result.NewStatus,
		"reason":          result.Reason,
		"email_match":     result.EmailMatch,
		"override_used":   result.OverrideUsed,
	})
}

func oldUint64Value(value *uint64) interface{} {
	if value == nil {
		return nil
	}
	return *value
}

func parseQueryInt(c *gin.Context, key string, fallback int) int {
	value := c.Query(key)
	if value == "" {
		return fallback
	}
	parsed, err := strconv.Atoi(value)
	if err != nil || parsed <= 0 {
		return fallback
	}
	return parsed
}

func parseUintParam(c *gin.Context, key string) (uint64, error) {
	value := c.Param(key)
	return strconv.ParseUint(value, 10, 64)
}

func parseQueryUint(c *gin.Context, key string) uint64 {
	value := c.Query(key)
	if value == "" {
		return 0
	}
	parsed, err := strconv.ParseUint(value, 10, 64)
	if err != nil {
		return 0
	}
	return parsed
}

func calculateTotalPages(total int64, pageSize int) int {
	if pageSize <= 0 {
		return 0
	}
	pages := total / int64(pageSize)
	if total%int64(pageSize) != 0 {
		pages++
	}
	return int(pages)
}

func (h *Handler) logAudit(c *gin.Context, action, resourceID string, metadata map[string]interface{}) {
	if h.audit == nil {
		return
	}
	entry := audit.Entry{
		Action:       action,
		ResourceType: "user",
		ResourceID:   resourceID,
		Metadata:     model.JSONB(metadata),
		IPAddress:    c.ClientIP(),
		UserAgent:    c.GetHeader("User-Agent"),
	}
	if userID, ok := currentUserID(c); ok {
		entry.UserID = &userID
	}
	h.audit.Log(c.Request.Context(), entry)
}

func currentUserID(c *gin.Context) (uint64, bool) {
	claimsValue, ok := c.Get("current_user")
	if !ok {
		return 0, false
	}
	claims, ok := claimsValue.(*jwt.Claims)
	if !ok {
		return 0, false
	}
	return claims.UserID, true
}
