package server

import (
	"context"
	"errors"
	"net/http"

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

	"scrabble/backend/internal/account"
	"scrabble/backend/internal/notify"
)

// chatAccessRequest is the gateway's chat write-eligibility query, addressed either
// by Telegram identity (ExternalID — the join path, when the bot sees a user enter
// the chat) or by account id (UserID — the change path, resolving an emitted
// chat-access-changed event). Exactly one field is set.
type chatAccessRequest struct {
	ExternalID string `json:"external_id"`
	UserID     string `json:"user_id"`
}

// chatAccessResponse is the resolved eligibility. ExternalID echoes the account's
// Telegram identity (empty when it has none — the gateway then has nothing to gate);
// Registered reports whether the lookup found an account at all; Eligible is the
// final gate the bot applies (registered and neither admin-suspended nor chat-muted).
type chatAccessResponse struct {
	ExternalID string `json:"external_id"`
	Registered bool   `json:"registered"`
	Eligible   bool   `json:"eligible"`
}

// handleChatAccess resolves whether a Telegram user may write in the moderated
// discussion chat. It is gateway-internal: the gateway's bot-link serves the bot's
// join-time query (by external_id) and resolves an emitted chat-access-changed event
// (by user_id) through it.
func (s *Server) handleChatAccess(c *gin.Context) {
	var req chatAccessRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		abortBadRequest(c, "invalid body")
		return
	}
	switch {
	case req.ExternalID != "":
		s.respondChatAccessByExternalID(c, req.ExternalID)
	case req.UserID != "":
		s.respondChatAccessByUserID(c, req.UserID)
	default:
		abortBadRequest(c, "external_id or user_id required")
	}
}

// respondChatAccessByExternalID answers the join-path query: an unknown identity is
// reported unregistered (and left muted); a known one carries its current eligibility.
func (s *Server) respondChatAccessByExternalID(c *gin.Context, externalID string) {
	ctx := c.Request.Context()
	resp := chatAccessResponse{ExternalID: externalID}
	acc, err := s.accounts.AccountByIdentity(ctx, account.KindTelegram, externalID)
	if errors.Is(err, account.ErrNotFound) {
		c.JSON(http.StatusOK, resp)
		return
	}
	if err != nil {
		s.abortErr(c, err)
		return
	}
	resp.Registered = true
	eligible, err := s.chatEligible(ctx, acc.ID)
	if err != nil {
		s.abortErr(c, err)
		return
	}
	resp.Eligible = eligible
	c.JSON(http.StatusOK, resp)
}

// respondChatAccessByUserID answers the change-path query: an account with no
// Telegram identity carries an empty external_id (nothing for the gateway to gate);
// otherwise it carries the identity and the current eligibility.
func (s *Server) respondChatAccessByUserID(c *gin.Context, raw string) {
	ctx := c.Request.Context()
	uid, err := uuid.Parse(raw)
	if err != nil {
		abortBadRequest(c, "invalid user_id")
		return
	}
	var resp chatAccessResponse
	ext, err := s.accounts.IdentityExternalID(ctx, uid, account.KindTelegram)
	if errors.Is(err, account.ErrNotFound) {
		c.JSON(http.StatusOK, resp)
		return
	}
	if err != nil {
		s.abortErr(c, err)
		return
	}
	resp.ExternalID = ext
	resp.Registered = true
	eligible, err := s.chatEligible(ctx, uid)
	if err != nil {
		s.abortErr(c, err)
		return
	}
	resp.Eligible = eligible
	c.JSON(http.StatusOK, resp)
}

// chatEligible reports whether the account may write in the moderated discussion
// chat: not currently admin-suspended and not holding the chat_muted role. A
// suspension dominates — it mutes regardless of the role. Registration is established
// by the caller's identity lookup.
func (s *Server) chatEligible(ctx context.Context, accountID uuid.UUID) (bool, error) {
	if _, blocked, err := s.accounts.CurrentSuspension(ctx, accountID); err != nil {
		return false, err
	} else if blocked {
		return false, nil
	}
	muted, err := s.accounts.HasRole(ctx, accountID, account.RoleChatMuted)
	if err != nil {
		return false, err
	}
	return !muted, nil
}

// publishChatAccessChange emits the chat-access-changed signal for the account, so
// the gateway re-resolves the player's chat eligibility and pushes the chat-gate
// command to the bot. Best-effort (notify.Nop when no notifier is wired).
func (s *Server) publishChatAccessChange(id uuid.UUID) {
	s.notifier.Publish(notify.ChatAccessChanged(id))
}
