package channel

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"
	"github.com/tidbcloud/AgentTeam/internal/unread"
)

const (
	defaultThreadListLimit = 100
	maxThreadListLimit     = 100
)

// ThreadHandler owns subscribed-thread and channel reply-thread routes.
type ThreadHandler struct {
	store             ThreadStore
	subsystemChannels ThreadSubsystemChannelStore
	appStore          ThreadAppStore
	reviewStore       ThreadReviewStore
	messageFanout     MessageFanout
	realtime          ChannelRealtime
	hub               ThreadReviewHub
	unreadRuntime     ChannelUnreadReader
}

// List handles GET /api/threads.
// Returns the reply threads the current user is subscribed to.
func (h *ThreadHandler) List(r api.WorkspaceScopedRequest) (api.Response, error) {
	return h.ListLegacy(r)
}

// Get handles GET /api/threads/:threadChannelId.
// It returns one visible reply thread, even when the current user has not
// subscribed to the thread inbox.
func (h *ThreadHandler) Get(r api.WorkspaceScopedRequest) (api.Response, error) {
	authenticated, workspaceID, threadChannelID, err := h.threadActionContext(r)
	if err != nil {
		return api.Response{}, err
	}

	thread, err := h.store.GetReplyThreadForUser(r.Context(), authenticated.User.ID, workspaceID, threadChannelID)
	if err != nil {
		return api.Response{}, err
	}

	return api.OK(converter.ToSubscribedThread(thread)), nil
}

// Subscribe handles POST /api/threads/:threadChannelId/subscribe.
// Subscribes the current user to a reply thread.
func (h *ThreadHandler) Subscribe(r api.WorkspaceScopedRequest) (api.Response, error) {
	authenticated, workspaceID, threadChannelID, err := h.threadActionContext(r)
	if err != nil {
		return api.Response{}, err
	}

	var request dto.ThreadSubscribeRequest
	if r.Gin.Request.Body != nil && r.Gin.Request.ContentLength != 0 {
		request, err = api.BindJSON[dto.ThreadSubscribeRequest](r.Gin)
		if err != nil {
			return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "Invalid subscribe request")
		}
	}

	state := strings.TrimSpace(request.State)
	if state == "" {
		if err := h.store.SubscribeThreadForUser(r.Context(), authenticated.User.ID, workspaceID, threadChannelID); err != nil {
			return api.Response{}, err
		}
	} else if err := h.store.SetThreadSubscriptionStateForUser(r.Context(), authenticated.User.ID, workspaceID, threadChannelID, state); err != nil {
		return api.Response{}, err
	}
	applyChannelMemberScopeInvalidated(h.realtime, h.unreadRuntime, workspaceID, authenticated.User.ID, r.Workspace.MemberID, threadChannelID, unread.ScopeTypeThread, "thread_subscribe")

	conversation, err := h.store.LoadConversationForUser(r.Context(), authenticated.User.ID, workspaceID, threadChannelID, true)
	if err != nil {
		return api.Response{}, err
	}

	return api.OK(converter.ToThreadSubscribeResponse(conversation)), nil
}

// Unsubscribe handles POST /api/threads/:threadChannelId/unsubscribe.
// Unsubscribes the current user from a reply thread.
func (h *ThreadHandler) Unsubscribe(r api.WorkspaceScopedRequest) (api.Response, error) {
	authenticated, workspaceID, threadChannelID, err := h.threadActionContext(r)
	if err != nil {
		return api.Response{}, err
	}

	if err := h.store.UnsubscribeThreadForUser(r.Context(), authenticated.User.ID, workspaceID, threadChannelID); err != nil {
		return api.Response{}, err
	}
	applyChannelMemberScopeInvalidated(h.realtime, h.unreadRuntime, workspaceID, authenticated.User.ID, r.Workspace.MemberID, threadChannelID, unread.ScopeTypeThread, "thread_unsubscribe")

	return api.OK(dto.EmptyResponse{}), nil
}

// ListForChannel handles GET /api/channels/:channelId/threads.
// Returns reply thread summaries for one channel.
func (h *ThreadHandler) ListForChannel(r api.WorkspaceScopedRequest) (api.Response, error) {
	return h.ListForChannelLegacy(r)
}

// OpenForChannel handles POST /api/channels/:channelId/threads.
// Opens or resolves a reply thread for a parent message in one channel.
func (h *ThreadHandler) OpenForChannel(r api.WorkspaceScopedJSON[dto.AppOpenThreadRequest]) (api.Response, error) {
	request := r.Body
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	if channelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "channelId is required")
	}

	summary, err := h.store.OpenReplyThreadForUser(r.Context(), authenticated.User.ID, workspaceID, channelID, request.ParentMessageID)
	if err != nil {
		return api.Response{}, err
	}
	applyChannelMemberScopeInvalidated(h.realtime, h.unreadRuntime, workspaceID, authenticated.User.ID, r.Workspace.MemberID, summary.ThreadChannelID, unread.ScopeTypeThread, "thread_open")

	return api.OK(converter.ToOpenReplyThreadResponse(summary, nil)), nil
}

// CreateShareCard handles POST /api/threads/:threadChannelId/share-card.
func (h *ThreadHandler) CreateShareCard(r api.WorkspaceScopedJSON[dto.ThreadShareCardCreateRequest]) (api.Response, error) {
	threadChannelID := strings.TrimSpace(r.Gin.Param("threadChannelId"))
	if threadChannelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "threadChannelId is required")
	}
	targetScopeID := strings.TrimSpace(r.Body.TargetScopeID)
	if targetScopeID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "targetScopeId is required")
	}
	appMessage, delivery, err := h.appStore.CreateThreadShareCardForUser(r.Context(), r.User.User, r.WorkspaceID(), threadChannelID, targetScopeID)
	if err != nil {
		return api.Response{}, err
	}
	if h.messageFanout != nil {
		h.messageFanout.EmitCommittedHumanMessage(r.Context(), r.WorkspaceID(), appMessage, delivery)
	}
	return api.OK(converter.ToAppMessage(appMessage)), nil
}

