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 {
		shutdownConsumers(mqConsumers)
		return nil, errors.New("rocketmq user_outbox consumer requires tencent_im.enabled")
	}
	if cfg.RocketMQ.UserOutbox.Enabled {
		consumer, err := newUserOutboxConsumer(cfg, services.broadcast)
		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.RedPacketBroadcastWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled {
		consumer, err := newRedPacketWalletConsumer(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
		}
		_, 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 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 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 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
}
