package server

import (
	"context"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"

	"scrabble/backend/internal/engine"
	"scrabble/backend/internal/lobby"
)

// The /api/v1/user/invitations/* handlers wire friend-game invitations:
// create a 2-4 player invitation, accept/decline as an invitee, cancel as the
// inviter, and list the open invitations touching the caller. Display names for the
// inviter and invitees are resolved from the account store.

// invitationInviteeDTO is one invitee's seat and response with their name resolved.
type invitationInviteeDTO struct {
	AccountID   string `json:"account_id"`
	DisplayName string `json:"display_name"`
	Seat        int    `json:"seat"`
	Response    string `json:"response"`
}

// invitationDTO is a friend-game invitation with its settings and invitees.
type invitationDTO struct {
	ID                   string                 `json:"id"`
	Inviter              accountRefDTO          `json:"inviter"`
	Invitees             []invitationInviteeDTO `json:"invitees"`
	Variant              string                 `json:"variant"`
	TurnTimeoutSecs      int                    `json:"turn_timeout_secs"`
	HintsAllowed         bool                   `json:"hints_allowed"`
	HintsPerPlayer       int                    `json:"hints_per_player"`
	DropoutTiles         string                 `json:"dropout_tiles"`
	MultipleWordsPerTurn bool                   `json:"multiple_words_per_turn"`
	Status               string                 `json:"status"`
	GameID               string                 `json:"game_id,omitempty"`
	ExpiresAtUnix        int64                  `json:"expires_at_unix"`
}

// invitationListDTO is the caller's open invitations.
type invitationListDTO struct {
	Invitations []invitationDTO `json:"invitations"`
}

// createInvitationRequest proposes a friend game to the named invitees.
type createInvitationRequest struct {
	InviteeIDs           []string `json:"invitee_ids"`
	Variant              string   `json:"variant"`
	TurnTimeoutSecs      int      `json:"turn_timeout_secs"`
	HintsAllowed         bool     `json:"hints_allowed"`
	HintsPerPlayer       int      `json:"hints_per_player"`
	DropoutTiles         string   `json:"dropout_tiles"`
	MultipleWordsPerTurn bool     `json:"multiple_words_per_turn"`
}

// invitationDTOFrom projects a lobby invitation, resolving names through memo.
func (s *Server) invitationDTOFrom(ctx context.Context, inv lobby.Invitation, memo map[string]string) invitationDTO {
	dto := invitationDTO{
		ID:                   inv.ID.String(),
		Inviter:              s.namedRef(ctx, inv.InviterID, memo),
		Invitees:             make([]invitationInviteeDTO, 0, len(inv.Invitees)),
		Variant:              inv.Settings.Variant.String(),
		TurnTimeoutSecs:      int(inv.Settings.TurnTimeout.Seconds()),
		HintsAllowed:         inv.Settings.HintsAllowed,
		HintsPerPlayer:       inv.Settings.HintsPerPlayer,
		DropoutTiles:         inv.Settings.DropoutTiles.String(),
		MultipleWordsPerTurn: inv.Settings.MultipleWordsPerTurn,
		Status:               inv.Status,
		ExpiresAtUnix:        inv.ExpiresAt.Unix(),
	}
	if inv.GameID != nil {
		dto.GameID = inv.GameID.String()
	}
	for _, iv := range inv.Invitees {
		ref := s.namedRef(ctx, iv.AccountID, memo)
		dto.Invitees = append(dto.Invitees, invitationInviteeDTO{
			AccountID:   ref.AccountID,
			DisplayName: ref.DisplayName,
			Seat:        iv.Seat,
			Response:    iv.Response,
		})
	}
	return dto
}

