package messageapi

import (
	"encoding/json"
	"net/http"
	"strconv"
	"strings"
	"sync"

	activityv1 "hyapp.local/api/proto/activity/v1"
	userv1 "hyapp.local/api/proto/user/v1"
	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/services/gateway-service/internal/auth"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"
)

type messageTabSectionData struct {
	Section     string `json:"section"`
	Title       string `json:"title"`
	UnreadCount int64  `json:"unread_count"`
	Source      string `json:"source"`
}

type inboxMessageData struct {
	MessageID   string                `json:"message_id"`
	Title       string                `json:"title"`
	Summary     string                `json:"summary"`
	Body        string                `json:"body"`
	IconURL     string                `json:"icon_url"`
	ImageURL    string                `json:"image_url"`
	ImageURLs   []string              `json:"image_urls"`
	RewardItems []inboxRewardItemData `json:"reward_items"`
	Actions     []inboxActionData     `json:"actions"`
	ActionType  string                `json:"action_type"`
	ActionParam string                `json:"action_param"`
	Read        bool                  `json:"read"`
	SentAtMS    int64                 `json:"sent_at_ms"`
}

type inboxActionData struct {
	Action string             `json:"action"`
	Label  string             `json:"label"`
	Style  string             `json:"style"`
	API    inboxActionAPIData `json:"api"`
}

type inboxActionAPIData struct {
	Method string         `json:"method"`
	Path   string         `json:"path"`
	Query  map[string]any `json:"query,omitempty"`
	Body   any            `json:"body,omitempty"`
}

type inboxRewardItemData struct {
	RewardType   string `json:"reward_type"`
	Name         string `json:"name"`
	ImageURL     string `json:"image_url"`
	Quantity     int64  `json:"quantity"`
	DurationDays int64  `json:"duration_days"`
}

type actionConfirmData struct {
	ConfirmID       string `json:"confirm_id"`
	Status          string `json:"status"`
	Action          string `json:"action,omitempty"`
	BusinessType    string `json:"business_type,omitempty"`
	BusinessSubtype string `json:"business_subtype,omitempty"`
	BusinessID      string `json:"business_id,omitempty"`
	SenderUserID    string `json:"sender_user_id,omitempty"`
	ReceiverUserID  string `json:"receiver_user_id,omitempty"`
	ProcessedAtMS   int64  `json:"processed_at_ms,omitempty"`
	CreatedAtMS     int64  `json:"created_at_ms,omitempty"`
	UpdatedAtMS     int64  `json:"updated_at_ms,omitempty"`
}

type userProfileBatchData struct {
	UserID        string `json:"user_id"`
	DisplayUserID string `json:"display_user_id"`
	Username      string `json:"username"`
	Avatar        string `json:"avatar,omitempty"`
	Gender        string `json:"gender,omitempty"`
	Country       string `json:"country,omitempty"`
	RegionID      int64  `json:"region_id,omitempty"`
	AppCode       string `json:"app_code"`
	VIPLevel      int32  `json:"vip_level"`
}

