package im

import (
	"net/http"
	"strconv"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/api/dto/converter"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
)

const (
	defaultIMDMListLimit = 100
	maxIMDMListLimit     = 100
)

// DMHandler owns direct-message scope reads and opens on the Workspace IM surface.
type DMHandler struct {
	dms          DMStore
	streamWriter StreamEventWriter
	scopeSync    UserScopeMembershipSync
}

// List handles GET /api/v1/workspaces/:workspaceId/dm-channels.
func (h *DMHandler) List(r api.WorkspaceScopedRequest) (api.Response, error) {
	if h.dms == nil {
		return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Internal server error")
	}
	limit, err := parseDMLimit(r.Gin.Query("limit"))
	if err != nil {
		return api.Response{}, err
	}
	dms, err := h.dms.ListDMChannelsForUserWithLimit(r.Context(), r.UserID(), r.WorkspaceID(), limit)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToDMChannels(dms)), nil
}

// Get handles GET /api/v1/workspaces/:workspaceId/dm-channels/:channelId.
func (h *DMHandler) Get(r api.WorkspaceScopedRequest) (api.Response, error) {
	if h.dms == nil {
		return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Internal server error")
	}
	channelID, err := routeDMChannelID(r)
	if err != nil {
		return api.Response{}, err
	}
	dm, err := h.dms.GetDMChannelForUser(r.Context(), r.UserID(), r.WorkspaceID(), channelID)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToDMChannel(dm)), nil
}

// Open handles POST /api/v1/workspaces/:workspaceId/dm-channels.
func (h *DMHandler) Open(r api.WorkspaceScopedJSON[dto.OpenDMRequest]) (api.Response, error) {
	if h.dms == nil {
		return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Internal server error")
	}
	agentID := strings.TrimSpace(r.Body.AgentID)
	userID := strings.TrimSpace(r.Body.UserID)
	if agentID == "" && userID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "agentId or userId is required")
	}
	if agentID != "" && userID != "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "agentId and userId are mutually exclusive")
	}
	var (
		dm  message.DMChannel
		err error
	)
	if agentID != "" {
		dm, err = h.dms.OpenDMChannelWithAgent(r.Context(), r.UserID(), r.WorkspaceID(), agentID)
	} else {
		dm, err = h.dms.OpenDMChannelWithUser(r.Context(), r.UserID(), r.WorkspaceID(), userID)
	}
	if err != nil {
		return api.Response{}, err
	}
	if err := publishDMOpenedEvent(r.Context(), h.streamWriter, r.WorkspaceID(), r.Workspace.MemberID, dm); err != nil {
		return api.Response{}, err
	}
	// A newly opened DM is a new scope the caller's existing connections have
	// never subscribed to during bootstrap; mirror the channel join path so
	// every active connection starts receiving live-only events for the DM
	// scope without forcing a reconnect.
	if h.scopeSync != nil && strings.TrimSpace(dm.ID) != "" {
		h.scopeSync.SubscribeScopeForUser(r.WorkspaceID(), r.UserID(), dm.ID)
	}
	return api.OK(converter.ToDMChannel(dm)), nil
}

func routeDMChannelID(r api.WorkspaceScopedRequest) (string, error) {
	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	if channelID == "" {
		return "", api.NewHTTPError(http.StatusBadRequest, "channelId is required")
	}
	return channelID, nil
}

func parseDMLimit(raw string) (int, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return defaultIMDMListLimit, nil
	}
	limit, err := strconv.Atoi(trimmed)
	if err != nil || limit <= 0 {
		return 0, api.NewHTTPError(http.StatusBadRequest, "limit must be a positive integer")
	}
	if limit > maxIMDMListLimit {
		return maxIMDMListLimit, nil
	}
	return limit, nil
}
