package server

import (
	"net/http"

	"galaxy/backend/internal/lobby"
	"galaxy/backend/internal/server/handlers"
	"galaxy/backend/internal/server/httperr"
	"galaxy/backend/internal/server/middleware/userid"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"go.uber.org/zap"
)

// UserLobbyInvitesHandlers groups the invite-lifecycle handlers under
// `/api/v1/user/lobby/games/{game_id}/invites/*`. The current implementation ships real
// implementations backed by `*lobby.Service`.
type UserLobbyInvitesHandlers struct {
	svc *lobby.Service
	logger *zap.Logger
}

// NewUserLobbyInvitesHandlers constructs the handler set. svc may be
// nil — in that case every handler returns 501 not_implemented.
func NewUserLobbyInvitesHandlers(svc *lobby.Service, logger *zap.Logger) *UserLobbyInvitesHandlers {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &UserLobbyInvitesHandlers{svc: svc, logger: logger.Named("http.user.lobby.invites")}
}

// Issue handles POST /api/v1/user/lobby/games/{game_id}/invites.
func (h *UserLobbyInvitesHandlers) Issue() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("userLobbyInvitesIssue")
	}
	return func(c *gin.Context) {
		userID, ok := userid.FromContext(c.Request.Context())
		if !ok {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "X-User-ID header is required")
			return
		}
		gameID, ok := parseGameIDParam(c)
		if !ok {
			return
		}
		var req lobbyInviteIssueRequestWire
		if err := c.ShouldBindJSON(&req); err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be valid JSON")
			return
		}
		var invitedID *uuid.UUID
		if req.InvitedUserID != nil && *req.InvitedUserID != "" {
			parsed, err := uuid.Parse(*req.InvitedUserID)
			if err != nil {
				httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "invited_user_id must be a valid UUID")
				return
			}
			invitedID = &parsed
		}
		expires, err := parseTimePtrField(req.ExpiresAt, "expires_at")
		if err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, err.Error())
			return
		}
		ctx := c.Request.Context()
		raceName := ""
		if req.RaceName != nil {
			raceName = *req.RaceName
		}
		invite, err := h.svc.IssueInvite(ctx, lobby.IssueInviteInput{
			GameID: gameID,
			InviterUserID: userID,
			InvitedUserID: invitedID,
			RaceName: raceName,
			ExpiresAt: expires,
		})
		if err != nil {
			respondLobbyError(c, h.logger, "user lobby invites issue", ctx, err)
			return
		}
		c.JSON(http.StatusCreated, lobbyInviteDetailToWire(invite))
	}
}

// Redeem handles POST /api/v1/user/lobby/games/{game_id}/invites/{invite_id}/redeem.
func (h *UserLobbyInvitesHandlers) Redeem() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("userLobbyInvitesRedeem")
	}
	return func(c *gin.Context) {
		userID, ok := userid.FromContext(c.Request.Context())
		if !ok {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "X-User-ID header is required")
			return
		}
		gameID, ok := parseGameIDParam(c)
		if !ok {
			return
		}
		inviteID, ok := parseInviteIDParam(c)
		if !ok {
			return
		}
		ctx := c.Request.Context()
		invite, err := h.svc.RedeemInvite(ctx, userID, gameID, inviteID)
		if err != nil {
			respondLobbyError(c, h.logger, "user lobby invites redeem", ctx, err)
			return
		}
		c.JSON(http.StatusOK, lobbyInviteDetailToWire(invite))
	}
}

// Decline handles POST /api/v1/user/lobby/games/{game_id}/invites/{invite_id}/decline.
func (h *UserLobbyInvitesHandlers) Decline() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("userLobbyInvitesDecline")
	}
	return func(c *gin.Context) {
		userID, ok := userid.FromContext(c.Request.Context())
		if !ok {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "X-User-ID header is required")
			return
		}
		gameID, ok := parseGameIDParam(c)
		if !ok {
			return
		}
		inviteID, ok := parseInviteIDParam(c)
		if !ok {
			return
		}
		ctx := c.Request.Context()
		invite, err := h.svc.DeclineInvite(ctx, userID, gameID, inviteID)
		if err != nil {
			respondLobbyError(c, h.logger, "user lobby invites decline", ctx, err)
			return
		}
		c.JSON(http.StatusOK, lobbyInviteDetailToWire(invite))
	}
}

// Revoke handles POST /api/v1/user/lobby/games/{game_id}/invites/{invite_id}/revoke.
func (h *UserLobbyInvitesHandlers) Revoke() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("userLobbyInvitesRevoke")
	}
	return func(c *gin.Context) {
		userID, ok := userid.FromContext(c.Request.Context())
		if !ok {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "X-User-ID header is required")
			return
		}
		gameID, ok := parseGameIDParam(c)
		if !ok {
			return
		}
		inviteID, ok := parseInviteIDParam(c)
		if !ok {
			return
		}
		ctx := c.Request.Context()
		caller := userID
		invite, err := h.svc.RevokeInvite(ctx, &caller, false, gameID, inviteID)
		if err != nil {
			respondLobbyError(c, h.logger, "user lobby invites revoke", ctx, err)
			return
		}
		c.JSON(http.StatusOK, lobbyInviteDetailToWire(invite))
	}
}

// lobbyInviteIssueRequestWire mirrors `LobbyInviteIssueRequest`.
type lobbyInviteIssueRequestWire struct {
	InvitedUserID *string `json:"invited_user_id,omitempty"`
	RaceName *string `json:"race_name,omitempty"`
	ExpiresAt *string `json:"expires_at,omitempty"`
}
