package service

import (
	"context"
	"fmt"
	"strings"
	"time"

	roomeventsv1 "hyapp.local/api/proto/events/room/v1"
	roomv1 "hyapp.local/api/proto/room/v1"
	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/tencentim"
	"hyapp/pkg/xerr"
	"hyapp/services/room-service/internal/room/command"
	"hyapp/services/room-service/internal/room/outbox"
	"hyapp/services/room-service/internal/room/rank"
	"hyapp/services/room-service/internal/room/state"
)

// SendGift 先同步调用 wallet，再在 Room Cell 内完成热度、榜单和房间事件落地。
func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*roomv1.SendGiftResponse, error) {
	ctx = contextFromMeta(ctx, req.GetMeta())
	// SendGift 的账务不在 room-service 内结算，但房间表现、热度和榜单由 Room Cell 同步更新。
	cmd := command.SendGift{
		Base:          baseFromMeta(req.GetMeta()),
		TargetType:    normalizeGiftTargetType(req.GetTargetType()),
		TargetUserIDs: normalizeGiftTargetUserIDs(req.GetTargetUserId(), req.GetTargetUserIds()),
		GiftID:        req.GetGiftId(),
		GiftCount:     req.GetGiftCount(),
	}
	if cmd.TargetType == "" {
		cmd.TargetType = "user"
	}
	if cmd.TargetType == "user" && len(cmd.TargetUserIDs) == 1 {
		cmd.TargetUserID = cmd.TargetUserIDs[0]
	}

	result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, localRank *rank.LocalRank) (mutationResult, []outbox.Record, error) {
		if _, exists := current.OnlineUsers[cmd.ActorUserID()]; !exists {
			// 送礼者必须在房间业务 presence 内。
			return mutationResult{}, nil, xerr.New(xerr.NotFound, "sender not in room")
		}

		if cmd.TargetType != "user" {
			// v1 HTTP 契约已预留 all_mic/all_room/couple；账务拆单和房间表现策略补齐前先 fail-close。
			return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_type is unsupported")
		}
		if len(cmd.TargetUserIDs) != 1 || cmd.TargetUserID <= 0 {
			return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_user_ids must contain one user")
		}
		if _, exists := current.OnlineUsers[cmd.TargetUserID]; !exists {
			// v1 要求礼物目标在房间内，避免给不存在的房间表现目标计榜。
			return mutationResult{}, nil, xerr.New(xerr.NotFound, "target not in room")
		}

		if cmd.GiftID == "" || cmd.GiftCount <= 0 {
			// 礼物 ID 和数量是钱包查服务端价格的最小输入，客户端不再提交礼物单价。
			return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "gift_id and gift_count must be valid")
		}
		roomMeta, exists, err := s.repository.GetRoomMeta(ctx, cmd.RoomID())
		if err != nil {
			return mutationResult{}, nil, err
		}
		if !exists {
			return mutationResult{}, nil, xerr.New(xerr.NotFound, "room not found")
		}

		// 钱包扣费在房间状态变更前完成；失败时不写 command log、不进入 Room Cell 提交态。
		walletStartedAt := time.Now()
		billing, err := s.wallet.DebitGift(ctx, &walletv1.DebitGiftRequest{
			CommandId:    cmd.ID(),
			RoomId:       cmd.RoomID(),
			SenderUserId: cmd.ActorUserID(),
			TargetUserId: cmd.TargetUserID,
			GiftId:       cmd.GiftID,
			GiftCount:    cmd.GiftCount,
			AppCode:      appcode.FromContext(ctx),
			RegionId:     roomMeta.VisibleRegionID,
		})
		walletDebitMS := elapsedMS(walletStartedAt)
		if err != nil {
			return mutationResult{}, nil, err
		}
		if billing == nil {
			return mutationResult{}, nil, xerr.New(xerr.Unavailable, "wallet debit response is empty")
		}
		heatValue := billing.GetHeatValue()
		settledCommand := cmd
		settledCommand.BillingReceiptID = billing.GetBillingReceiptId()
		settledCommand.CoinSpent = billing.GetCoinSpent()
		settledCommand.GiftPointAdded = billing.GetGiftPointAdded()
		settledCommand.HeatValue = heatValue
		settledCommand.PriceVersion = billing.GetPriceVersion()
		commandPayload, err := command.Serialize(settledCommand)
		if err != nil {
			return mutationResult{}, nil, err
		}

		// 扣费成功后，Room Cell 同步更新热度和本地礼物榜。
		current.Heat += heatValue
		current.GiftRank = localRank.ApplyGift(cmd.ActorUserID(), heatValue, now)
		current.Version++

		// 送礼事件、热度事件和榜单事件使用同一个房间版本，方便消费者关联同一次命令。
		giftEvent, err := outbox.Build(current.RoomID, "RoomGiftSent", current.Version, now, &roomeventsv1.RoomGiftSent{
			SenderUserId:     cmd.ActorUserID(),
			TargetUserId:     cmd.TargetUserID,
			GiftId:           cmd.GiftID,
			GiftCount:        cmd.GiftCount,
			GiftValue:        heatValue,
			BillingReceiptId: billing.GetBillingReceiptId(),
			VisibleRegionId:  roomMeta.VisibleRegionID,
			CommandId:        cmd.ID(),
		})
		if err != nil {
			return mutationResult{}, nil, err
		}

		heatEvent, err := outbox.Build(current.RoomID, "RoomHeatChanged", current.Version, now, &roomeventsv1.RoomHeatChanged{
			Delta:       heatValue,
			CurrentHeat: current.Heat,
		})
		if err != nil {
			return mutationResult{}, nil, err
		}

		rankItem := findRankItem(current.GiftRank, cmd.ActorUserID())
		rankEvent, err := outbox.Build(current.RoomID, "RoomRankChanged", current.Version, now, &roomeventsv1.RoomRankChanged{
			UserId:    rankItem.UserID,
			Score:     rankItem.Score,
			GiftValue: rankItem.GiftValue,
		})
		if err != nil {
			return mutationResult{}, nil, err
		}

		return mutationResult{
			snapshot:         current.ToProto(),
			billingReceiptID: billing.GetBillingReceiptId(),
			roomHeat:         current.Heat,
			giftRank:         cloneProtoRank(current.GiftRank),
			commandPayload:   commandPayload,
			walletDebitMS:    walletDebitMS,
			syncEvent: &tencentim.RoomEvent{
				// 同步广播只选 GiftSent 主事件作为客户端房间系统消息，Heat/Rank 通过字段携带。
				EventID:      giftEvent.EventID,
				RoomID:       current.RoomID,
				EventType:    "room_gift_sent",
				ActorUserID:  cmd.ActorUserID(),
				TargetUserID: cmd.TargetUserID,
				GiftValue:    heatValue,
				RoomHeat:     current.Heat,
				RoomVersion:  current.Version,
				Attributes: map[string]string{
					"gift_id":            cmd.GiftID,
					"gift_count":         fmt.Sprintf("%d", cmd.GiftCount),
					"billing_receipt_id": billing.GetBillingReceiptId(),
					"coin_spent":         fmt.Sprintf("%d", settledCommand.CoinSpent),
					"gift_point_added":   fmt.Sprintf("%d", settledCommand.GiftPointAdded),
					"price_version":      settledCommand.PriceVersion,
				},
			},
		}, []outbox.Record{giftEvent, heatEvent, rankEvent}, nil
	})
	if err != nil {
		return nil, err
	}

	return &roomv1.SendGiftResponse{
		Result:           commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
		BillingReceiptId: result.billingReceiptID,
		RoomHeat:         result.roomHeat,
		GiftRank:         result.giftRank,
		Room:             result.snapshot,
	}, nil
}

func normalizeGiftTargetType(raw string) string {
	raw = strings.TrimSpace(raw)
	switch raw {
	case "", "user", "all_mic", "all_room", "couple":
		return raw
	default:
		return raw
	}
}

func normalizeGiftTargetUserIDs(targetUserID int64, targetUserIDs []int64) []int64 {
	seen := make(map[int64]bool, len(targetUserIDs)+1)
	ids := make([]int64, 0, len(targetUserIDs)+1)
	for _, userID := range targetUserIDs {
		if userID <= 0 || seen[userID] {
			continue
		}
		seen[userID] = true
		ids = append(ids, userID)
	}
	if len(ids) == 0 && targetUserID > 0 {
		ids = append(ids, targetUserID)
	}
	return ids
}

func findRankItem(items []state.RankItem, userID int64) state.RankItem {
	// 送礼后通常能找到发送方榜单项；找不到时返回 user_id 占位避免空事件。
	for _, item := range items {
		if item.UserID == userID {
			return item
		}
	}

	return state.RankItem{UserID: userID}
}
