package app

import (
	"context"
	"encoding/json"
	"errors"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/pkg/rocketmqx"
	"hyapp/pkg/roommq"
	"hyapp/pkg/usermq"
	"hyapp/pkg/walletmq"
	"hyapp/services/activity-service/internal/config"
	broadcastservice "hyapp/services/activity-service/internal/service/broadcast"
)

func buildMQConsumers(cfg config.Config, services *serviceBundle) ([]*rocketmqx.Consumer, error) {
	mqConsumers := make([]*rocketmqx.Consumer, 0, 4)
	if cfg.RocketMQ.RoomOutbox.Enabled {
		consumer, err := newRoomOutboxConsumer(cfg, services)
		if err != nil {
			shutdownConsumers(mqConsumers)
			return nil, err
		}
		mqConsumers = append(mqConsumers, consumer)
	}
	if cfg.RocketMQ.UserOutbox.Enabled && !services.broadcastPublisherAvailable {
		if !cfg.TaskEventWorker.Enabled {
			shutdownConsumers(mqConsumers)
			return nil, errors.New("rocketmq user_outbox consumer requires tencent_im.enabled")
		}
	}
	if cfg.RocketMQ.UserOutbox.Enabled && services.broadcastPublisherAvailable {
		consumer, err := newUserOutboxConsumer(cfg, services.broadcast)
		if err != nil {
			shutdownConsumers(mqConsumers)
			return nil, err
		}
		mqConsumers = append(mqConsumers, consumer)
	}
	if cfg.TaskEventWorker.Enabled && cfg.RocketMQ.UserOutbox.Enabled {
		consumer, err := newTaskUserOutboxConsumer(cfg, services)
		if err != nil {
			shutdownConsumers(mqConsumers)
			return nil, err
		}
		mqConsumers = append(mqConsumers, consumer)
	}
	if cfg.FirstRechargeRewardWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled {
		consumer, err := newFirstRechargeWalletConsumer(cfg, services)
		if err != nil {
			shutdownConsumers(mqConsumers)
			return nil, err
		}
		mqConsumers = append(mqConsumers, consumer)
	}
	if cfg.CumulativeRechargeRewardWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled {
		consumer, err := newCumulativeRechargeWalletConsumer(cfg, services)
		if err != nil {
			shutdownConsumers(mqConsumers)
			return nil, err
		}
		mqConsumers = append(mqConsumers, consumer)
	}
	if cfg.InviteActivityRewardWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled {
		consumer, err := newInviteActivityWalletConsumer(cfg, services)
		if err != nil {
			shutdownConsumers(mqConsumers)
			return nil, err
		}
		mqConsumers = append(mqConsumers, consumer)
	}
	if cfg.InviteActivityRewardWorker.Enabled && cfg.RocketMQ.UserOutbox.Enabled {
		consumer, err := newInviteActivityUserOutboxConsumer(cfg, services)
		if err != nil {
			shutdownConsumers(mqConsumers)
			return nil, err
		}
		mqConsumers = append(mqConsumers, consumer)
	}
	if cfg.RedPacketBroadcastWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled {
		consumer, err := newRedPacketWalletConsumer(cfg, services)
		if err != nil {
			shutdownConsumers(mqConsumers)
			return nil, err
		}
		mqConsumers = append(mqConsumers, consumer)
	}
	if cfg.TaskEventWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled {
		consumer, err := newTaskWalletConsumer(cfg, services)
		if err != nil {
			shutdownConsumers(mqConsumers)
			return nil, err
		}
		mqConsumers = append(mqConsumers, consumer)
	}
	return mqConsumers, nil
}

func newRoomOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) {
	consumer, err := rocketmqx.NewConsumer(roomOutboxConsumerConfig(cfg.RocketMQ))
	if err != nil {
		return nil, err
	}
	if err := consumer.Subscribe(cfg.RocketMQ.RoomOutbox.Topic, roommq.TagRoomOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
		envelope, _, err := roommq.DecodeRoomOutboxMessage(message.Body)
		if err != nil {
			return err
		}
		// 一个 room outbox envelope 代表房间状态已经提交；下游模块各自写幂等消费表。
		// 任何模块失败都返回错误给 MQ，让同一个 envelope 重试，避免“播报成功但活动漏计分”的半完成状态。
		eventCtx := appcode.WithContext(ctx, envelope.GetAppCode())
		if _, err := services.broadcast.HandleRoomEvent(eventCtx, envelope); err != nil {
			return err
		}
		if _, err := services.growth.HandleRoomEvent(eventCtx, envelope); err != nil {
			return err
		}
		if _, err := services.weeklyStar.HandleRoomEvent(eventCtx, envelope); err != nil {
			return err
		}
		if _, err := services.task.HandleRoomEvent(eventCtx, envelope); err != nil {
			return err
		}
		_, err = services.roomTurnoverReward.HandleRoomEvent(eventCtx, envelope)
		return err
	}); err != nil {
		_ = consumer.Shutdown()
		return nil, err
	}
	return consumer, nil
}

