package config

import (
	"fmt"
	"strings"
	"time"

	"hyapp/pkg/configx"
	"hyapp/pkg/logx"
)

// Config 描述 lucky-gift-service 的进程启动边界。
// 规则、RTP、奖池、应用级外部接入和 outbox 都属于本服务；room/activity/admin 只能通过 gRPC 访问。
type Config struct {
	ServiceName       string                `yaml:"service_name"`
	NodeID            string                `yaml:"node_id"`
	Environment       string                `yaml:"environment"`
	GRPCAddr          string                `yaml:"grpc_addr"`
	HealthHTTPAddr    string                `yaml:"health_http_addr"`
	MySQLDSN          string                `yaml:"mysql_dsn"`
	MySQLAutoMigrate  bool                  `yaml:"mysql_auto_migrate"`
	WalletServiceAddr string                `yaml:"wallet_service_addr"`
	LuckyGiftWorker   LuckyGiftWorkerConfig `yaml:"lucky_gift_worker"`
	// RocketMQ 只发布 lucky-gift-service 自有 outbox 事实；room/activity 不能直读幸运礼物数据库。
	RocketMQ RocketMQConfig `yaml:"rocketmq"`
	Log      logx.Config    `yaml:"log"`
}

// RocketMQConfig 保存幸运礼物 owner topic 的连接身份。
type RocketMQConfig struct {
	Enabled          bool                    `yaml:"enabled"`
	NameServers      []string                `yaml:"name_servers"`
	NameServerDomain string                  `yaml:"name_server_domain"`
	AccessKey        string                  `yaml:"access_key"`
	SecretKey        string                  `yaml:"secret_key"`
	SecurityToken    string                  `yaml:"security_token"`
	Namespace        string                  `yaml:"namespace"`
	VIPChannel       bool                    `yaml:"vip_channel"`
	LuckyGiftOutbox  LuckyGiftOutboxMQConfig `yaml:"lucky_gift_outbox"`
}

// LuckyGiftOutboxMQConfig 控制 granted 中奖事实发布；地址和凭据来自运行环境，业务开关在可运行配置中默认开启。
type LuckyGiftOutboxMQConfig struct {
	Enabled       bool          `yaml:"enabled"`
	Topic         string        `yaml:"topic"`
	ProducerGroup string        `yaml:"producer_group"`
	SendTimeout   time.Duration `yaml:"send_timeout"`
	Retry         int           `yaml:"retry"`
}

// LuckyGiftWorkerConfig 控制 draw outbox 补偿和统计快照刷新。
// 外部 App 抽奖不走 HyApp wallet，但仍写 outbox 给审计/统计；内部房间抽奖中奖后由 worker 兜底返奖，
// 并在 granted 收敛后把 owner 事实可靠发布给 room/activity 的独立消费者。
type LuckyGiftWorkerConfig struct {
	Enabled              bool          `yaml:"enabled"`
	WorkerPollInterval   time.Duration `yaml:"worker_poll_interval"`
	WorkerBatchSize      int           `yaml:"worker_batch_size"`
	WorkerConcurrency    int           `yaml:"worker_concurrency"`
	WorkerLockTTL        time.Duration `yaml:"worker_lock_ttl"`
	WorkerMaxRetry       int           `yaml:"worker_max_retry"`
	PublishTimeout       time.Duration `yaml:"publish_timeout"`
	StatsRefreshInterval time.Duration `yaml:"stats_refresh_interval"`
	StatsBatchSize       int           `yaml:"stats_batch_size"`
	// Retention 控制 delivered 历史事件的定期清理；它独立于 Enabled，临时关闭补偿投递不会停掉清理。
	Retention OutboxRetentionConfig `yaml:"retention"`
}

// OutboxRetentionConfig 控制 lucky_gift_outbox 中 delivered 历史事件的小批量定期删除。
// 只清理 delivered：pending/retryable/delivering 仍在投递生命周期内，failed 留给人工补偿。
// 抽奖事实和审计都在 lucky_draw_records；下游只读 RocketMQ owner 事实，不直读本表，因此 delivered 行可定期清理。
type OutboxRetentionConfig struct {
	Enabled bool `yaml:"enabled"`
	// MaxAge 是 delivered 事件按 updated_at_ms 计算的保留时长，早于 now-MaxAge 的才会删除。
	MaxAge time.Duration `yaml:"max_age"`
	// ScanInterval 是清空一轮可删事件后到下一轮扫描的间隔。
	ScanInterval time.Duration `yaml:"scan_interval"`
	// BatchSize 是单条 DELETE 的行数上限，小批量避免大事务长时间持有行锁。
	BatchSize int `yaml:"batch_size"`
}

