package walletapi

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

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	userv1 "hyapp.local/api/proto/user/v1"
	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/tencentim"
	"hyapp/pkg/xerr"
	"hyapp/services/gateway-service/internal/auth"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"
)

const (
	directGiftIMDesc        = "gift"
	directGiftIMExt         = "direct_message_gift"
	directGiftIMPreviewText = "[Gift]"
)

type directGiftRequestBody struct {
	CommandID          string `json:"command_id"`
	CommandIDCamel     string `json:"commandId"`
	TargetUserID       int64  `json:"target_user_id"`
	TargetUserIDCamel  int64  `json:"targetUserId"`
	GiftID             string `json:"gift_id"`
	GiftIDCamel        string `json:"giftId"`
	GiftCount          int32  `json:"gift_count"`
	GiftCountCamel     int32  `json:"giftCount"`
	PriceVersion       string `json:"price_version"`
	PriceVersionCamel  string `json:"priceVersion"`
	EntitlementID      string `json:"entitlement_id"`
	EntitlementIDCamel string `json:"entitlementId"`
	Source             string `json:"source"`
}

type directGiftHostScope struct {
	targetIsHost            bool
	targetHostRegionID      int64
	targetAgencyOwnerUserID int64
}

type directGiftSettlementData struct {
	BillingReceiptID       string                 `json:"billing_receipt_id"`
	TransactionID          string                 `json:"transaction_id"`
	EventID                string                 `json:"event_id"`
	CommandID              string                 `json:"command_id"`
	AppCode                string                 `json:"app_code"`
	SenderUserID           int64                  `json:"sender_user_id"`
	TargetUserID           int64                  `json:"target_user_id"`
	GiftID                 string                 `json:"gift_id"`
	GiftName               string                 `json:"gift_name"`
	GiftIconURL            string                 `json:"gift_icon_url"`
	GiftAnimationURL       string                 `json:"gift_animation_url"`
	GiftEffectTypes        []string               `json:"gift_effect_types"`
	GiftCount              int32                  `json:"gift_count"`
	GiftTypeCode           string                 `json:"gift_type_code"`
	CoinSpent              int64                  `json:"coin_spent"`
	ChargeAssetType        string                 `json:"charge_asset_type"`
	ChargeAmount           int64                  `json:"charge_amount"`
	ChargeSource           string                 `json:"charge_source"`
	PriceVersion           string                 `json:"price_version"`
	BalanceAfter           int64                  `json:"balance_after"`
	CoinBalanceAfter       int64                  `json:"coin_balance_after"`
	GiftIncomeCoinAmount   int64                  `json:"gift_income_coin_amount"`
	GiftIncomeBalanceAfter int64                  `json:"gift_income_balance_after"`
	HostPeriodDiamondAdded int64                  `json:"host_period_diamond_added"`
	HostPeriodCycleKey     string                 `json:"host_period_cycle_key"`
	EntitlementID          string                 `json:"entitlement_id,omitempty"`
	Message                *directGiftMessageData `json:"message,omitempty"`
}

type directGiftMessageData struct {
	MessageID         string `json:"message_id"`
	PeerUserID        string `json:"peer_user_id"`
	SenderUserID      string `json:"sender_user_id"`
	Text              string `json:"text"`
	CustomData        string `json:"custom_data"`
	CustomDescription string `json:"custom_description"`
	CustomExtension   string `json:"custom_extension"`
	CloudCustomData   string `json:"cloud_custom_data"`
	SentAtMS          int64  `json:"sent_at_ms"`
	IsMine            bool   `json:"is_mine"`
}

type directGiftIMPayload struct {
	Type      string `json:"type"`
	EventID   string `json:"event_id"`
	CommandID string `json:"command_id"`
	AppCode   string `json:"app_code"`
	// 私聊礼物同样由客户端 JSON 解码，用户 ID 必须是字符串，不能暴露 Go int64 为 JSON number。
	SenderUserID           string   `json:"sender_user_id"`
	TargetUserID           string   `json:"target_user_id"`
	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"`
	GiftPriceCoin          int64    `json:"gift_price_coin"`
	MetadataJSON           string   `json:"metadata_json"`
	EffectTypes            []string `json:"effect_types"`
	BillingReceiptID       string   `json:"billing_receipt_id"`
	TransactionID          string   `json:"transaction_id"`
	ChargeAssetType        string   `json:"charge_asset_type"`
	ChargeAmount           int64    `json:"charge_amount"`
	ChargeSource           string   `json:"source,omitempty"`
	PriceVersion           string   `json:"price_version,omitempty"`
	CoinBalanceAfter       int64    `json:"coin_balance_after"`
	GiftIncomeCoinAmount   int64    `json:"gift_income_coin_amount"`
	GiftIncomeBalanceAfter int64    `json:"gift_income_balance_after"`
	EntitlementID          string   `json:"entitlement_id,omitempty"`
}

