package app

import (
	"context"
	"errors"
	"net"
	"sync"
	"time"

	"google.golang.org/grpc"
	"hyapp/pkg/grpchealth"
	"hyapp/pkg/healthhttp"
	"hyapp/pkg/logx"
	"hyapp/pkg/rocketmqx"
	serviceapp "hyapp/pkg/servicekit/app"
	"hyapp/pkg/servicekit/grpcserver"
	servicemq "hyapp/pkg/servicekit/mq"
	"hyapp/services/activity-service/internal/config"
	activitytemplateservice "hyapp/services/activity-service/internal/service/activitytemplate"
	broadcastservice "hyapp/services/activity-service/internal/service/broadcast"
	cumulativerechargeservice "hyapp/services/activity-service/internal/service/cumulativerecharge"
	firstrechargeservice "hyapp/services/activity-service/internal/service/firstrecharge"
	inviteactivityservice "hyapp/services/activity-service/internal/service/inviteactivity"
	messageservice "hyapp/services/activity-service/internal/service/message"
	wheelservice "hyapp/services/activity-service/internal/service/wheel"
	mysqlstorage "hyapp/services/activity-service/internal/storage/mysql"
)

// App 装配 activity-service 的 gRPC 入口、健康检查、后台 worker 和外部连接。
// 具体业务模块的创建和注册都放在独立文件里，避免启动入口继续膨胀成一个难维护的大函数。
type App struct {
	server                             *grpc.Server
	listener                           net.Listener
	health                             *grpchealth.ServingChecker
	healthHTTP                         *healthhttp.Server
	mysqlRepo                          *mysqlstorage.Repository
	broadcast                          *broadcastservice.Service
	firstRechargeReward                *firstrechargeservice.Service
	cumulativeRecharge                 *cumulativerechargeservice.Service
	inviteActivityReward               *inviteactivityservice.Service
	actionConfirm                      *messageservice.ActionConfirmService
	wheel                              *wheelservice.Service
	activityTemplate                   *activitytemplateservice.Service
	broadcastWorkerEnabled             bool
	messageActionWorkerEnabled         bool
	wheelRewardWorkerEnabled           bool
	activityTemplateStatsWorkerEnabled bool
	workerNodeID                       string
	messageActionWorkerOptions         config.MessageActionConfirmWorkerConfig
	wheelRewardWorkerOptions           config.WheelRewardWorkerConfig
	activityTemplateStatsWorkerOptions config.ActivityTemplateStatsWorkerConfig
	messageActionOutboxTopic           string
	activityTemplateOutboxTopic        string
	userLeaderboardRedisClose          func() error
	mqConsumers                        []*rocketmqx.Consumer
	messageActionProducer              *rocketmqx.Producer
	activityTemplateProducer           *rocketmqx.Producer
	userConn                           *grpc.ClientConn
	walletConn                         *grpc.ClientConn
	roomConn                           *grpc.ClientConn
	workers                            *serviceapp.BackgroundGroup
	closeOnce                          sync.Once
}

// New 初始化 activity-service 应用。
func New(cfg config.Config) (*App, error) {
	startupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	repository, err := openRepository(startupCtx, cfg)
	if err != nil {
		return nil, err
	}
	var listener net.Listener
	var clients externalClients
	var mqConsumers []*rocketmqx.Consumer
	var messageActionProducer *rocketmqx.Producer
	var activityTemplateProducer *rocketmqx.Producer
	var userLeaderboardRedisClose func() error
	cleanup := func() {
		shutdownConsumers(mqConsumers)
		servicemq.ShutdownProducers([]*rocketmqx.Producer{messageActionProducer, activityTemplateProducer})
		if userLeaderboardRedisClose != nil {
			_ = userLeaderboardRedisClose()
		}
		clients.Close()
		if listener != nil {
			_ = listener.Close()
		}
		_ = repository.Close()
	}

	listener, err = listenGRPC(cfg.GRPCAddr)
	if err != nil {
		cleanup()
		return nil, err
	}
	clients, err = dialExternalClients(cfg)
	if err != nil {
		cleanup()
		return nil, err
	}

	server := grpcserver.New("activity-service")
	userLeaderboardStore, closeLeaderboardRedis, err := openUserLeaderboardStore(startupCtx, cfg.UserLeaderboardWorker)
	if err != nil {
		cleanup()
		return nil, err
	}
	userLeaderboardRedisClose = closeLeaderboardRedis
	services, err := buildServiceBundle(cfg, repository, clients, userLeaderboardStore)
	if err != nil {
		cleanup()
		return nil, err
	}
	registerGRPCServers(server, services)

	mqConsumers, err = buildMQConsumers(cfg, services)
	if err != nil {
		cleanup()
		return nil, err
	}
	if cfg.RocketMQ.MessageActionOutbox.Enabled {
		messageActionProducer, err = rocketmqx.NewProducer(messageActionProducerConfig(cfg.RocketMQ))
		if err != nil {
			cleanup()
			return nil, err
		}
	}
	if cfg.RocketMQ.ActivityTemplateOutbox.Enabled {
		activityTemplateProducer, err = rocketmqx.NewProducer(activityTemplateProducerConfig(cfg.RocketMQ))
		if err != nil {
			cleanup()
			return nil, err
		}
	}
	health, healthHTTP, err := newHealthServers(cfg, server, repository)
	if err != nil {
		cleanup()
		return nil, err
	}

	return &App{
		server:                             server,
		listener:                           listener,
		health:                             health,
		healthHTTP:                         healthHTTP,
		mysqlRepo:                          repository,
		broadcast:                          services.broadcast,
		firstRechargeReward:                services.firstRechargeReward,
		cumulativeRecharge:                 services.cumulativeRecharge,
		inviteActivityReward:               services.inviteActivityReward,
		actionConfirm:                      services.actionConfirm,
		wheel:                              services.wheel,
		activityTemplate:                   services.activityTemplate,
		broadcastWorkerEnabled:             cfg.Broadcast.Enabled,
		messageActionWorkerEnabled:         cfg.MessageActionConfirmWorker.Enabled,
		wheelRewardWorkerEnabled:           cfg.WheelRewardWorker.Enabled,
		activityTemplateStatsWorkerEnabled: cfg.ActivityTemplateStatsWorker.Enabled,
		workerNodeID:                       cfg.NodeID,
		messageActionWorkerOptions:         cfg.MessageActionConfirmWorker,
		wheelRewardWorkerOptions:           cfg.WheelRewardWorker,
		activityTemplateStatsWorkerOptions: cfg.ActivityTemplateStatsWorker,
		messageActionOutboxTopic:           cfg.RocketMQ.MessageActionOutbox.Topic,
		activityTemplateOutboxTopic:        cfg.RocketMQ.ActivityTemplateOutbox.Topic,
		userLeaderboardRedisClose:          userLeaderboardRedisClose,
		mqConsumers:                        mqConsumers,
		messageActionProducer:              messageActionProducer,
		activityTemplateProducer:           activityTemplateProducer,
		userConn:                           clients.userConn,
		walletConn:                         clients.walletConn,
		roomConn:                           clients.roomConn,
		workers:                            serviceapp.NewBackground(context.Background()),
	}, nil
}