func Default() Config {
	return Config{
		ServiceName:       "lucky-gift-service",
		NodeID:            "lucky-gift-local",
		Environment:       "local",
		GRPCAddr:          ":13013",
		HealthHTTPAddr:    ":13113",
		MySQLDSN:          "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_lucky_gift?parseTime=true&charset=utf8mb4&loc=UTC",
		MySQLAutoMigrate:  true,
		WalletServiceAddr: "127.0.0.1:13004",
		LuckyGiftWorker: LuckyGiftWorkerConfig{
			Enabled:              true,
			WorkerPollInterval:   time.Second,
			WorkerBatchSize:      100,
			WorkerConcurrency:    8,
			WorkerLockTTL:        30 * time.Second,
			WorkerMaxRetry:       8,
			PublishTimeout:       5 * time.Second,
			StatsRefreshInterval: 10 * time.Minute,
			StatsBatchSize:       5000,
			Retention: OutboxRetentionConfig{
				Enabled: true,
				// 线上 delivered 约 9 万行/天且无下游读方；7 天足够排障，30 天会让表膨胀到近 300 万行。
				MaxAge:       7 * 24 * time.Hour,
				ScanInterval: 5 * time.Minute,
				BatchSize:    500,
			},
		},
		RocketMQ: defaultRocketMQConfig(),
		Log: logx.Config{
			Level:           "info",
			Format:          "json",
			MaxPayloadBytes: 2048,
		},
	}
}

func defaultRocketMQConfig() RocketMQConfig {
	return RocketMQConfig{
		Enabled: false,
		LuckyGiftOutbox: LuckyGiftOutboxMQConfig{
			Enabled:       false,
			Topic:         "hyapp_lucky_gift_outbox",
			ProducerGroup: "hyapp-lucky-gift-outbox-producer",
			SendTimeout:   5 * time.Second,
			Retry:         2,
		},
	}
}

func Load(path string) (Config, error) {
	cfg := Default()
	if strings.TrimSpace(path) == "" {
		return cfg, nil
	}
	if err := configx.LoadYAML(path, &cfg); err != nil {
		return Config{}, err
	}
	cfg.ServiceName = strings.TrimSpace(cfg.ServiceName)
	if cfg.ServiceName == "" {
		cfg.ServiceName = "lucky-gift-service"
	}
	cfg.NodeID = strings.TrimSpace(cfg.NodeID)
	if cfg.NodeID == "" {
		cfg.NodeID = cfg.ServiceName
	}
	cfg.Environment = strings.ToLower(strings.TrimSpace(cfg.Environment))
	if cfg.Environment == "" {
		cfg.Environment = "local"
	}
	cfg.GRPCAddr = strings.TrimSpace(cfg.GRPCAddr)
	if cfg.GRPCAddr == "" {
		cfg.GRPCAddr = ":13013"
	}
	cfg.HealthHTTPAddr = strings.TrimSpace(cfg.HealthHTTPAddr)
	if cfg.HealthHTTPAddr == "" {
		cfg.HealthHTTPAddr = ":13113"
	}
	cfg.WalletServiceAddr = strings.TrimSpace(cfg.WalletServiceAddr)
	if cfg.WalletServiceAddr == "" {
		cfg.WalletServiceAddr = "127.0.0.1:13004"
	}
	if cfg.LuckyGiftWorker.WorkerPollInterval <= 0 {
		cfg.LuckyGiftWorker.WorkerPollInterval = time.Second
	}
	if cfg.LuckyGiftWorker.WorkerBatchSize <= 0 {
		cfg.LuckyGiftWorker.WorkerBatchSize = 100
	}
	if cfg.LuckyGiftWorker.WorkerConcurrency <= 0 {
		cfg.LuckyGiftWorker.WorkerConcurrency = 8
	}
	if cfg.LuckyGiftWorker.WorkerLockTTL <= 0 {
		cfg.LuckyGiftWorker.WorkerLockTTL = 30 * time.Second
	}
	if cfg.LuckyGiftWorker.WorkerMaxRetry <= 0 {
		cfg.LuckyGiftWorker.WorkerMaxRetry = 8
	}
	if cfg.LuckyGiftWorker.PublishTimeout <= 0 {
		cfg.LuckyGiftWorker.PublishTimeout = 5 * time.Second
	}
	if cfg.LuckyGiftWorker.StatsRefreshInterval <= 0 {
		cfg.LuckyGiftWorker.StatsRefreshInterval = 10 * time.Minute
	}
	if cfg.LuckyGiftWorker.StatsBatchSize <= 0 {
		cfg.LuckyGiftWorker.StatsBatchSize = 5000
	}
	defaults := Default().LuckyGiftWorker.Retention
	if cfg.LuckyGiftWorker.Retention.MaxAge <= 0 {
		cfg.LuckyGiftWorker.Retention.MaxAge = defaults.MaxAge
	}
	if cfg.LuckyGiftWorker.Retention.ScanInterval <= 0 {
		cfg.LuckyGiftWorker.Retention.ScanInterval = defaults.ScanInterval
	}
	if cfg.LuckyGiftWorker.Retention.BatchSize <= 0 {
		cfg.LuckyGiftWorker.Retention.BatchSize = defaults.BatchSize
	}
	if cfg.LuckyGiftWorker.Retention.Enabled && cfg.LuckyGiftWorker.Retention.MaxAge < 24*time.Hour {
		// 保留窗口低于一天基本可以断定是把单位写错（比如 30 写成 30ns/30s），宁可拒绝启动。
		return Config{}, fmt.Errorf("lucky_gift_worker retention max_age %s is below the 24h safety floor", cfg.LuckyGiftWorker.Retention.MaxAge)
	}
	rocketMQ, err := normalizeRocketMQConfig(cfg.RocketMQ)
	if err != nil {
		return Config{}, err
	}
	cfg.RocketMQ = rocketMQ
	return cfg, nil
}

