package app

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

	"hyapp/pkg/appcode"
	"hyapp/pkg/usermq"
	"hyapp/pkg/walletmq"
	taskdomain "hyapp/services/activity-service/internal/domain/task"
)

const (
	taskWalletRechargeRecorded = walletmq.EventTypeWalletRechargeRecorded
	taskCPRelationshipCreated  = "UserCPRelationshipCreated"
	taskMicDailyStatsUpdated   = "UserMicDailyStatsUpdated"
	taskMoodPublished          = "UserMoodPublished"
)

// taskEventFromWalletMessage 只把钱包 outbox 中的充值入账事实映射为任务事件。
// 钱包流水可能同时承载扣款、冻结、后台调账等事件，任务系统只消费正向充值金币，避免“钱包余额增加”被误算成充值任务。
func taskEventFromWalletMessage(body []byte) (taskdomain.Event, string, bool, error) {
	message, err := walletmq.DecodeWalletOutboxMessage(body)
	if err != nil {
		return taskdomain.Event{}, "", false, err
	}
	// 返回 ok=false 表示这条钱包事件和任务无关，MQ 可以直接确认；只有解析错误或任务写入错误才触发重试。
	if message.EventType != taskWalletRechargeRecorded || message.UserID <= 0 {
		return taskdomain.Event{}, appcode.Normalize(message.AppCode), false, nil
	}
	decoded := decodeTaskPayload(message.PayloadJSON)
	// coin_amount 是新事件主字段；amount/available_delta/message.AvailableDelta 是兼容旧 outbox 或不同钱包实现的兜底来源。
	coinAmount := firstNonZeroInt64(
		int64FromDecoded(decoded, "coin_amount"),
		int64FromDecoded(decoded, "amount"),
		int64FromDecoded(decoded, "available_delta"),
		message.AvailableDelta,
	)
	if coinAmount <= 0 {
		return taskdomain.Event{}, appcode.Normalize(message.AppCode), false, nil
	}
	// 充值维度保留交易号、命令号、资产类型和商品信息，后台可按渠道或商品做任务过滤，也方便排查进度来源。
	dimensions := map[string]any{
		"transaction_id":    message.TransactionID,
		"command_id":        message.CommandID,
		"asset_type":        message.AssetType,
		"recharge_type":     firstNonEmptyString(stringFromDecoded(decoded, "recharge_type"), stringFromDecoded(decoded, "channel"), stringFromDecoded(decoded, "provider")),
		"google_product_id": firstNonEmptyString(stringFromDecoded(decoded, "google_product_id"), stringFromDecoded(decoded, "product_name"), stringFromDecoded(decoded, "product_code")),
	}
	return taskdomain.Event{
		EventID:        message.EventID + ":task:recharge_coin",
		EventType:      message.EventType,
		SourceService:  "wallet-service",
		UserID:         message.UserID,
		MetricType:     taskdomain.MetricRechargeCoin,
		Value:          coinAmount,
		OccurredAtMS:   message.OccurredAtMS,
		DimensionsJSON: mustTaskDimensions(dimensions),
	}, appcode.Normalize(message.AppCode), true, nil
}

// taskEventsFromUserMessage 按 user-service outbox 类型拆分任务事件。
// 这里不让 App 自报 CP、麦位或心情进度，所有进度都来自 user-service 已提交事实。
func taskEventsFromUserMessage(body []byte) ([]taskdomain.Event, string, error) {
	message, err := usermq.DecodeUserOutboxMessage(body)
	if err != nil {
		return nil, "", err
	}
	appCode := appcode.Normalize(message.AppCode)
	switch message.EventType {
	case taskCPRelationshipCreated:
		return cpTaskEventsFromUserMessage(message), appCode, nil
	case taskMicDailyStatsUpdated:
		return micTaskEventsFromUserMessage(message), appCode, nil
	case taskMoodPublished:
		return moodTaskEventsFromUserMessage(message), appCode, nil
	default:
		return nil, appCode, nil
	}
}

func cpTaskEventsFromUserMessage(message usermq.UserOutboxMessage) []taskdomain.Event {
	payload := decodeTaskPayload(message.PayloadJSON)
	// CP 创建事件可能嵌套 relationship/application，也可能只带 aggregate_id；按多种字段名读取是为了兼容不同生产者版本。
	relationship := mapFromTaskPayload(payload, "relationship", "Relationship")
	application := mapFromTaskPayload(payload, "application", "Application")
	requester := mapFromTaskPayload(application, "requester", "Requester")
	target := mapFromTaskPayload(application, "target", "Target")
	relationType := firstNonEmptyString(
		stringFromTaskPayload(relationship, "relation_type", "RelationType"),
		stringFromTaskPayload(application, "relation_type", "RelationType"),
	)
	// CP 是双方共同完成的事实；为双方各生成一条任务事件，event_id 带 user_id 后缀保证双方幂等键互不冲突。
	userIDs := uniquePositiveInt64s(
		int64FromTaskPayload(relationship, "user_a_id", "UserAID", "userAId"),
		int64FromTaskPayload(relationship, "user_b_id", "UserBID", "userBId"),
		int64FromTaskPayload(requester, "user_id", "UserID", "userId"),
		int64FromTaskPayload(target, "user_id", "UserID", "userId"),
		message.AggregateID,
	)
	events := make([]taskdomain.Event, 0, len(userIDs))
	for _, userID := range userIDs {
		events = append(events, taskdomain.Event{
			EventID:       message.EventID + ":task:cp_relationship_created:" + strconv.FormatInt(userID, 10),
			EventType:     message.EventType,
			SourceService: "user-service",
			UserID:        userID,
			MetricType:    taskdomain.MetricCPRelationshipCreated,
			Value:         1,
			OccurredAtMS:  message.OccurredAtMS,
			DimensionsJSON: mustTaskDimensions(map[string]any{
				"relationship_id": stringFromTaskPayload(relationship, "relationship_id", "RelationshipID"),
				"relation_type":   relationType,
			}),
		})
	}
	return events
}