// listMessageTabs returns backend unread summary and declares user conversations as Tencent IM SDK owned.
func (h *Handler) listMessageTabs(writer http.ResponseWriter, request *http.Request) {
	if h.messageClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	resp, err := h.messageClient.ListMessageTabs(request.Context(), &activityv1.ListMessageTabsRequest{
		Meta:   httpkit.ActivityMeta(request),
		UserId: auth.UserIDFromContext(request.Context()),
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	sections := make([]messageTabSectionData, 0, len(resp.GetSections()))
	for _, section := range resp.GetSections() {
		sections = append(sections, messageTabSectionData{
			Section:     section.GetSection(),
			Title:       section.GetTitle(),
			UnreadCount: section.GetUnreadCount(),
			Source:      section.GetSource(),
		})
	}
	httpkit.WriteOK(writer, request, map[string]any{"sections": sections})
}

// listInboxMessages reads system/activity inbox list; private user conversations stay in Tencent IM SDK.
func (h *Handler) listInboxMessages(writer http.ResponseWriter, request *http.Request) {
	if h.messageClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	pageSize, ok := parseMessagePageSize(request.URL.Query().Get("page_size"))
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	section := strings.TrimSpace(request.URL.Query().Get("section"))
	resp, err := h.messageClient.ListInboxMessages(request.Context(), &activityv1.ListInboxMessagesRequest{
		Meta:      httpkit.ActivityMeta(request),
		UserId:    auth.UserIDFromContext(request.Context()),
		Section:   section,
		PageSize:  pageSize,
		PageToken: strings.TrimSpace(request.URL.Query().Get("page_token")),
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	items := make([]inboxMessageData, 0, len(resp.GetItems()))
	for _, item := range resp.GetItems() {
		rewardItems := make([]inboxRewardItemData, 0, len(item.GetRewardItems()))
		for _, reward := range item.GetRewardItems() {
			rewardItems = append(rewardItems, inboxRewardItemData{
				RewardType:   reward.GetRewardType(),
				Name:         reward.GetName(),
				ImageURL:     reward.GetImageUrl(),
				Quantity:     reward.GetQuantity(),
				DurationDays: reward.GetDurationDays(),
			})
		}
		actions := make([]inboxActionData, 0, len(item.GetActions()))
		for _, action := range item.GetActions() {
			if action == nil || action.GetApi() == nil {
				continue
			}
			actions = append(actions, inboxActionData{
				Action: action.GetAction(),
				Label:  action.GetLabel(),
				Style:  action.GetStyle(),
				API: inboxActionAPIData{
					Method: action.GetApi().GetMethod(),
					Path:   action.GetApi().GetPath(),
					Query:  inboxActionJSONMap(action.GetApi().GetQueryJson()),
					Body:   inboxActionJSONValue(action.GetApi().GetBodyJson()),
				},
			})
		}
		items = append(items, inboxMessageData{
			MessageID:   item.GetMessageId(),
			Title:       item.GetTitle(),
			Summary:     item.GetSummary(),
			Body:        item.GetBody(),
			IconURL:     item.GetIconUrl(),
			ImageURL:    item.GetImageUrl(),
			ImageURLs:   item.GetImageUrls(),
			RewardItems: rewardItems,
			Actions:     actions,
			ActionType:  item.GetActionType(),
			ActionParam: item.GetActionParam(),
			Read:        item.GetRead(),
			SentAtMS:    item.GetSentAtMs(),
		})
	}
	httpkit.WriteOK(writer, request, map[string]any{"section": resp.GetSection(), "items": items, "next_page_token": resp.GetNextPageToken()})
}

func inboxActionJSONMap(raw string) map[string]any {
	result := map[string]any{}
	if strings.TrimSpace(raw) == "" {
		return result
	}
	if err := json.Unmarshal([]byte(raw), &result); err != nil {
		return map[string]any{}
	}
	return result
}

func inboxActionJSONValue(raw string) any {
	if strings.TrimSpace(raw) == "" {
		return nil
	}
	var result any
	if err := json.Unmarshal([]byte(raw), &result); err != nil {
		return nil
	}
	return result
}

// markInboxSectionRead marks all current visible unread messages in one backend-owned section.
func (h *Handler) markInboxSectionRead(writer http.ResponseWriter, request *http.Request) {
	if h.messageClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	var body struct {
		Section string `json:"section"`
	}
	if !httpkit.Decode(writer, request, &body) {
		return
	}
	resp, err := h.messageClient.MarkInboxSectionRead(request.Context(), &activityv1.MarkInboxSectionReadRequest{
		Meta:    httpkit.ActivityMeta(request),
		UserId:  auth.UserIDFromContext(request.Context()),
		Section: body.Section,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, map[string]any{"section": resp.GetSection(), "read_count": resp.GetReadCount()})
}

// markInboxMessageReadByPath handles /api/v1/messages/{message_id}/read.
func (h *Handler) markInboxMessageReadByPath(writer http.ResponseWriter, request *http.Request) {
	if h.messageClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	messageID := strings.TrimSpace(request.PathValue("message_id"))
	if messageID == "" {
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
		return
	}
	h.markInboxMessageRead(writer, request, messageID)
}

// deleteInboxMessageByPath handles /api/v1/messages/{message_id}.
func (h *Handler) deleteInboxMessageByPath(writer http.ResponseWriter, request *http.Request) {
	if h.messageClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	messageID := strings.TrimSpace(request.PathValue("message_id"))
	if messageID == "" {
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
		return
	}
	if request.Method == http.MethodDelete {
		h.deleteInboxMessage(writer, request, messageID)
		return
	}
	httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
}

func (h *Handler) markInboxMessageRead(writer http.ResponseWriter, request *http.Request, messageID string) {
	if request.Method != http.MethodPost {
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
		return
	}
	resp, err := h.messageClient.MarkInboxMessageRead(request.Context(), &activityv1.MarkInboxMessageReadRequest{
		Meta:      httpkit.ActivityMeta(request),
		UserId:    auth.UserIDFromContext(request.Context()),
		MessageId: messageID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, map[string]any{"message_id": resp.GetMessageId(), "read": resp.GetRead(), "read_at_ms": resp.GetReadAtMs()})
}

func (h *Handler) deleteInboxMessage(writer http.ResponseWriter, request *http.Request, messageID string) {
	resp, err := h.messageClient.DeleteInboxMessage(request.Context(), &activityv1.DeleteInboxMessageRequest{
		Meta:      httpkit.ActivityMeta(request),
		UserId:    auth.UserIDFromContext(request.Context()),
		MessageId: messageID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, map[string]any{"message_id": resp.GetMessageId(), "deleted": resp.GetDeleted()})
}

// acceptActionConfirm handles an IM confirm accept click; only confirm_id and command_id come from App.
func (h *Handler) acceptActionConfirm(writer http.ResponseWriter, request *http.Request) {
	if h.actionClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	confirmID := strings.TrimSpace(request.PathValue("confirm_id"))
	var body struct {
		CommandID string `json:"command_id"`
	}
	if confirmID == "" {
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
		return
	}
	if !httpkit.Decode(writer, request, &body) {
		return
	}
	resp, err := h.actionClient.AcceptActionConfirm(request.Context(), &activityv1.AcceptActionConfirmRequest{
		Meta:        httpkit.ActivityMeta(request),
		ConfirmId:   confirmID,
		ActorUserId: auth.UserIDFromContext(request.Context()),
		CommandId:   body.CommandID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, actionConfirmResponseData(resp.GetConfirm()))
}

// rejectActionConfirm handles an IM confirm reject click with an optional reason.
func (h *Handler) rejectActionConfirm(writer http.ResponseWriter, request *http.Request) {
	if h.actionClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	confirmID := strings.TrimSpace(request.PathValue("confirm_id"))
	var body struct {
		CommandID string `json:"command_id"`
		Reason    string `json:"reason"`
	}
	if confirmID == "" {
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
		return
	}
	if !httpkit.Decode(writer, request, &body) {
		return
	}
	resp, err := h.actionClient.RejectActionConfirm(request.Context(), &activityv1.RejectActionConfirmRequest{
		Meta:        httpkit.ActivityMeta(request),
		ConfirmId:   confirmID,
		ActorUserId: auth.UserIDFromContext(request.Context()),
		CommandId:   body.CommandID,
		Reason:      body.Reason,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, actionConfirmResponseData(resp.GetConfirm()))
}

// batchActionConfirmStatus returns confirm status for current user's visible confirm IDs.
func (h *Handler) batchActionConfirmStatus(writer http.ResponseWriter, request *http.Request) {
	if h.actionClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	var body struct {
		ConfirmIDs []string `json:"confirm_ids"`
	}
	if !httpkit.Decode(writer, request, &body) {
		return
	}
	resp, err := h.actionClient.BatchGetActionConfirmStatus(request.Context(), &activityv1.BatchGetActionConfirmStatusRequest{
		Meta:       httpkit.ActivityMeta(request),
		UserId:     auth.UserIDFromContext(request.Context()),
		ConfirmIds: body.ConfirmIDs,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	items := make([]actionConfirmData, 0, len(resp.GetItems()))
	for _, item := range resp.GetItems() {
		items = append(items, actionConfirmResponseData(item))
	}
	httpkit.WriteOK(writer, request, map[string]any{"items": items})
}

// listActionConfirms returns confirm states for a C2C peer conversation.
func (h *Handler) listActionConfirms(writer http.ResponseWriter, request *http.Request) {
	if h.actionClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	peerUserID, err := strconv.ParseInt(strings.TrimSpace(request.URL.Query().Get("peer_user_id")), 10, 64)
	if err != nil || peerUserID <= 0 {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	pageSize, ok := parseMessagePageSize(request.URL.Query().Get("page_size"))
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	resp, err := h.actionClient.ListConversationActionConfirms(request.Context(), &activityv1.ListConversationActionConfirmsRequest{
		Meta:       httpkit.ActivityMeta(request),
		UserId:     auth.UserIDFromContext(request.Context()),
		PeerUserId: peerUserID,
		PageSize:   pageSize,
		PageToken:  strings.TrimSpace(request.URL.Query().Get("page_token")),
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	items := make([]actionConfirmData, 0, len(resp.GetItems()))
	for _, item := range resp.GetItems() {
		items = append(items, actionConfirmResponseData(item))
	}
	httpkit.WriteOK(writer, request, map[string]any{"items": items, "next_page_token": resp.GetNextPageToken()})
}

func actionConfirmResponseData(confirm *activityv1.ActionConfirm) actionConfirmData {
	if confirm == nil {
		return actionConfirmData{}
	}
	return actionConfirmData{
		ConfirmID:       confirm.GetConfirmId(),
		Status:          confirm.GetStatus(),
		Action:          confirm.GetAction(),
		BusinessType:    confirm.GetBusinessType(),
		BusinessSubtype: confirm.GetBusinessSubtype(),
		BusinessID:      confirm.GetBusinessId(),
		SenderUserID:    strconv.FormatInt(confirm.GetSenderUserId(), 10),
		ReceiverUserID:  strconv.FormatInt(confirm.GetReceiverUserId(), 10),
		ProcessedAtMS:   confirm.GetProcessedAtMs(),
		CreatedAtMS:     confirm.GetCreatedAtMs(),
		UpdatedAtMS:     confirm.GetUpdatedAtMs(),
	}
}

// batchUserProfiles returns only safe profile fields needed to render Tencent IM conversation rows.
func (h *Handler) batchUserProfiles(writer http.ResponseWriter, request *http.Request) {
	if h.userProfileClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	userIDs, ok := parseBatchUserIDs(request)
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	var (
		profileResp *userv1.BatchGetUsersResponse
		vipResp     *walletv1.BatchGetUserEffectiveVipLevelsResponse
		profileErr  error
		vipErr      error
		waitGroup   sync.WaitGroup
	)
	waitGroup.Add(1)
	go func() {
		defer waitGroup.Done()
		profileResp, profileErr = h.userProfileClient.BatchGetUsers(request.Context(), &userv1.BatchGetUsersRequest{
			Meta:    httpkit.UserMeta(request, ""),
			UserIds: userIDs,
		})
	}()
	if h.effectiveVIPClient != nil {
		waitGroup.Add(1)
		go func() {
			defer waitGroup.Done()
			meta := httpkit.UserMeta(request, "")
			vipResp, vipErr = h.effectiveVIPClient.BatchGetUserEffectiveVipLevels(request.Context(), &walletv1.BatchGetUserEffectiveVipLevelsRequest{
				RequestId: meta.GetRequestId(),
				AppCode:   meta.GetAppCode(),
				UserIds:   userIDs,
			})
		}()
	}
	waitGroup.Wait()
	if profileErr != nil {
		httpkit.WriteRPCError(writer, request, profileErr)
		return
	}
	if vipErr != nil {
		httpkit.WriteRPCError(writer, request, vipErr)
		return
	}

	// 未装配新 wallet client 只发生在隔离单测或 gateway 滚动升级窗口；字段仍显式返回 0。
	// 生产完整装配会使用上面的 wallet 批量 owner 查询，绝不从徽章、资源或昵称反推等级。
	vipLevels := make(map[int64]int32, len(userIDs))
	for _, item := range vipResp.GetUsers() {
		if item != nil && item.GetUserId() > 0 && item.GetLevel() > 0 {
			vipLevels[item.GetUserId()] = item.GetLevel()
		}
	}
	items := make([]userProfileBatchData, 0, len(profileResp.GetUsers()))
	for _, userID := range userIDs {
		if user := profileResp.GetUsers()[userID]; user != nil {
			items = append(items, userProfileBatchData{
				UserID:        strconv.FormatInt(user.GetUserId(), 10),
				DisplayUserID: user.GetDisplayUserId(),
				Username:      user.GetUsername(),
				Avatar:        user.GetAvatar(),
				Gender:        user.GetGender(),
				Country:       user.GetCountry(),
				RegionID:      user.GetRegionId(),
				AppCode:       user.GetAppCode(),
				VIPLevel:      vipLevels[user.GetUserId()],
			})
		}
	}
	httpkit.WriteOK(writer, request, map[string]any{"profiles": items})
}

func parseMessagePageSize(raw string) (int32, bool) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return 0, true
	}
	value, err := strconv.Atoi(raw)
	if err != nil || value < 0 || value > 1000 {
		return 0, false
	}
	return int32(value), true
}

func parseBatchUserIDs(request *http.Request) ([]int64, bool) {
	rawValues := append([]string(nil), request.URL.Query()["user_ids"]...)
	rawValues = append(rawValues, request.URL.Query()["user_id"]...)
	seen := make(map[int64]bool)
	result := make([]int64, 0, len(rawValues))
	for _, raw := range rawValues {
		for piece := range strings.SplitSeq(raw, ",") {
			piece = strings.TrimSpace(piece)
			if piece == "" {
				continue
			}
			userID, err := strconv.ParseInt(piece, 10, 64)
			if err != nil || userID <= 0 {
				return nil, false
			}
			if !seen[userID] {
				seen[userID] = true
				result = append(result, userID)
			}
		}
	}
	if len(result) == 0 || len(result) > 100 {
		return nil, false
	}
	return result, true
}
