package userapi

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

	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/client"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"
)

type cpUserProfileData struct {
	UserID        string `json:"user_id"`
	DisplayUserID string `json:"display_user_id"`
	Username      string `json:"username"`
	Avatar        string `json:"avatar"`
}

type cpAvatarFrameData struct {
	ResourceID   int64  `json:"resource_id"`
	ResourceCode string `json:"resource_code"`
	Name         string `json:"name"`
	AssetURL     string `json:"asset_url"`
	PreviewURL   string `json:"preview_url"`
	AnimationURL string `json:"animation_url"`
	MetadataJSON string `json:"metadata_json"`
}

type cpIntimacyLeaderboardUserData struct {
	UserID        string             `json:"user_id"`
	DisplayUserID string             `json:"display_user_id"`
	Username      string             `json:"username"`
	Avatar        string             `json:"avatar"`
	AvatarFrame   *cpAvatarFrameData `json:"avatar_frame,omitempty"`
}

type cpIntimacyLeaderboardItemData struct {
	Rank           int64                          `json:"rank"`
	RelationshipID string                         `json:"relationship_id"`
	RelationType   string                         `json:"relation_type"`
	IntimacyValue  int64                          `json:"intimacy_value"`
	Level          int32                          `json:"level"`
	UserA          *cpIntimacyLeaderboardUserData `json:"user_a,omitempty"`
	UserB          *cpIntimacyLeaderboardUserData `json:"user_b,omitempty"`
	FormedAtMS     int64                          `json:"formed_at_ms"`
	UpdatedAtMS    int64                          `json:"updated_at_ms"`
}

type cpFormationGiftFeedItemData struct {
	FormationID   string             `json:"formation_id"`
	GiftCoinValue int64              `json:"gift_coin_value"`
	Requester     *cpUserProfileData `json:"requester,omitempty"`
	Target        *cpUserProfileData `json:"target,omitempty"`
	FormedAtMS    int64              `json:"formed_at_ms"`
}

type cpGiftSnapshotData struct {
	GiftID           string `json:"gift_id"`
	GiftName         string `json:"gift_name"`
	GiftIconURL      string `json:"gift_icon_url"`
	GiftAnimationURL string `json:"gift_animation_url"`
	GiftCount        int32  `json:"gift_count"`
	GiftValue        int64  `json:"gift_value"`
	CoinSpent        int64  `json:"coin_spent"`
	BillingReceiptID string `json:"billing_receipt_id"`
}

type cpApplicationData struct {
	ApplicationID string              `json:"application_id"`
	RelationType  string              `json:"relation_type"`
	Status        string              `json:"status"`
	Requester     *cpUserProfileData  `json:"requester,omitempty"`
	Target        *cpUserProfileData  `json:"target,omitempty"`
	RoomID        string              `json:"room_id"`
	RoomRegionID  int64               `json:"room_region_id"`
	Gift          *cpGiftSnapshotData `json:"gift,omitempty"`
	CreatedAtMS   int64               `json:"created_at_ms"`
	UpdatedAtMS   int64               `json:"updated_at_ms"`
	ExpiresAtMS   int64               `json:"expires_at_ms"`
	DecidedAtMS   int64               `json:"decided_at_ms"`
}

type cpRelationshipData struct {
	RelationshipID        string             `json:"relationship_id"`
	RelationType          string             `json:"relation_type"`
	Status                string             `json:"status"`
	Me                    *cpUserProfileData `json:"me,omitempty"`
	Partner               *cpUserProfileData `json:"partner,omitempty"`
	IntimacyValue         int64              `json:"intimacy_value"`
	Level                 int32              `json:"level"`
	CurrentLevelThreshold int64              `json:"current_level_threshold"`
	NextLevelThreshold    int64              `json:"next_level_threshold"`
	NeededForNextLevel    int64              `json:"needed_for_next_level"`
	LevelProgressPercent  int32              `json:"level_progress_percent"`
	MaxLevel              bool               `json:"max_level"`
	BreakupCostCoins      int64              `json:"breakup_cost_coins"`
	FormedAtMS            int64              `json:"formed_at_ms"`
	EndedAtMS             int64              `json:"ended_at_ms"`
	UpdatedAtMS           int64              `json:"updated_at_ms"`
}