func micTaskEventsFromUserMessage(message usermq.UserOutboxMessage) []taskdomain.Event {
	payload := decodeTaskPayload(message.PayloadJSON)
	// 麦上任务按服务端统计的增量分钟累计；不足 1 分钟的残余毫秒不写任务，避免重复消费时需要维护小数状态。
	micOnlineMS := int64FromTaskPayload(payload, "mic_online_delta_ms", "MicOnlineDeltaMS")
	minutes := micOnlineMS / int64(time.Minute/time.Millisecond)
	userID := firstNonZeroInt64(int64FromTaskPayload(payload, "user_id", "UserID"), message.AggregateID)
	if userID <= 0 || minutes <= 0 {
		return nil
	}
	statDate := stringFromTaskPayload(payload, "stat_date", "StatDate")
	// 日统计事件用 stat_date 的 UTC 中午作为任务发生时间，避免跨时区边界把同一天统计归到前一天或后一天。
	occurredAtMS := taskStatDateOccurredAtMS(statDate, message.OccurredAtMS)
	return []taskdomain.Event{{
		EventID:       message.EventID + ":task:mic_online_minute",
		EventType:     message.EventType,
		SourceService: "user-service",
		UserID:        userID,
		MetricType:    taskdomain.MetricMicOnlineMinute,
		Value:         minutes,
		OccurredAtMS:  occurredAtMS,
		DimensionsJSON: mustTaskDimensions(map[string]any{
			"stat_date":      statDate,
			"mic_session_id": stringFromTaskPayload(payload, "mic_session_id", "MicSessionID"),
		}),
	}}
}

func moodTaskEventsFromUserMessage(message usermq.UserOutboxMessage) []taskdomain.Event {
	// 心情发布先预留 owner API 事件口径；只要 user-service 发出已提交 outbox，就按一次发布累计。
	userID := message.AggregateID
	if userID <= 0 {
		payload := decodeTaskPayload(message.PayloadJSON)
		userID = int64FromTaskPayload(payload, "user_id", "UserID")
	}
	if userID <= 0 {
		return nil
	}
	return []taskdomain.Event{{
		EventID:        message.EventID + ":task:mood_publish_count",
		EventType:      message.EventType,
		SourceService:  "user-service",
		UserID:         userID,
		MetricType:     taskdomain.MetricMoodPublishCount,
		Value:          1,
		OccurredAtMS:   message.OccurredAtMS,
		DimensionsJSON: mustTaskDimensions(decodeTaskPayload(message.PayloadJSON)),
	}}
}

func decodeTaskPayload(payload string) map[string]any {
	// 无效 payload 不阻断无维度任务，返回空对象后仍可累计通用指标；需要指定维度的任务会在仓储匹配阶段自然跳过。
	var decoded map[string]any
	if err := json.Unmarshal([]byte(payload), &decoded); err != nil || decoded == nil {
		return map[string]any{}
	}
	return decoded
}

func mapFromTaskPayload(decoded map[string]any, keys ...string) map[string]any {
	for _, key := range keys {
		if value, ok := decoded[key].(map[string]any); ok {
			return value
		}
	}
	return map[string]any{}
}

func stringFromTaskPayload(decoded map[string]any, keys ...string) string {
	for _, key := range keys {
		if value, ok := decoded[key].(string); ok && strings.TrimSpace(value) != "" {
			return strings.TrimSpace(value)
		}
	}
	return ""
}

func int64FromTaskPayload(decoded map[string]any, keys ...string) int64 {
	for _, key := range keys {
		if value := int64FromDecoded(decoded, key); value != 0 {
			return value
		}
	}
	return 0
}

func uniquePositiveInt64s(values ...int64) []int64 {
	// 同一个用户可能同时出现在 relationship 和 requester 字段，去重后再生成任务事件，避免一次 CP 事实给同一人加两次进度。
	seen := make(map[int64]bool, len(values))
	result := make([]int64, 0, len(values))
	for _, value := range values {
		if value <= 0 || seen[value] {
			continue
		}
		seen[value] = true
		result = append(result, value)
	}
	return result
}

func taskStatDateOccurredAtMS(statDate string, fallback int64) int64 {
	parsed, err := time.ParseInLocation("2006-01-02", strings.TrimSpace(statDate), time.UTC)
	if err != nil {
		// 旧消息没有 stat_date 或格式异常时退回 outbox 发生时间，保证事件仍可消费并由幂等表记录排障。
		return fallback
	}
	return parsed.Add(12 * time.Hour).UnixMilli()
}

func mustTaskDimensions(payload map[string]any) string {
	data, err := json.Marshal(payload)
	if err != nil {
		// 维度只用于过滤和排查，序列化失败不能阻断主指标；返回 {} 后只有无维度过滤的任务会累计。
		return "{}"
	}
	return string(data)
}