// handleCreateInvitation records a new friend-game invitation from the caller.
func (s *Server) handleCreateInvitation(c *gin.Context) {
	uid, ok := userID(c)
	if !ok {
		abortBadRequest(c, "missing identity")
		return
	}
	var req createInvitationRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		abortBadRequest(c, "invalid request body")
		return
	}
	variant, err := engine.ParseVariant(req.Variant)
	if err != nil {
		abortBadRequest(c, "unknown variant")
		return
	}
	if !s.ensureVariantAllowed(c, uid, variant.String()) {
		return
	}
	settings := lobby.InvitationSettings{
		Variant:              variant,
		HintsAllowed:         req.HintsAllowed,
		HintsPerPlayer:       req.HintsPerPlayer,
		MultipleWordsPerTurn: req.MultipleWordsPerTurn,
	}
	if req.TurnTimeoutSecs > 0 {
		settings.TurnTimeout = time.Duration(req.TurnTimeoutSecs) * time.Second
	}
	if req.DropoutTiles != "" {
		dropout, err := engine.ParseDropoutTiles(req.DropoutTiles)
		if err != nil {
			abortBadRequest(c, "unknown dropout_tiles")
			return
		}
		settings.DropoutTiles = dropout
	}
	inviteeIDs := make([]uuid.UUID, 0, len(req.InviteeIDs))
	for _, raw := range req.InviteeIDs {
		id, ok := parseUUIDField(raw)
		if !ok {
			abortBadRequest(c, "invalid invitee id")
			return
		}
		inviteeIDs = append(inviteeIDs, id)
	}
	inv, err := s.invitations.CreateInvitation(c.Request.Context(), uid, inviteeIDs, settings)
	if err != nil {
		s.abortErr(c, err)
		return
	}
	c.JSON(http.StatusOK, s.invitationDTOFrom(c.Request.Context(), inv, map[string]string{}))
}

// handleAcceptInvitation records the caller's acceptance, starting the game when it
// completes the set.
func (s *Server) handleAcceptInvitation(c *gin.Context) {
	s.respondInvitation(c, true)
}

// handleDeclineInvitation records the caller's decline, cancelling the invitation.
func (s *Server) handleDeclineInvitation(c *gin.Context) {
	s.respondInvitation(c, false)
}

// respondInvitation applies the caller's accept/decline to the :id invitation.
func (s *Server) respondInvitation(c *gin.Context, accept bool) {
	uid, ok := userID(c)
	if !ok {
		abortBadRequest(c, "missing identity")
		return
	}
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		abortBadRequest(c, "invalid invitation id")
		return
	}
	inv, err := s.invitations.RespondInvitation(c.Request.Context(), id, uid, accept)
	if err != nil {
		s.abortErr(c, err)
		return
	}
	c.JSON(http.StatusOK, s.invitationDTOFrom(c.Request.Context(), inv, map[string]string{}))
}

// handleCancelInvitation withdraws the caller's own pending invitation.
func (s *Server) handleCancelInvitation(c *gin.Context) {
	uid, ok := userID(c)
	if !ok {
		abortBadRequest(c, "missing identity")
		return
	}
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		abortBadRequest(c, "invalid invitation id")
		return
	}
	if err := s.invitations.CancelInvitation(c.Request.Context(), id, uid); err != nil {
		s.abortErr(c, err)
		return
	}
	c.JSON(http.StatusOK, okResponse{OK: true})
}

// handleListInvitations returns the open invitations touching the caller.
func (s *Server) handleListInvitations(c *gin.Context) {
	uid, ok := userID(c)
	if !ok {
		abortBadRequest(c, "missing identity")
		return
	}
	invs, err := s.invitations.ListInvitations(c.Request.Context(), uid)
	if err != nil {
		s.abortErr(c, err)
		return
	}
	memo := map[string]string{}
	out := make([]invitationDTO, 0, len(invs))
	for _, inv := range invs {
		out = append(out, s.invitationDTOFrom(c.Request.Context(), inv, memo))
	}
	c.JSON(http.StatusOK, invitationListDTO{Invitations: out})
}
