package role

import (
	"errors"
	"net/http"
	"strconv"

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

	"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}
}

type roleRequest struct {
	Name          string   `json:"name" binding:"required"`
	Description   string   `json:"description"`
	IsDefault     bool     `json:"is_default"`
	PermissionIDs []uint64 `json:"permission_ids"`
}

func (h *Handler) List(c *gin.Context) {
	page := parseQueryInt(c, "page", 1)
	pageSize := parseQueryInt(c, "page_size", 20)
	result, err := h.service.ListRoles(c.Request.Context(), page, pageSize)
	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) Create(c *gin.Context) {
	var req roleRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.Error(c, http.StatusBadRequest, "invalid request body")
		return
	}
	role, err := h.service.CreateRole(c.Request.Context(), rbac.RoleInput{
		Name:          req.Name,
		Description:   req.Description,
		IsDefault:     req.IsDefault,
		PermissionIDs: req.PermissionIDs,
	})
	if err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}
	response.Success(c, role)
	h.logAudit(c, "role.create", strconv.FormatUint(role.ID, 10), map[string]interface{}{"name": role.Name})
}

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 roleRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.Error(c, http.StatusBadRequest, "invalid request body")
		return
	}
	role, err := h.service.UpdateRole(c.Request.Context(), id, rbac.RoleInput{
		Name:          req.Name,
		Description:   req.Description,
		IsDefault:     req.IsDefault,
		PermissionIDs: req.PermissionIDs,
	})
	if err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}
	response.Success(c, role)
	h.logAudit(c, "role.update", strconv.FormatUint(id, 10), map[string]interface{}{"name": role.Name})
}

func (h *Handler) Delete(c *gin.Context) {
	id, err := parseUintParam(c, "id")
	if err != nil {
		response.Error(c, http.StatusBadRequest, err.Error())
		return
	}
	if err := h.service.DeleteRole(c.Request.Context(), id); err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}
	response.Success(c, gin.H{"status": "role_deleted"})
	h.logAudit(c, "role.delete", strconv.FormatUint(id, 10), nil)
}

type assignPermissionsRequest struct {
	PermissionIDs []uint64 `json:"permission_ids" binding:"required"`
}

func (h *Handler) AssignPermissions(c *gin.Context) {
	id, err := parseUintParam(c, "id")
	if err != nil {
		response.Error(c, http.StatusBadRequest, err.Error())
		return
	}
	var req assignPermissionsRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.Error(c, http.StatusBadRequest, "invalid request body")
		return
	}
	if err := h.service.AssignPermissions(c.Request.Context(), id, req.PermissionIDs); err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}
	response.Success(c, gin.H{"status": "permissions_assigned"})
	h.logAudit(c, "role.assign_permissions", strconv.FormatUint(id, 10), map[string]interface{}{"permission_ids": req.PermissionIDs})
}

func (h *Handler) ListPermissions(c *gin.Context) {
	perms, err := h.service.ListPermissions(c.Request.Context())
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			response.Success(c, []model.Permission{})
		} else {
			response.Error(c, http.StatusInternalServerError, err.Error())
		}
		return
	}
	response.Success(c, perms)
}

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 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: "role",
		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
}