func (b directGiftRequestBody) normalizedCommandID() string {
	if commandID := strings.TrimSpace(b.CommandID); commandID != "" {
		return commandID
	}
	return strings.TrimSpace(b.CommandIDCamel)
}

func (b directGiftRequestBody) normalizedTargetUserID() int64 {
	if b.TargetUserID > 0 {
		return b.TargetUserID
	}
	return b.TargetUserIDCamel
}

func (b directGiftRequestBody) normalizedGiftID() string {
	if giftID := strings.TrimSpace(b.GiftID); giftID != "" {
		return giftID
	}
	return strings.TrimSpace(b.GiftIDCamel)
}

func (b directGiftRequestBody) normalizedGiftCount() int32 {
	if b.GiftCount > 0 {
		return b.GiftCount
	}
	return b.GiftCountCamel
}

func (b directGiftRequestBody) normalizedPriceVersion() string {
	if priceVersion := strings.TrimSpace(b.PriceVersion); priceVersion != "" {
		return priceVersion
	}
	return strings.TrimSpace(b.PriceVersionCamel)
}

func (b directGiftRequestBody) normalizedEntitlementID() string {
	if entitlementID := strings.TrimSpace(b.EntitlementID); entitlementID != "" {
		return entitlementID
	}
	return strings.TrimSpace(b.EntitlementIDCamel)
}

// sendDirectGift 完成私聊送礼账务结算，并由服务端发送腾讯云 IM C2C custom message。
func (h *Handler) sendDirectGift(writer http.ResponseWriter, request *http.Request) {
	if h.walletClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	if h.imPublisher == nil {
		// 私聊送礼的可见事实现在由服务端 C2C 承担；没有 publisher 时不能先扣费再让客户端补发。
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	var body directGiftRequestBody
	if !httpkit.Decode(writer, request, &body) {
		return
	}
	commandID := body.normalizedCommandID()
	targetUserID := body.normalizedTargetUserID()
	giftID := body.normalizedGiftID()
	giftCount := body.normalizedGiftCount()
	if commandID == "" || targetUserID <= 0 || giftID == "" || giftCount <= 0 {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}

	senderUserID := auth.UserIDFromContext(request.Context())
	app := appcode.FromContext(request.Context())
	if h.userProfileClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	sender, senderRegionID, ok := h.resolveDirectGiftSender(writer, request, senderUserID)
	if !ok {
		return
	}
	target, ok := h.resolveDirectGiftTarget(writer, request, targetUserID)
	if !ok {
		return
	}
	hostScope, err := h.resolveDirectGiftTargetHostScope(request, targetUserID)
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	if !h.ensureDirectGiftIMAccounts(writer, request, sender, target) {
		return
	}
	// 首次和重试都使用同一稳定 command_id；Wallet 会在读取当前派生上下文后优先返回首次回执，
	// 因此正常请求无需额外做一次只读 receipt 探测。
	walletRequest := &walletv1.DebitDirectGiftRequest{
		CommandId:               commandID,
		SenderUserId:            senderUserID,
		TargetUserId:            targetUserID,
		GiftId:                  giftID,
		GiftCount:               giftCount,
		PriceVersion:            body.normalizedPriceVersion(),
		AppCode:                 app,
		RegionId:                senderRegionID,
		SenderRegionId:          senderRegionID,
		TargetIsHost:            hostScope.targetIsHost,
		TargetHostRegionId:      hostScope.targetHostRegionID,
		TargetAgencyOwnerUserId: hostScope.targetAgencyOwnerUserID,
		EntitlementId:           body.normalizedEntitlementID(),
		ChargeSource:            strings.TrimSpace(body.Source),
	}
	resp, err := h.walletClient.DebitDirectGift(request.Context(), walletRequest)
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	h.completeDirectGift(writer, request, app, commandID, senderUserID, targetUserID, giftID, giftCount, resp)
}

// completeDirectGift 把首次与重放回执收敛成同一个逻辑 IM 事件和 HTTP 响应。
// IM 投递允许重试，但 event_id、消息去重键和账务 payload 始终来自首次 Wallet 回执，客户端不会看到第二笔礼物事实。
func (h *Handler) completeDirectGift(writer http.ResponseWriter, request *http.Request, app string, commandID string, senderUserID int64, targetUserID int64, giftID string, giftCount int32, resp *walletv1.DebitGiftResponse) {
	settlement := directGiftSettlementFromProto(app, commandID, senderUserID, targetUserID, giftID, giftCount, resp)
	_, message, ok := h.publishDirectGiftIM(writer, request, senderUserID, targetUserID, settlement)
	if !ok {
		return
	}
	settlement.Message = message
	httpkit.WriteOK(writer, request, settlement)
}

func (h *Handler) resolveDirectGiftSender(writer http.ResponseWriter, request *http.Request, senderUserID int64) (*userv1.User, int64, bool) {
	resp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
		Meta:   httpkit.UserMeta(request, ""),
		UserId: senderUserID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return nil, 0, false
	}
	user := resp.GetUser()
	if user == nil || user.GetRegionId() <= 0 {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return nil, 0, false
	}
	// 私聊没有房间 visible_region_id；结算区域只能来自当前用户资料，不能让客户端提交 region_id。
	return user, user.GetRegionId(), true
}

