package app

import (
	"context"
	"errors"
	"fmt"
	"time"

	"hyapp/pkg/outboxpartition"
	"hyapp/pkg/outboxreplay"
	"hyapp/pkg/rocketmqx"
	servicemq "hyapp/pkg/servicekit/mq"
	"hyapp/services/game-service/internal/config"
	mysqlstorage "hyapp/services/game-service/internal/storage/mysql"
)

// ReplayGameOutboxOnce runs the ordinary game fact publisher for one App and a
// process-wide hard limit. It opens no gRPC listener and no downstream service
// connection, so an empty partition is a read-only no-op apart from MQ producer
// registration.
func ReplayGameOutboxOnce(ctx context.Context, cfg config.Config, scope outboxreplay.Scope) (outboxreplay.Result, error) {
	validatedScope, err := outboxreplay.NewScope(scope.AppCode, scope.Limit, 500)
	if err != nil {
		return outboxreplay.Result{}, err
	}
	if !cfg.RocketMQ.GameOutbox.Enabled {
		return outboxreplay.Result{}, errors.New("game RocketMQ outbox is not enabled")
	}
	repository, err := mysqlstorage.Open(ctx, cfg.MySQLDSN)
	if err != nil {
		return outboxreplay.Result{}, err
	}
	defer func() { _ = repository.Close() }()
	producer, err := rocketmqx.NewProducer(rocketMQProducerConfig(cfg.RocketMQ))
	if err != nil {
		return outboxreplay.Result{}, err
	}
	if err := servicemq.StartProducers([]*rocketmqx.Producer{producer}); err != nil {
		return outboxreplay.Result{}, err
	}
	defer servicemq.ShutdownProducers([]*rocketmqx.Producer{producer})

	replayConfig := cfg
	replayConfig.OutboxWorker.BatchSize = 1
	replayConfig.OutboxWorker.AppCodes = []string{validatedScope.AppCode}
	replayCtx, cancel := context.WithCancel(ctx)
	defer cancel()
	replayApp := &App{
		repo:             repository,
		outboxProducer:   producer,
		outboxPartitions: outboxpartition.New(replayConfig.OutboxWorker.AppCodes, outboxpartition.DefaultDiscoveryRefreshInterval),
		outboxCtx:        replayCtx,
		outboxCancel:     cancel,
		cfg:              replayConfig,
	}
	workerID := fmt.Sprintf("ops-game-outbox-%s-%d", validatedScope.AppCode, time.Now().UTC().UnixMilli())
	return outboxreplay.Run(ctx, validatedScope, func(context.Context) (int, error) {
		// processOutboxBatch derives the same worker prefix as the live service;
		// attach the unique run ID to NodeID so lock ownership remains auditable.
		replayApp.cfg.NodeID = workerID
		return replayApp.processOutboxBatch()
	})
}