func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) {
	defaults := defaultRocketMQConfig()
	cfg.NameServers = normalizeStringSlice(cfg.NameServers)
	cfg.NameServerDomain = strings.TrimSpace(cfg.NameServerDomain)
	cfg.AccessKey = strings.TrimSpace(cfg.AccessKey)
	cfg.SecretKey = strings.TrimSpace(cfg.SecretKey)
	cfg.SecurityToken = strings.TrimSpace(cfg.SecurityToken)
	cfg.Namespace = strings.TrimSpace(cfg.Namespace)
	if cfg.LuckyGiftOutbox.Topic = strings.TrimSpace(cfg.LuckyGiftOutbox.Topic); cfg.LuckyGiftOutbox.Topic == "" {
		cfg.LuckyGiftOutbox.Topic = defaults.LuckyGiftOutbox.Topic
	}
	if cfg.LuckyGiftOutbox.ProducerGroup = strings.TrimSpace(cfg.LuckyGiftOutbox.ProducerGroup); cfg.LuckyGiftOutbox.ProducerGroup == "" {
		cfg.LuckyGiftOutbox.ProducerGroup = defaults.LuckyGiftOutbox.ProducerGroup
	}
	if cfg.LuckyGiftOutbox.SendTimeout <= 0 {
		cfg.LuckyGiftOutbox.SendTimeout = defaults.LuckyGiftOutbox.SendTimeout
	}
	if cfg.LuckyGiftOutbox.Retry < 0 {
		cfg.LuckyGiftOutbox.Retry = defaults.LuckyGiftOutbox.Retry
	}
	if cfg.LuckyGiftOutbox.Enabled {
		cfg.Enabled = true
	}
	if cfg.Enabled {
		if len(cfg.NameServers) == 0 && cfg.NameServerDomain == "" {
			return RocketMQConfig{}, fmt.Errorf("rocketmq name_servers or name_server_domain is required")
		}
		if (cfg.AccessKey == "") != (cfg.SecretKey == "") {
			return RocketMQConfig{}, fmt.Errorf("rocketmq access_key and secret_key must be configured together")
		}
	}
	return cfg, nil
}

func normalizeStringSlice(input []string) []string {
	out := make([]string, 0, len(input))
	for _, value := range input {
		if value = strings.TrimSpace(value); value != "" {
			out = append(out, value)
		}
	}
	return out
}