func (h *Handler) resolveDirectGiftTarget(writer http.ResponseWriter, request *http.Request, targetUserID int64) (*userv1.User, bool) {
	resp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
		Meta:   httpkit.UserMeta(request, ""),
		UserId: targetUserID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return nil, false
	}
	if resp.GetUser() == nil || resp.GetUser().GetUserId() <= 0 {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return nil, false
	}
	return resp.GetUser(), true
}

func (h *Handler) resolveDirectGiftTargetHostScope(request *http.Request, targetUserID int64) (directGiftHostScope, error) {
	if targetUserID <= 0 {
		return directGiftHostScope{}, status.Error(codes.InvalidArgument, "target user is invalid")
	}
	if h.userHostClient == nil {
		// Host 身份与 Agency 归属只能由 user-service 判定；配置缺失时不能把真实 Host 降级成普通收礼人绕过收益政策。
		return directGiftHostScope{}, status.Error(codes.Unavailable, "user host service is unavailable")
	}
	resp, err := h.userHostClient.GetHostProfile(request.Context(), &userv1.GetHostProfileRequest{
		Meta:   httpkit.UserMeta(request, ""),
		UserId: targetUserID,
	})
	if err != nil {
		if xerr.ReasonFromGRPC(err) == xerr.NotFound {
			return directGiftHostScope{}, nil
		}
		return directGiftHostScope{}, err
	}
	if resp == nil || resp.GetHostProfile() == nil {
		return directGiftHostScope{}, status.Error(codes.Unavailable, "user host service returned an invalid response")
	}
	profile := resp.GetHostProfile()
	if !strings.EqualFold(strings.TrimSpace(profile.GetStatus()), "active") {
		return directGiftHostScope{}, nil
	}
	if profile.GetRegionId() <= 0 {
		return directGiftHostScope{}, status.Error(codes.FailedPrecondition, "active host region is missing")
	}
	return directGiftHostScope{
		targetIsHost:            true,
		targetHostRegionID:      profile.GetRegionId(),
		targetAgencyOwnerUserID: profile.GetCurrentAgencyOwnerUserId(),
	}, nil
}

func (h *Handler) ensureDirectGiftIMAccounts(writer http.ResponseWriter, request *http.Request, sender *userv1.User, target *userv1.User) bool {
	if h.imAccountImporter == nil {
		// 测试 fake publisher 或已有账号环境可以不注入 importer；生产 app 初始化会注入 REST importer。
		return true
	}
	for _, user := range []*userv1.User{sender, target} {
		if user == nil || user.GetUserId() <= 0 {
			httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
			return false
		}
		if err := h.imAccountImporter.ImportAccount(request.Context(), user.GetUserId(), user.GetUsername(), user.GetAvatar()); err != nil {
			httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
			return false
		}
	}
	return true
}

func (h *Handler) publishDirectGiftIM(writer http.ResponseWriter, request *http.Request, senderUserID int64, targetUserID int64, settlement directGiftSettlementData) (json.RawMessage, *directGiftMessageData, bool) {
	payloadJSON, err := directGiftPayloadJSON(settlement)
	if err != nil {
		httpkit.WriteError(writer, request, http.StatusInternalServerError, httpkit.CodeInternalError, "internal error")
		return nil, nil, false
	}
	if err := h.imPublisher.PublishUserCustomMessage(request.Context(), tencentim.CustomUserMessage{
		ToAccount:            tencentim.FormatUserID(targetUserID),
		FromAccount:          tencentim.FormatUserID(senderUserID),
		SyncOtherMachine:     1,
		EventID:              settlement.EventID,
		Desc:                 directGiftIMDesc,
		Ext:                  directGiftIMExt,
		DeduplicateByEventID: true,
		PayloadJSON:          payloadJSON,
	}); err != nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return nil, nil, false
	}
	sentAtMS := time.Now().UTC().UnixMilli()
	message := &directGiftMessageData{
		MessageID:         settlement.EventID,
		PeerUserID:        strconv.FormatInt(targetUserID, 10),
		SenderUserID:      strconv.FormatInt(senderUserID, 10),
		Text:              directGiftIMPreviewText,
		CustomData:        string(payloadJSON),
		CustomDescription: directGiftIMDesc,
		CustomExtension:   directGiftIMExt,
		CloudCustomData:   string(payloadJSON),
		SentAtMS:          sentAtMS,
		IsMine:            true,
	}
	return payloadJSON, message, true
}