// Run 启动 gRPC 服务。
func (a *App) Run() error {
	a.runHealthHTTP()
	a.health.MarkServing()
	if err := a.startMQ(); err != nil {
		a.health.MarkStopped()
		return err
	}
	if a.broadcastWorkerEnabled && a.broadcast != nil && a.workers != nil {
		a.workers.Go(func(ctx context.Context) {
			a.broadcast.RunWorker(ctx, broadcastservice.WorkerOptions{})
		})
	}
	if a.messageActionWorkerEnabled && a.actionConfirm != nil && a.messageActionProducer != nil && a.workers != nil {
		a.workers.Go(func(ctx context.Context) {
			a.runMessageActionOutboxWorker(ctx)
		})
	}
	if a.wheelRewardWorkerEnabled && a.wheel != nil && a.workers != nil {
		a.workers.Go(func(ctx context.Context) {
			a.wheel.RunRewardWorker(ctx, wheelservice.RewardWorkerOptions{
				WorkerID:     a.workerNodeID + ":wheel-reward",
				AppCodes:     a.wheelRewardWorkerOptions.AppCodes,
				PollInterval: a.wheelRewardWorkerOptions.PollInterval,
				BatchSize:    a.wheelRewardWorkerOptions.BatchSize,
				LockTTL:      a.wheelRewardWorkerOptions.LockTTL,
				MaxRetry:     a.wheelRewardWorkerOptions.MaxRetry,
			})
		})
	}
	if a.activityTemplateStatsWorkerEnabled && a.activityTemplate != nil && a.activityTemplateProducer != nil && a.workers != nil {
		a.workers.Go(func(ctx context.Context) {
			a.runActivityTemplateStatsOutboxWorker(ctx)
		})
	}
	err := a.server.Serve(a.listener)
	if a.workers != nil {
		a.workers.StopAndWait()
	}
	a.shutdownMQ()
	a.health.MarkStopped()
	if errors.Is(err, grpc.ErrServerStopped) {
		return nil
	}

	return err
}

// Close 关闭 gRPC 服务。
func (a *App) Close() {
	a.closeOnce.Do(func() {
		a.health.MarkDraining()
		if a.workers != nil {
			a.workers.StopAndWait()
		}
		a.shutdownMQ()
		grpcserver.GracefulStop(a.server, 15*time.Second)
		a.closeHealthHTTP()
		if a.userConn != nil {
			_ = a.userConn.Close()
		}
		if a.walletConn != nil {
			_ = a.walletConn.Close()
		}
		if a.roomConn != nil {
			_ = a.roomConn.Close()
		}
		if a.mysqlRepo != nil {
			// MySQL 连接池最后关闭，保证 drain 中的查询或消费提交能完成。
			_ = a.mysqlRepo.Close()
		}
		if a.userLeaderboardRedisClose != nil {
			_ = a.userLeaderboardRedisClose()
		}
	})
}

func (a *App) runHealthHTTP() {
	if a.healthHTTP == nil {
		return
	}
	go func() {
		if err := a.healthHTTP.Run(); err != nil {
			logx.Error(context.Background(), "health_http_run_failed", err)
		}
	}()
}

func (a *App) closeHealthHTTP() {
	if a.healthHTTP == nil {
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	_ = a.healthHTTP.Close(ctx)
}

func (a *App) startMQ() error {
	if err := servicemq.StartProducers([]*rocketmqx.Producer{a.messageActionProducer, a.activityTemplateProducer}); err != nil {
		return err
	}
	return servicemq.StartConsumers(a.mqConsumers)
}

func (a *App) shutdownMQ() {
	servicemq.ShutdownConsumers(a.mqConsumers)
	servicemq.ShutdownProducers([]*rocketmqx.Producer{a.messageActionProducer, a.activityTemplateProducer})
}