type cpRejectRequest struct {
	Reason string `json:"reason"`
}

type cpBreakRelationshipRequest struct {
	CommandID string `json:"command_id"`
}

// listCPApplications 返回当前用户收到或发出的 CP/兄弟/姐妹申请。
func (h *Handler) listCPApplications(writer http.ResponseWriter, request *http.Request) {
	if h.userCPClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	page, pageSize, ok := socialPage(request)
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	resp, err := h.userCPClient.ListCPApplications(request.Context(), &userv1.ListCPApplicationsRequest{
		Meta:      httpkit.UserMeta(request, ""),
		UserId:    auth.UserIDFromContext(request.Context()),
		Direction: strings.TrimSpace(request.URL.Query().Get("direction")),
		Status:    strings.TrimSpace(request.URL.Query().Get("status")),
		Page:      page,
		PageSize:  pageSize,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	applications := make([]cpApplicationData, 0, len(resp.GetApplications()))
	for _, item := range resp.GetApplications() {
		applications = append(applications, cpApplicationFromProto(item))
	}
	httpkit.WriteOK(writer, request, map[string]any{
		"applications":   applications,
		"total":          resp.GetTotal(),
		"server_time_ms": resp.GetServerTimeMs(),
	})
}

// acceptCPApplication 同意一条申请；同意后同一对用户其他 pending 类型由 user-service 阻断。
func (h *Handler) acceptCPApplication(writer http.ResponseWriter, request *http.Request) {
	if h.userCPClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	applicationID := strings.TrimSpace(request.PathValue("application_id"))
	if applicationID == "" {
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
		return
	}
	resp, err := h.userCPClient.AcceptCPApplication(request.Context(), &userv1.AcceptCPApplicationRequest{
		Meta:          httpkit.UserMeta(request, ""),
		UserId:        auth.UserIDFromContext(request.Context()),
		ApplicationId: applicationID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, map[string]any{
		"application":  cpApplicationFromProto(resp.GetApplication()),
		"relationship": cpRelationshipFromProto(resp.GetRelationship()),
	})
}

// rejectCPApplication 拒绝一条申请；拒绝不会删除历史申请，客户端仍可看到 decided 状态。
func (h *Handler) rejectCPApplication(writer http.ResponseWriter, request *http.Request) {
	if h.userCPClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	applicationID := strings.TrimSpace(request.PathValue("application_id"))
	if applicationID == "" {
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
		return
	}
	var body cpRejectRequest
	if request.Body != nil && request.ContentLength != 0 && !httpkit.Decode(writer, request, &body) {
		return
	}
	resp, err := h.userCPClient.RejectCPApplication(request.Context(), &userv1.RejectCPApplicationRequest{
		Meta:          httpkit.UserMeta(request, ""),
		UserId:        auth.UserIDFromContext(request.Context()),
		ApplicationId: applicationID,
		Reason:        body.Reason,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, map[string]any{"application": cpApplicationFromProto(resp.GetApplication())})
}

// listCPRelationships 默认返回当前用户关系；资料卡传 user_id 时返回被查看用户关系。
func (h *Handler) listCPRelationships(writer http.ResponseWriter, request *http.Request) {
	if h.userCPClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	page, pageSize, ok := socialPage(request)
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	targetUserID := cpRelationshipListUserID(request)
	resp, err := h.userCPClient.ListCPRelationships(request.Context(), &userv1.ListCPRelationshipsRequest{
		Meta:         httpkit.UserMeta(request, ""),
		UserId:       targetUserID,
		RelationType: strings.TrimSpace(request.URL.Query().Get("relation_type")),
		Page:         page,
		PageSize:     pageSize,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	relationships := make([]cpRelationshipData, 0, len(resp.GetRelationships()))
	for _, item := range resp.GetRelationships() {
		relationships = append(relationships, cpRelationshipFromProto(item))
	}
	httpkit.WriteOK(writer, request, map[string]any{
		"relationships": relationships,
		"total":         resp.GetTotal(),
	})
}

func cpRelationshipListUserID(request *http.Request) int64 {
	currentUserID := auth.UserIDFromContext(request.Context())
	raw := strings.TrimSpace(request.URL.Query().Get("user_id"))
	if raw == "" {
		return currentUserID
	}
	// Flutter 已经会把被查看用户放进 user_id；非法值按历史语义回落到登录用户，避免破坏旧客户端的“我的关系”入口。
	targetUserID, err := strconv.ParseInt(raw, 10, 64)
	if err != nil || targetUserID <= 0 {
		return currentUserID
	}
	return targetUserID
}

// listCPIntimacyLeaderboard 返回 cron 聚合后的 CP 亲密值排行榜，不在 gateway 实时扫描关系表。
func (h *Handler) listCPIntimacyLeaderboard(writer http.ResponseWriter, request *http.Request) {
	if h.userCPClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	// 榜单接口沿用社交列表分页参数，非法 page/page_size 在 gateway 直接拒绝，避免把异常分页传给内部服务。
	page, pageSize, ok := socialPage(request)
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	// gateway 只透传 relation_type 筛选和请求 meta；榜单数据来自 user-service 的 Redis 读模型，不在 HTTP 层拼装。
	resp, err := h.userCPClient.ListCPIntimacyLeaderboard(request.Context(), &userv1.ListCPIntimacyLeaderboardRequest{
		Meta:         httpkit.UserMeta(request, ""),
		RelationType: strings.TrimSpace(request.URL.Query().Get("relation_type")),
		Page:         page,
		PageSize:     pageSize,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	items := make([]cpIntimacyLeaderboardItemData, 0, len(resp.GetItems()))
	for _, item := range resp.GetItems() {
		// 双方资料、头像框和 rank 都已经在内部 RPC 返回；这里仅转换为 App JSON 字段名。
		items = append(items, cpIntimacyLeaderboardItemFromProto(item))
	}
	httpkit.WriteOK(writer, request, map[string]any{
		"items":          items,
		"total":          resp.GetTotal(),
		"page":           resp.GetPage(),
		"page_size":      resp.GetPageSize(),
		"server_time_ms": resp.GetServerTimeMs(),
	})
}

// listCPFormationGiftFeed 返回已接受的 CP 成立礼物事件流，金额口径固定为 wallet 结算出的礼物金币价值。
func (h *Handler) listCPFormationGiftFeed(writer http.ResponseWriter, request *http.Request) {
	feedClient, ok := h.userCPClient.(client.UserCPFormationGiftFeedClient)
	if h.userCPClient == nil || !ok {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	limit, ok := cpFormationGiftFeedLimit(request)
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	// HTTP 只允许客户端缩小/放大返回条数到 100；严格 >5000、accepted 和 CP 类型都不能由 query 覆盖。
	resp, err := feedClient.ListCPFormationGiftFeed(request.Context(), &userv1.ListCPFormationGiftFeedRequest{
		Meta:  httpkit.UserMeta(request, ""),
		Limit: limit,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	items := make([]cpFormationGiftFeedItemData, 0, len(resp.GetItems()))
	for _, item := range resp.GetItems() {
		if item == nil {
			continue
		}
		items = append(items, cpFormationGiftFeedItemData{
			FormationID:   item.GetFormationId(),
			GiftCoinValue: item.GetGiftCoinValue(),
			Requester:     cpUserProfileFromProto(item.GetRequester()),
			Target:        cpUserProfileFromProto(item.GetTarget()),
			FormedAtMS:    item.GetFormedAtMs(),
		})
	}
	httpkit.WriteOK(writer, request, map[string]any{"items": items})
}

func cpFormationGiftFeedLimit(request *http.Request) (int32, bool) {
	raw := strings.TrimSpace(request.URL.Query().Get("limit"))
	if raw == "" {
		// 0 由 user-service 归一成默认 20，保证 HTTP 和内部 RPC 共用一套上限语义。
		return 0, true
	}
	parsed, err := strconv.ParseInt(raw, 10, 32)
	if err != nil || parsed <= 0 {
		return 0, false
	}
	if parsed > 100 {
		parsed = 100
	}
	return int32(parsed), true
}

// breakCPRelationship 先在 user-service 创建解除占位，再扣钱包，最后确认关系 ended，避免并发请求重复扣费。
func (h *Handler) breakCPRelationship(writer http.ResponseWriter, request *http.Request) {
	if h.userCPClient == nil || h.walletClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	relationshipID := strings.TrimSpace(request.PathValue("relationship_id"))
	if relationshipID == "" {
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
		return
	}
	var body cpBreakRelationshipRequest
	if !httpkit.Decode(writer, request, &body) {
		return
	}
	commandID := strings.TrimSpace(body.CommandID)
	if commandID == "" {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "command_id is required")
		return
	}
	userID := auth.UserIDFromContext(request.Context())
	meta := httpkit.UserMeta(request, "")
	prepare, err := h.userCPClient.PrepareBreakCPRelationship(request.Context(), &userv1.PrepareBreakCPRelationshipRequest{
		Meta:           meta,
		UserId:         userID,
		RelationshipId: relationshipID,
		CommandId:      commandID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	if prepare.GetStatus() == "confirmed" {
		httpkit.WriteOK(writer, request, map[string]any{
			"relationship":          cpRelationshipFromProto(prepare.GetRelationship()),
			"breakup_cost_coins":    prepare.GetBreakupCostCoins(),
			"wallet_transaction_id": prepare.GetWalletTransactionId(),
			"coin_balance_after":    prepare.GetCoinBalanceAfter(),
		})
		return
	}

	walletTransactionID := ""
	coinBalanceAfter := int64(0)
	cost := prepare.GetBreakupCostCoins()
	if cost > 0 {
		walletResp, walletErr := h.walletClient.DebitCPBreakupFee(request.Context(), &walletv1.DebitCPBreakupFeeRequest{
			CommandId:      commandID,
			AppCode:        meta.GetAppCode(),
			UserId:         userID,
			RelationshipId: relationshipID,
			RelationType:   prepare.GetRelationship().GetRelationType(),
			Amount:         cost,
		})
		if walletErr != nil {
			// 钱包失败表示关系不应解除；取消 pending 占位后把真实钱包错误返回给客户端。
			_, _ = h.userCPClient.CancelBreakCPRelationship(request.Context(), &userv1.CancelBreakCPRelationshipRequest{
				Meta:           meta,
				UserId:         userID,
				RelationshipId: relationshipID,
				CommandId:      commandID,
				Reason:         "wallet_debit_failed",
			})
			httpkit.WriteRPCError(writer, request, walletErr)
			return
		}
		walletTransactionID = walletResp.GetTransactionId()
		coinBalanceAfter = walletResp.GetCoinBalanceAfter()
	}
	confirmed, err := h.userCPClient.ConfirmBreakCPRelationship(request.Context(), &userv1.ConfirmBreakCPRelationshipRequest{
		Meta:                meta,
		UserId:              userID,
		RelationshipId:      relationshipID,
		CommandId:           commandID,
		WalletTransactionId: walletTransactionID,
		PaidCoinAmount:      cost,
		CoinBalanceAfter:    coinBalanceAfter,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, map[string]any{
		"relationship":          cpRelationshipFromProto(confirmed.GetRelationship()),
		"breakup_cost_coins":    confirmed.GetBreakupCostCoins(),
		"wallet_transaction_id": confirmed.GetWalletTransactionId(),
		"coin_balance_after":    confirmed.GetCoinBalanceAfter(),
	})
}

func cpApplicationFromProto(item *userv1.CPApplication) cpApplicationData {
	if item == nil {
		return cpApplicationData{}
	}
	return cpApplicationData{
		ApplicationID: item.GetApplicationId(),
		RelationType:  item.GetRelationType(),
		Status:        item.GetStatus(),
		Requester:     cpUserProfileFromProto(item.GetRequester()),
		Target:        cpUserProfileFromProto(item.GetTarget()),
		RoomID:        item.GetRoomId(),
		RoomRegionID:  item.GetRoomRegionId(),
		Gift:          cpGiftSnapshotFromProto(item.GetGift()),
		CreatedAtMS:   item.GetCreatedAtMs(),
		UpdatedAtMS:   item.GetUpdatedAtMs(),
		ExpiresAtMS:   item.GetExpiresAtMs(),
		DecidedAtMS:   item.GetDecidedAtMs(),
	}
}

func cpRelationshipFromProto(item *userv1.CPRelationship) cpRelationshipData {
	if item == nil {
		return cpRelationshipData{}
	}
	return cpRelationshipData{
		RelationshipID:        item.GetRelationshipId(),
		RelationType:          item.GetRelationType(),
		Status:                item.GetStatus(),
		Me:                    cpUserProfileFromProto(item.GetMe()),
		Partner:               cpUserProfileFromProto(item.GetPartner()),
		IntimacyValue:         item.GetIntimacyValue(),
		Level:                 item.GetLevel(),
		CurrentLevelThreshold: item.GetCurrentLevelThreshold(),
		NextLevelThreshold:    item.GetNextLevelThreshold(),
		NeededForNextLevel:    item.GetNeededForNextLevel(),
		LevelProgressPercent:  item.GetLevelProgressPercent(),
		MaxLevel:              item.GetMaxLevel(),
		BreakupCostCoins:      item.GetBreakupCostCoins(),
		FormedAtMS:            item.GetFormedAtMs(),
		EndedAtMS:             item.GetEndedAtMs(),
		UpdatedAtMS:           item.GetUpdatedAtMs(),
	}
}

func cpUserProfileFromProto(item *userv1.CPUserProfile) *cpUserProfileData {
	if item == nil || item.GetUserId() <= 0 {
		return nil
	}
	return &cpUserProfileData{
		UserID:        userIDString(item.GetUserId()),
		DisplayUserID: item.GetDisplayUserId(),
		Username:      item.GetUsername(),
		Avatar:        item.GetAvatar(),
	}
}

func cpIntimacyLeaderboardItemFromProto(item *userv1.CPIntimacyLeaderboardItem) cpIntimacyLeaderboardItemData {
	if item == nil {
		return cpIntimacyLeaderboardItemData{}
	}
	// user_a/user_b 是关系无序 pair 的固定两端，不按当前登录用户换位；排行榜展示需要稳定的双方快照。
	return cpIntimacyLeaderboardItemData{
		Rank:           item.GetRank(),
		RelationshipID: item.GetRelationshipId(),
		RelationType:   item.GetRelationType(),
		IntimacyValue:  item.GetIntimacyValue(),
		Level:          item.GetLevel(),
		UserA:          cpIntimacyLeaderboardUserFromProto(item.GetUserA()),
		UserB:          cpIntimacyLeaderboardUserFromProto(item.GetUserB()),
		FormedAtMS:     item.GetFormedAtMs(),
		UpdatedAtMS:    item.GetUpdatedAtMs(),
	}
}

func cpIntimacyLeaderboardUserFromProto(item *userv1.CPIntimacyLeaderboardUser) *cpIntimacyLeaderboardUserData {
	if item == nil || item.GetUserId() <= 0 {
		// 内部服务如果返回空用户，App JSON 直接省略该侧，避免输出 user_id=0 的伪用户。
		return nil
	}
	return &cpIntimacyLeaderboardUserData{
		UserID:        userIDString(item.GetUserId()),
		DisplayUserID: item.GetDisplayUserId(),
		Username:      item.GetUsername(),
		Avatar:        item.GetAvatar(),
		AvatarFrame:   cpAvatarFrameFromProto(item.GetAvatarFrame()),
	}
}

func cpAvatarFrameFromProto(item *userv1.CPAvatarFrameSnapshot) *cpAvatarFrameData {
	if item == nil || item.GetResourceId() <= 0 {
		// 未佩戴头像框时保持 nil，omitempty 会让 App 端按无头像框处理。
		return nil
	}
	return &cpAvatarFrameData{
		ResourceID:   item.GetResourceId(),
		ResourceCode: item.GetResourceCode(),
		Name:         item.GetName(),
		AssetURL:     item.GetAssetUrl(),
		PreviewURL:   item.GetPreviewUrl(),
		AnimationURL: item.GetAnimationUrl(),
		MetadataJSON: item.GetMetadataJson(),
	}
}

func cpGiftSnapshotFromProto(item *userv1.CPGiftSnapshot) *cpGiftSnapshotData {
	if item == nil {
		return nil
	}
	return &cpGiftSnapshotData{
		GiftID:           item.GetGiftId(),
		GiftName:         item.GetGiftName(),
		GiftIconURL:      item.GetGiftIconUrl(),
		GiftAnimationURL: item.GetGiftAnimationUrl(),
		GiftCount:        item.GetGiftCount(),
		GiftValue:        item.GetGiftValue(),
		CoinSpent:        item.GetCoinSpent(),
		BillingReceiptID: item.GetBillingReceiptId(),
	}
}
