package handler

import (
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"

	"tanabata/backend/internal/domain"
	"tanabata/backend/internal/port"
	"tanabata/backend/internal/service"
)

// UserHandler handles all /users endpoints.
type UserHandler struct {
	userSvc *service.UserService
}

// NewUserHandler creates a UserHandler.
func NewUserHandler(userSvc *service.UserService) *UserHandler {
	return &UserHandler{userSvc: userSvc}
}

// ---------------------------------------------------------------------------
// Response types
// ---------------------------------------------------------------------------

type userJSON struct {
	ID        int16  `json:"id"`
	Name      string `json:"name"`
	IsAdmin   bool   `json:"is_admin"`
	CanCreate bool   `json:"can_create"`
	IsBlocked bool   `json:"is_blocked"`
}

func toUserJSON(u domain.User) userJSON {
	return userJSON{
		ID:        u.ID,
		Name:      u.Name,
		IsAdmin:   u.IsAdmin,
		CanCreate: u.CanCreate,
		IsBlocked: u.IsBlocked,
	}
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

func requireAdmin(c *gin.Context) bool {
	_, isAdmin, _ := domain.UserFromContext(c.Request.Context())
	if !isAdmin {
		respondError(c, domain.ErrForbidden)
		return false
	}
	return true
}

func parseUserID(c *gin.Context) (int16, bool) {
	n, err := strconv.ParseInt(c.Param("user_id"), 10, 16)
	if err != nil {
		respondError(c, domain.ErrValidation)
		return 0, false
	}
	return int16(n), true
}

// ---------------------------------------------------------------------------
// GET /users/me
// ---------------------------------------------------------------------------

func (h *UserHandler) GetMe(c *gin.Context) {
	u, err := h.userSvc.GetMe(c.Request.Context())
	if err != nil {
		respondError(c, err)
		return
	}
	respondJSON(c, http.StatusOK, toUserJSON(*u))
}

// ---------------------------------------------------------------------------
// PATCH /users/me
// ---------------------------------------------------------------------------

func (h *UserHandler) UpdateMe(c *gin.Context) {
	var body struct {
		Name     string  `json:"name"`
		Password *string `json:"password"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		respondError(c, domain.ErrValidation)
		return
	}

	updated, err := h.userSvc.UpdateMe(c.Request.Context(), service.UpdateMeParams{
		Name:     body.Name,
		Password: body.Password,
	})
	if err != nil {
		respondError(c, err)
		return
	}
	respondJSON(c, http.StatusOK, toUserJSON(*updated))
}

// ---------------------------------------------------------------------------
// GET /users  (admin)
// ---------------------------------------------------------------------------

func (h *UserHandler) List(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	params := port.OffsetParams{
		Sort:  c.DefaultQuery("sort", "id"),
		Order: c.DefaultQuery("order", "asc"),
	}
	if s := c.Query("limit"); s != "" {
		if n, err := strconv.Atoi(s); err == nil {
			params.Limit = n
		}
	}
	if s := c.Query("offset"); s != "" {
		if n, err := strconv.Atoi(s); err == nil {
			params.Offset = n
		}
	}

	page, err := h.userSvc.List(c.Request.Context(), params)
	if err != nil {
		respondError(c, err)
		return
	}

	items := make([]userJSON, len(page.Items))
	for i, u := range page.Items {
		items[i] = toUserJSON(u)
	}
	respondJSON(c, http.StatusOK, gin.H{
		"items":  items,
		"total":  page.Total,
		"offset": page.Offset,
		"limit":  page.Limit,
	})
}

// ---------------------------------------------------------------------------
// POST /users  (admin)
// ---------------------------------------------------------------------------

func (h *UserHandler) Create(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	var body struct {
		Name      string `json:"name"     binding:"required"`
		Password  string `json:"password" binding:"required"`
		IsAdmin   bool   `json:"is_admin"`
		CanCreate bool   `json:"can_create"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		respondError(c, domain.ErrValidation)
		return
	}

	created, err := h.userSvc.Create(c.Request.Context(), service.CreateUserParams{
		Name:      body.Name,
		Password:  body.Password,
		IsAdmin:   body.IsAdmin,
		CanCreate: body.CanCreate,
	})
	if err != nil {
		respondError(c, err)
		return
	}
	respondJSON(c, http.StatusCreated, toUserJSON(*created))
}

// ---------------------------------------------------------------------------
// GET /users/:user_id  (admin)
// ---------------------------------------------------------------------------

func (h *UserHandler) Get(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	id, ok := parseUserID(c)
	if !ok {
		return
	}

	u, err := h.userSvc.Get(c.Request.Context(), id)
	if err != nil {
		respondError(c, err)
		return
	}
	respondJSON(c, http.StatusOK, toUserJSON(*u))
}

// ---------------------------------------------------------------------------
// PATCH /users/:user_id  (admin)
// ---------------------------------------------------------------------------

func (h *UserHandler) UpdateAdmin(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	id, ok := parseUserID(c)
	if !ok {
		return
	}

	var body struct {
		IsAdmin   *bool `json:"is_admin"`
		CanCreate *bool `json:"can_create"`
		IsBlocked *bool `json:"is_blocked"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		respondError(c, domain.ErrValidation)
		return
	}

	updated, err := h.userSvc.UpdateAdmin(c.Request.Context(), id, service.UpdateAdminParams{
		IsAdmin:   body.IsAdmin,
		CanCreate: body.CanCreate,
		IsBlocked: body.IsBlocked,
	})
	if err != nil {
		respondError(c, err)
		return
	}
	respondJSON(c, http.StatusOK, toUserJSON(*updated))
}

// ---------------------------------------------------------------------------
// DELETE /users/:user_id  (admin)
// ---------------------------------------------------------------------------

func (h *UserHandler) Delete(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	id, ok := parseUserID(c)
	if !ok {
		return
	}

	if err := h.userSvc.Delete(c.Request.Context(), id); err != nil {
		respondError(c, err)
		return
	}
	c.Status(http.StatusNoContent)
}