func directGiftPayloadJSON(settlement directGiftSettlementData) (json.RawMessage, error) {
	payload := directGiftIMPayload{
		Type:                   directGiftIMDesc,
		EventID:                settlement.EventID,
		CommandID:              settlement.CommandID,
		AppCode:                settlement.AppCode,
		SenderUserID:           tencentim.FormatOptionalUserID(settlement.SenderUserID),
		TargetUserID:           tencentim.FormatOptionalUserID(settlement.TargetUserID),
		GiftID:                 settlement.GiftID,
		GiftName:               settlement.GiftName,
		GiftIconURL:            settlement.GiftIconURL,
		GiftAnimationURL:       settlement.GiftAnimationURL,
		GiftCount:              settlement.GiftCount,
		GiftPriceCoin:          directGiftUnitCoinPrice(settlement),
		MetadataJSON:           "",
		EffectTypes:            settlement.GiftEffectTypes,
		BillingReceiptID:       settlement.BillingReceiptID,
		TransactionID:          settlement.TransactionID,
		ChargeAssetType:        settlement.ChargeAssetType,
		ChargeAmount:           settlement.ChargeAmount,
		ChargeSource:           settlement.ChargeSource,
		PriceVersion:           settlement.PriceVersion,
		CoinBalanceAfter:       settlement.CoinBalanceAfter,
		GiftIncomeCoinAmount:   settlement.GiftIncomeCoinAmount,
		GiftIncomeBalanceAfter: settlement.GiftIncomeBalanceAfter,
		EntitlementID:          settlement.EntitlementID,
	}
	return json.Marshal(payload)
}

func directGiftUnitCoinPrice(settlement directGiftSettlementData) int64 {
	if settlement.GiftCount <= 0 || settlement.CoinSpent <= 0 {
		return 0
	}
	return settlement.CoinSpent / int64(settlement.GiftCount)
}

func directGiftSettlementFromProto(app string, commandID string, senderUserID int64, targetUserID int64, giftID string, giftCount int32, resp *walletv1.DebitGiftResponse) directGiftSettlementData {
	normalizedCommandID := strings.TrimSpace(commandID)
	return directGiftSettlementData{
		BillingReceiptID:       resp.GetBillingReceiptId(),
		TransactionID:          resp.GetTransactionId(),
		EventID:                directGiftEventID(app, normalizedCommandID),
		CommandID:              normalizedCommandID,
		AppCode:                appcode.Normalize(app),
		SenderUserID:           senderUserID,
		TargetUserID:           targetUserID,
		GiftID:                 strings.TrimSpace(giftID),
		GiftName:               resp.GetGiftName(),
		GiftIconURL:            resp.GetGiftIconUrl(),
		GiftAnimationURL:       resp.GetGiftAnimationUrl(),
		GiftEffectTypes:        resp.GetGiftEffectTypes(),
		GiftCount:              giftCount,
		GiftTypeCode:           resp.GetGiftTypeCode(),
		CoinSpent:              resp.GetCoinSpent(),
		ChargeAssetType:        resp.GetChargeAssetType(),
		ChargeAmount:           resp.GetChargeAmount(),
		ChargeSource:           resp.GetChargeSource(),
		PriceVersion:           resp.GetPriceVersion(),
		BalanceAfter:           resp.GetBalanceAfter(),
		CoinBalanceAfter:       resp.GetBalanceAfter(),
		GiftIncomeCoinAmount:   resp.GetGiftIncomeCoinAmount(),
		GiftIncomeBalanceAfter: resp.GetGiftIncomeBalanceAfter(),
		HostPeriodDiamondAdded: resp.GetHostPeriodDiamondAdded(),
		HostPeriodCycleKey:     resp.GetHostPeriodCycleKey(),
		EntitlementID:          resp.GetEntitlementId(),
	}
}

func directGiftEventID(app string, commandID string) string {
	normalizedApp := appcode.Normalize(app)
	if normalizedApp == "" {
		normalizedApp = appcode.Default
	}
	return "direct_gift:" + normalizedApp + ":" + strings.TrimSpace(commandID)
}