func newUserOutboxConsumer(cfg config.Config, broadcastSvc *broadcastservice.Service) (*rocketmqx.Consumer, error) {
	consumer, err := rocketmqx.NewConsumer(userOutboxConsumerConfig(cfg.RocketMQ))
	if err != nil {
		return nil, err
	}
	if err := consumer.Subscribe(cfg.RocketMQ.UserOutbox.Topic, usermq.TagUserOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
		outboxMessage, err := usermq.DecodeUserOutboxMessage(message.Body)
		if err != nil {
			return err
		}
		return consumeUserRegionChangedForBroadcast(ctx, broadcastSvc, outboxMessage)
	}); err != nil {
		_ = consumer.Shutdown()
		return nil, err
	}
	return consumer, nil
}

func newFirstRechargeWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) {
	consumer, err := rocketmqx.NewConsumer(walletOutboxConsumerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.FirstRechargeConsumerGroup))
	if err != nil {
		return nil, err
	}
	if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
		event, ok, err := firstRechargeEventFromWalletMessage(message.Body)
		if err != nil || !ok {
			return err
		}
		return services.firstRechargeReward.ConsumeWalletRechargeEvent(appcode.WithContext(ctx, event.AppCode), event)
	}); err != nil {
		_ = consumer.Shutdown()
		return nil, err
	}
	return consumer, nil
}

func newCumulativeRechargeWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) {
	// 累充奖励使用独立 consumer group，避免和首充奖励共享消费位点后互相影响重放和排查。
	consumer, err := rocketmqx.NewConsumer(walletOutboxConsumerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.CumulativeRechargeConsumerGroup))
	if err != nil {
		return nil, err
	}
	if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
		event, ok, err := cumulativeRechargeEventFromWalletMessage(message.Body)
		if err != nil || !ok {
			return err
		}
		return services.cumulativeRecharge.ConsumeWalletRechargeEvent(appcode.WithContext(ctx, event.AppCode), event)
	}); err != nil {
		_ = consumer.Shutdown()
		return nil, err
	}
	return consumer, nil
}

func newInviteActivityWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) {
	// 邀请活动单独使用 wallet_outbox consumer group；重放累计充值时不影响首充、累充和任务的消费进度。
	consumer, err := rocketmqx.NewConsumer(walletOutboxConsumerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.InviteActivityConsumerGroup))
	if err != nil {
		return nil, err
	}
	if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
		event, ok, err := inviteActivityRechargeEventFromWalletMessage(message.Body)
		if err != nil || !ok {
			return err
		}
		return services.inviteActivityReward.ConsumeWalletRechargeEvent(appcode.WithContext(ctx, event.AppCode), event)
	}); err != nil {
		_ = consumer.Shutdown()
		return nil, err
	}
	return consumer, nil
}

func newInviteActivityUserOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) {
	// 邀请绑定和有效邀请都来自 user-service 已提交事实；消费失败必须让同一 outbox 重试，仓储和钱包命令共同保证幂等。
	consumer, err := rocketmqx.NewConsumer(userOutboxInviteActivityConsumerConfig(cfg.RocketMQ))
	if err != nil {
		return nil, err
	}
	if err := consumer.Subscribe(cfg.RocketMQ.UserOutbox.Topic, usermq.TagUserOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
		inviteEvent, inviteOK, err := inviteActivityInviteEventFromUserMessage(message.Body)
		if err != nil {
			return err
		}
		if inviteOK {
			return services.inviteActivityReward.ConsumeInviteEvent(appcode.WithContext(ctx, inviteEvent.AppCode), inviteEvent)
		}
		event, ok, err := inviteActivityValidInviteEventFromUserMessage(message.Body)
		if err != nil || !ok {
			return err
		}
		return services.inviteActivityReward.ConsumeValidInviteEvent(appcode.WithContext(ctx, event.AppCode), event)
	}); err != nil {
		_ = consumer.Shutdown()
		return nil, err
	}
	return consumer, nil
}

func newRedPacketWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) {
	consumer, err := rocketmqx.NewConsumer(walletOutboxConsumerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.RedPacketBroadcastConsumerGroup))
	if err != nil {
		return nil, err
	}
	if err := consumer.Subscribe(redPacketWalletOutboxTopic(cfg.RocketMQ.WalletOutbox), walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
		event, ok, err := redPacketEventFromWalletMessage(message.Body)
		if err != nil || !ok {
			return err
		}
		return services.broadcast.ConsumeRedPacketWalletEvent(appcode.WithContext(ctx, event.AppCode), event)
	}); err != nil {
		_ = consumer.Shutdown()
		return nil, err
	}
	return consumer, nil
}

func newTaskWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) {
	consumer, err := rocketmqx.NewConsumer(walletOutboxConsumerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.TaskConsumerGroup))
	if err != nil {
		return nil, err
	}
	if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
		// 钱包任务消费者使用独立 consumer group，不能复用红包或其他钱包消费者进度；否则充值任务会被其他业务确认掉。
		event, appCode, ok, err := taskEventFromWalletMessage(message.Body)
		if err != nil || !ok {
			return err
		}
		// ConsumeTaskEvent 内部按 event_id 幂等；这里返回错误给 MQ 才会重试，正常 skipped/duplicate 都视为成功确认。
		_, err = services.task.ConsumeTaskEvent(appcode.WithContext(ctx, appCode), event)
		return err
	}); err != nil {
		_ = consumer.Shutdown()
		return nil, err
	}
	return consumer, nil
}

func newTaskUserOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) {
	consumer, err := rocketmqx.NewConsumer(userOutboxTaskConsumerConfig(cfg.RocketMQ))
	if err != nil {
		return nil, err
	}
	if err := consumer.Subscribe(cfg.RocketMQ.UserOutbox.Topic, usermq.TagUserOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
		// user outbox 一条消息可能投影成多条任务事件，例如 CP 双方各一条；只有全部写入成功才确认 MQ。
		events, appCode, err := taskEventsFromUserMessage(message.Body)
		if err != nil || len(events) == 0 {
			return err
		}
		eventCtx := appcode.WithContext(ctx, appCode)
		for _, event := range events {
			// 单条失败时让整条 outbox 重试，已写入成功的事件靠 task_event_consumption 幂等表跳过。
			if _, err := services.task.ConsumeTaskEvent(eventCtx, event); err != nil {
				return err
			}
		}
		return nil
	}); err != nil {
		_ = consumer.Shutdown()
		return nil, err
	}
	return consumer, nil
}

func roomOutboxConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig {
	return rocketMQConsumerConfig(cfg, cfg.RoomOutbox.ConsumerGroup, cfg.RoomOutbox.ConsumerMaxReconsumeTimes)
}

func userOutboxConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig {
	return rocketMQConsumerConfig(cfg, cfg.UserOutbox.ConsumerGroup, cfg.UserOutbox.ConsumerMaxReconsumeTimes)
}

func userOutboxTaskConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig {
	return rocketMQConsumerConfig(cfg, cfg.UserOutbox.TaskConsumerGroup, cfg.UserOutbox.ConsumerMaxReconsumeTimes)
}

func userOutboxInviteActivityConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig {
	return rocketMQConsumerConfig(cfg, cfg.UserOutbox.InviteActivityConsumerGroup, cfg.UserOutbox.ConsumerMaxReconsumeTimes)
}

func walletOutboxConsumerConfig(cfg config.RocketMQConfig, group string) rocketmqx.ConsumerConfig {
	return rocketMQConsumerConfig(cfg, group, cfg.WalletOutbox.ConsumerMaxReconsumeTimes)
}

func redPacketWalletOutboxTopic(cfg config.WalletOutboxMQConfig) string {
	// 钱包实时 topic 显式配置后，红包 worker 只消费实时通道，避免继续被普通账务 topic 的高频消息拖慢。
	if cfg.RealtimeTopic != "" {
		return cfg.RealtimeTopic
	}
	return cfg.Topic
}

func rocketMQConsumerConfig(cfg config.RocketMQConfig, group string, maxReconsume int32) rocketmqx.ConsumerConfig {
	return rocketmqx.ConsumerConfig{
		EndpointConfig: rocketmqx.EndpointConfig{
			NameServers:      cfg.NameServers,
			NameServerDomain: cfg.NameServerDomain,
			AccessKey:        cfg.AccessKey,
			SecretKey:        cfg.SecretKey,
			SecurityToken:    cfg.SecurityToken,
			Namespace:        cfg.Namespace,
			VIPChannel:       cfg.VIPChannel,
		},
		GroupName:          group,
		MaxReconsumeTimes:  maxReconsume,
		ConsumeRetryDelay:  time.Second,
		ConsumePullBatch:   32,
		ConsumePullTimeout: 15 * time.Minute,
	}
}

func shutdownConsumers(consumers []*rocketmqx.Consumer) {
	for _, consumer := range consumers {
		_ = consumer.Shutdown()
	}
}

type userRegionChangedPayload struct {
	UserID      int64 `json:"user_id"`
	OldRegionID int64 `json:"old_region_id"`
	NewRegionID int64 `json:"new_region_id"`
}

func consumeUserRegionChangedForBroadcast(ctx context.Context, service *broadcastservice.Service, message usermq.UserOutboxMessage) error {
	if message.EventType != "UserRegionChanged" {
		return nil
	}
	var payload userRegionChangedPayload
	if err := json.Unmarshal([]byte(message.PayloadJSON), &payload); err != nil {
		return err
	}
	if payload.UserID <= 0 || payload.OldRegionID <= 0 || payload.OldRegionID == payload.NewRegionID {
		return nil
	}
	// 区域广播群成员关系是当前读模型；用户切区后只移除旧群，下一次 UserSig 会按新 users.region_id 返回新群。
	_, _, err := service.RemoveRegionBroadcastMember(appcode.WithContext(ctx, message.AppCode), payload.UserID, payload.OldRegionID)
	return err
}