// GetShareCard handles GET /api/thread-share-cards/:messageId.
func (h *ThreadHandler) GetShareCard(r api.WorkspaceScopedRequest) (api.Response, error) {
	messageID := strings.TrimSpace(r.Gin.Param("messageId"))
	if messageID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "messageId is required")
	}
	card, err := h.appStore.LoadThreadShareCardForUser(r.Context(), r.User.User.ID, r.WorkspaceID(), messageID)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToThreadShareCardView(card)), nil
}

func (h *ThreadHandler) ListV1(r api.WorkspaceScopedRequest) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	subsystem := strings.TrimSpace(r.Gin.Query("subsystem"))
	if subsystem != "" {
		return h.listForSubsystem(r, subsystem)
	}

	limit, err := parseOptionalLimit(r.Gin.Query("limit"), true)
	if err != nil {
		return api.Response{}, err
	}
	cursor, err := converter.FromThreadInboxCursorToken(r.Gin.Query("cursor"))
	if err != nil {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, err.Error())
	}

	page, err := h.store.ListSubscribedThreadInboxPageForUser(
		r.Context(),
		authenticated.User.ID,
		workspaceID,
		limit,
		cursor,
	)
	if err != nil {
		return api.Response{}, err
	}

	return api.OK(converter.ToThreadInboxPage(page)), nil
}

func (h *ThreadHandler) listForSubsystem(r api.WorkspaceScopedRequest, rawSubsystem string) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()
	subsystem := message.NormalizeChannelSubsystem(rawSubsystem)
	if subsystem != message.ChannelSubsystemKB {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "unsupported thread subsystem")
	}
	if h.subsystemChannels == nil {
		return api.Response{}, api.NewHTTPError(http.StatusNotImplemented, "thread subsystem channels are not configured")
	}
	limit, err := parseOptionalLimit(r.Gin.Query("limit"), true)
	if err != nil {
		return api.Response{}, err
	}
	channel, err := h.subsystemChannels.EnsureAgentThreadSessionChannelForUser(r.Context(), authenticated.User.ID, workspaceID)
	if err != nil {
		return api.Response{}, err
	}
	channelID := strings.TrimSpace(channel.ChannelID)
	if channelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "thread subsystem channel is unavailable")
	}
	summaries, err := h.store.ListReplyThreadSummariesForUserWithLimit(r.Context(), authenticated.User.ID, workspaceID, channelID, limit)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToReplyThreadSummaryMap(summaries)), nil
}

func (h *ThreadHandler) ListLegacy(r api.WorkspaceScopedRequest) (api.Response, error) {
	return h.list(r, false)
}

func (h *ThreadHandler) list(r api.WorkspaceScopedRequest, requireLimit bool) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	limit, err := parseOptionalLimit(r.Gin.Query("limit"), requireLimit)
	if err != nil {
		return api.Response{}, err
	}

	threads, err := h.store.ListSubscribedThreadsForUserWithLimit(r.Context(), authenticated.User.ID, workspaceID, limit)
	if err != nil {
		return api.Response{}, err
	}

	return api.OK(converter.ToSubscribedThreads(threads)), nil
}

func (h *ThreadHandler) Count(r api.WorkspaceScopedRequest) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	count, err := h.store.CountSubscribedThreadsForUser(
		r.Context(),
		authenticated.User.ID,
		workspaceID,
	)
	if err != nil {
		return api.Response{}, err
	}

	return api.OK(converter.ToThreadInboxCount(count)), nil
}

func (h *ThreadHandler) Membership(r api.WorkspaceScopedRequest) (api.Response, error) {
	authenticated, workspaceID, threadChannelID, err := h.threadActionContext(r)
	if err != nil {
		return api.Response{}, err
	}

	membership, err := h.store.GetThreadMembershipForUser(
		r.Context(),
		authenticated.User.ID,
		workspaceID,
		threadChannelID,
	)
	if err != nil {
		return api.Response{}, err
	}

	return api.OK(converter.ToThreadMembership(membership)), nil
}

func (h *ThreadHandler) ListForChannelV1(r api.WorkspaceScopedRequest) (api.Response, error) {
	return h.listForChannel(r, true)
}

func (h *ThreadHandler) ListForChannelLegacy(r api.WorkspaceScopedRequest) (api.Response, error) {
	return h.listForChannel(r, false)
}

func (h *ThreadHandler) listForChannel(r api.WorkspaceScopedRequest, requireLimit bool) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	if channelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "channelId is required")
	}

	limit, err := parseOptionalLimit(r.Gin.Query("limit"), requireLimit)
	if err != nil {
		return api.Response{}, err
	}

	summaries, err := h.store.ListReplyThreadSummariesForUserWithLimit(r.Context(), authenticated.User.ID, workspaceID, channelID, limit)
	if err != nil {
		return api.Response{}, err
	}

	return api.OK(converter.ToReplyThreadSummaryMap(summaries)), nil
}

func parseOptionalLimit(raw string, required bool) (int, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		if required {
			return 0, api.NewHTTPError(http.StatusBadRequest, "limit is required")
		}
		return defaultThreadListLimit, 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 > maxThreadListLimit {
		return maxThreadListLimit, nil
	}
	return limit, nil
}
