package config

import (
	"errors"
	"strings"
	"time"

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

// Config 描述 wallet-service 启动配置。
type Config struct {
	ServiceName string `yaml:"service_name"`
	NodeID      string `yaml:"node_id"`
	Environment string `yaml:"environment"`
	GRPCAddr    string `yaml:"grpc_addr"`
	// HealthHTTPAddr 是给 CLB/发布脚本探活使用的 HTTP 监听地址，不承载业务流量。
	HealthHTTPAddr string `yaml:"health_http_addr"`
	// MySQLDSN 是账户、交易、分录和 outbox 的必需存储连接串。
	MySQLDSN string `yaml:"mysql_dsn"`
	// ActivityServiceAddr 用于把 wallet 资源赠送 outbox 投影到 activity badge 展示读模型。
	ActivityServiceAddr string `yaml:"activity_service_addr"`
	// RedPacketExpiryWorker 控制红包过期退款扫描。
	RedPacketExpiryWorker RedPacketExpiryWorkerConfig `yaml:"red_packet_expiry_worker"`
	// RocketMQ 控制 wallet_outbox 事实发布。
	RocketMQ RocketMQConfig `yaml:"rocketmq"`
	// GooglePlay 控制 Google Play Developer API 购买校验。
	GooglePlay GooglePlayConfig `yaml:"google_play"`
	// OutboxWorker 控制 wallet_outbox 到 MQ 的补偿投递。
	OutboxWorker OutboxWorkerConfig `yaml:"outbox_worker"`
	// RealtimeOutboxWorker 控制红包等实时 UI 事件到独立 MQ topic 的补偿投递。
	RealtimeOutboxWorker OutboxWorkerConfig `yaml:"realtime_outbox_worker"`
	// ProjectionWorker 控制 wallet_outbox MQ 到本服务读模型的投影消费。
	ProjectionWorker ProjectionWorkerConfig `yaml:"projection_worker"`
	// MySQLAutoMigrate 保留给显式本地迁移；线上 schema 由发布流程或 initdb 管理。
	MySQLAutoMigrate bool        `yaml:"mysql_auto_migrate"`
	Log              logx.Config `yaml:"log"`
}

// RedPacketExpiryWorkerConfig 保存红包过期退款 worker 策略。
type RedPacketExpiryWorkerConfig struct {
	Enabled      bool          `yaml:"enabled"`
	AppCode      string        `yaml:"app_code"`
	PollInterval time.Duration `yaml:"poll_interval"`
	BatchSize    int32         `yaml:"batch_size"`
}

// RocketMQConfig 描述 wallet-service 发布账务事实的 MQ 连接。
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"`
	SendTimeout      time.Duration        `yaml:"send_timeout"`
	Retry            int                  `yaml:"retry"`
	WalletOutbox     WalletOutboxMQConfig `yaml:"wallet_outbox"`
	RealtimeOutbox   WalletOutboxMQConfig `yaml:"realtime_outbox"`
}

// WalletOutboxMQConfig 保存 wallet_outbox topic 的生产者配置。
type WalletOutboxMQConfig struct {
	Enabled       bool   `yaml:"enabled"`
	Topic         string `yaml:"topic"`
	ProducerGroup string `yaml:"producer_group"`
}

// OutboxWorkerConfig 控制 wallet_outbox pending 事件的 MQ fanout。
type OutboxWorkerConfig struct {
	Enabled        bool          `yaml:"enabled"`
	PollInterval   time.Duration `yaml:"poll_interval"`
	BatchSize      int           `yaml:"batch_size"`
	Concurrency    int           `yaml:"concurrency"`
	PublishTimeout time.Duration `yaml:"publish_timeout"`
	MaxRetryCount  int           `yaml:"max_retry_count"`
	InitialBackoff time.Duration `yaml:"initial_backoff"`
	MaxBackoff     time.Duration `yaml:"max_backoff"`
	EventTypes     []string      `yaml:"event_types"`
}

// ProjectionWorkerConfig 控制礼物墙和 badge 展示读模型从 wallet_outbox MQ 投影的节奏。
type ProjectionWorkerConfig struct {
	Enabled                   bool          `yaml:"enabled"`
	PollInterval              time.Duration `yaml:"poll_interval"` // Deprecated: runtime projection is MQ-driven.
	BatchSize                 int           `yaml:"batch_size"`
	LockTTL                   time.Duration `yaml:"lock_ttl"`
	ConsumerGroup             string        `yaml:"consumer_group"`
	ConsumerMaxReconsumeTimes int32         `yaml:"consumer_max_reconsume_times"`
}

// GooglePlayConfig 保存 Google Play Developer API 服务账号和网络配置。
type GooglePlayConfig struct {
	Enabled            bool          `yaml:"enabled"`
	PackageName        string        `yaml:"package_name"`
	ServiceAccountFile string        `yaml:"service_account_file"`
	ServiceAccountJSON string        `yaml:"service_account_json"`
	APIBaseURL         string        `yaml:"api_base_url"`
	TokenURL           string        `yaml:"token_url"`
	HTTPTimeout        time.Duration `yaml:"http_timeout"`
	ConsumeEnabled     bool          `yaml:"consume_enabled"`
}

// Default 返回本地开发默认配置。
func Default() Config {
	return Config{
		ServiceName:         "wallet-service",
		NodeID:              "wallet-local",
		Environment:         "local",
		GRPCAddr:            ":13004",
		HealthHTTPAddr:      ":13104",
		MySQLDSN:            "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_wallet?parseTime=true&charset=utf8mb4&loc=UTC",
		ActivityServiceAddr: "127.0.0.1:13006",
		RedPacketExpiryWorker: RedPacketExpiryWorkerConfig{
			Enabled:      true,
			AppCode:      "lalu",
			PollInterval: 5 * time.Second,
			BatchSize:    50,
		},
		RocketMQ: defaultRocketMQConfig(),
		GooglePlay: GooglePlayConfig{
			Enabled:        false,
			APIBaseURL:     "https://androidpublisher.googleapis.com",
			TokenURL:       "https://oauth2.googleapis.com/token",
			HTTPTimeout:    10 * time.Second,
			ConsumeEnabled: true,
		},
		OutboxWorker: OutboxWorkerConfig{
			Enabled:        false,
			PollInterval:   time.Second,
			BatchSize:      100,
			Concurrency:    1,
			PublishTimeout: 3 * time.Second,
			MaxRetryCount:  10,
			InitialBackoff: 5 * time.Second,
			MaxBackoff:     5 * time.Minute,
		},
		RealtimeOutboxWorker: OutboxWorkerConfig{
			Enabled:        false,
			PollInterval:   time.Second,
			BatchSize:      50,
			Concurrency:    2,
			PublishTimeout: 3 * time.Second,
			MaxRetryCount:  10,
			InitialBackoff: 2 * time.Second,
			MaxBackoff:     time.Minute,
			EventTypes:     defaultRealtimeOutboxEventTypes(),
		},
		ProjectionWorker: ProjectionWorkerConfig{
			Enabled:                   false,
			PollInterval:              60 * time.Second,
			BatchSize:                 10,
			LockTTL:                   30 * time.Second,
			ConsumerGroup:             "hyapp-wallet-projection",
			ConsumerMaxReconsumeTimes: 16,
		},
		MySQLAutoMigrate: false,
		Log: logx.Config{
			Level:           "info",
			Format:          "json",
			MaxPayloadBytes: 2048,
		},
	}
}

func defaultRocketMQConfig() RocketMQConfig {
	return RocketMQConfig{
		Enabled:     false,
		SendTimeout: 3 * time.Second,
		Retry:       2,
		WalletOutbox: WalletOutboxMQConfig{
			Enabled:       false,
			Topic:         "hyapp_wallet_outbox",
			ProducerGroup: "hyapp-wallet-outbox-producer",
		},
		RealtimeOutbox: WalletOutboxMQConfig{
			Enabled:       false,
			Topic:         "hyapp_wallet_realtime_outbox",
			ProducerGroup: "hyapp-wallet-realtime-outbox-producer",
		},
	}
}

// Load 从独立 config.yaml 读取 wallet-service 配置。
func Load(path string) (Config, error) {
	cfg := Default()
	if 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 = "wallet-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.HealthHTTPAddr = strings.TrimSpace(cfg.HealthHTTPAddr)
	if cfg.HealthHTTPAddr == "" {
		cfg.HealthHTTPAddr = ":13104"
	}
	cfg.ActivityServiceAddr = strings.TrimSpace(cfg.ActivityServiceAddr)
	if cfg.ActivityServiceAddr == "" {
		cfg.ActivityServiceAddr = "127.0.0.1:13006"
	}
	cfg.RedPacketExpiryWorker.AppCode = strings.TrimSpace(cfg.RedPacketExpiryWorker.AppCode)
	if cfg.RedPacketExpiryWorker.AppCode == "" {
		cfg.RedPacketExpiryWorker.AppCode = Default().RedPacketExpiryWorker.AppCode
	}
	if cfg.RedPacketExpiryWorker.PollInterval <= 0 {
		cfg.RedPacketExpiryWorker.PollInterval = Default().RedPacketExpiryWorker.PollInterval
	}
	if cfg.RedPacketExpiryWorker.BatchSize <= 0 {
		cfg.RedPacketExpiryWorker.BatchSize = Default().RedPacketExpiryWorker.BatchSize
	}
	rocketMQ, err := normalizeRocketMQConfig(cfg.RocketMQ)
	if err != nil {
		return Config{}, err
	}
	cfg.RocketMQ = rocketMQ
	cfg.GooglePlay.PackageName = strings.TrimSpace(cfg.GooglePlay.PackageName)
	cfg.GooglePlay.ServiceAccountFile = strings.TrimSpace(cfg.GooglePlay.ServiceAccountFile)
	cfg.GooglePlay.ServiceAccountJSON = strings.TrimSpace(cfg.GooglePlay.ServiceAccountJSON)
	cfg.GooglePlay.APIBaseURL = strings.TrimRight(strings.TrimSpace(cfg.GooglePlay.APIBaseURL), "/")
	if cfg.GooglePlay.APIBaseURL == "" {
		cfg.GooglePlay.APIBaseURL = Default().GooglePlay.APIBaseURL
	}
	cfg.GooglePlay.TokenURL = strings.TrimSpace(cfg.GooglePlay.TokenURL)
	if cfg.GooglePlay.TokenURL == "" {
		cfg.GooglePlay.TokenURL = Default().GooglePlay.TokenURL
	}
	if cfg.GooglePlay.HTTPTimeout <= 0 {
		cfg.GooglePlay.HTTPTimeout = Default().GooglePlay.HTTPTimeout
	}
	if cfg.GooglePlay.Enabled {
		if cfg.GooglePlay.PackageName == "" {
			return Config{}, errors.New("google_play package_name is required")
		}
		if cfg.GooglePlay.ServiceAccountFile == "" && cfg.GooglePlay.ServiceAccountJSON == "" {
			return Config{}, errors.New("google_play service account is required")
		}
	}
	cfg.OutboxWorker = normalizeOutboxWorkerConfig(cfg.OutboxWorker, Default().OutboxWorker)
	if cfg.OutboxWorker.Enabled && !cfg.RocketMQ.WalletOutbox.Enabled {
		return Config{}, errors.New("outbox_worker requires rocketmq.wallet_outbox.enabled")
	}
	cfg.RealtimeOutboxWorker = normalizeOutboxWorkerConfig(cfg.RealtimeOutboxWorker, Default().RealtimeOutboxWorker)
	if len(cfg.RealtimeOutboxWorker.EventTypes) == 0 {
		cfg.RealtimeOutboxWorker.EventTypes = defaultRealtimeOutboxEventTypes()
	}
	if cfg.RealtimeOutboxWorker.Enabled && !cfg.RocketMQ.RealtimeOutbox.Enabled {
		return Config{}, errors.New("realtime_outbox_worker requires rocketmq.realtime_outbox.enabled")
	}
	if cfg.ProjectionWorker.PollInterval <= 0 {
		cfg.ProjectionWorker.PollInterval = Default().ProjectionWorker.PollInterval
	}
	if cfg.ProjectionWorker.BatchSize <= 0 {
		cfg.ProjectionWorker.BatchSize = Default().ProjectionWorker.BatchSize
	}
	if cfg.ProjectionWorker.BatchSize > 100 {
		cfg.ProjectionWorker.BatchSize = 100
	}
	if cfg.ProjectionWorker.LockTTL <= 0 {
		cfg.ProjectionWorker.LockTTL = Default().ProjectionWorker.LockTTL
	}
	if cfg.ProjectionWorker.ConsumerGroup = strings.TrimSpace(cfg.ProjectionWorker.ConsumerGroup); cfg.ProjectionWorker.ConsumerGroup == "" {
		cfg.ProjectionWorker.ConsumerGroup = Default().ProjectionWorker.ConsumerGroup
	}
	if cfg.ProjectionWorker.ConsumerMaxReconsumeTimes <= 0 {
		cfg.ProjectionWorker.ConsumerMaxReconsumeTimes = Default().ProjectionWorker.ConsumerMaxReconsumeTimes
	}
	if cfg.ProjectionWorker.Enabled && !cfg.RocketMQ.WalletOutbox.Enabled {
		return Config{}, errors.New("projection_worker requires rocketmq.wallet_outbox.enabled")
	}

	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.SendTimeout <= 0 {
		cfg.SendTimeout = defaults.SendTimeout
	}
	if cfg.Retry <= 0 {
		cfg.Retry = defaults.Retry
	}
	if cfg.WalletOutbox.Topic = strings.TrimSpace(cfg.WalletOutbox.Topic); cfg.WalletOutbox.Topic == "" {
		cfg.WalletOutbox.Topic = defaults.WalletOutbox.Topic
	}
	if cfg.WalletOutbox.ProducerGroup = strings.TrimSpace(cfg.WalletOutbox.ProducerGroup); cfg.WalletOutbox.ProducerGroup == "" {
		cfg.WalletOutbox.ProducerGroup = defaults.WalletOutbox.ProducerGroup
	}
	if cfg.RealtimeOutbox.Topic = strings.TrimSpace(cfg.RealtimeOutbox.Topic); cfg.RealtimeOutbox.Topic == "" {
		cfg.RealtimeOutbox.Topic = defaults.RealtimeOutbox.Topic
	}
	if cfg.RealtimeOutbox.ProducerGroup = strings.TrimSpace(cfg.RealtimeOutbox.ProducerGroup); cfg.RealtimeOutbox.ProducerGroup == "" {
		cfg.RealtimeOutbox.ProducerGroup = defaults.RealtimeOutbox.ProducerGroup
	}
	if cfg.WalletOutbox.Enabled || cfg.RealtimeOutbox.Enabled {
		cfg.Enabled = true
	}
	if cfg.Enabled {
		if len(cfg.NameServers) == 0 && cfg.NameServerDomain == "" {
			return RocketMQConfig{}, errors.New("rocketmq name_servers or name_server_domain is required")
		}
		if (cfg.AccessKey == "") != (cfg.SecretKey == "") {
			return RocketMQConfig{}, errors.New("rocketmq access_key and secret_key must be configured together")
		}
	}
	return cfg, nil
}

func normalizeOutboxWorkerConfig(cfg OutboxWorkerConfig, defaults OutboxWorkerConfig) OutboxWorkerConfig {
	if cfg.PollInterval <= 0 {
		cfg.PollInterval = defaults.PollInterval
	}
	if cfg.BatchSize <= 0 {
		cfg.BatchSize = defaults.BatchSize
	}
	if cfg.BatchSize > 500 {
		cfg.BatchSize = 500
	}
	if cfg.Concurrency <= 0 {
		cfg.Concurrency = defaults.Concurrency
	}
	if cfg.Concurrency > 64 {
		cfg.Concurrency = 64
	}
	if cfg.PublishTimeout <= 0 {
		cfg.PublishTimeout = defaults.PublishTimeout
	}
	if cfg.MaxRetryCount <= 0 {
		cfg.MaxRetryCount = defaults.MaxRetryCount
	}
	if cfg.InitialBackoff <= 0 {
		cfg.InitialBackoff = defaults.InitialBackoff
	}
	if cfg.MaxBackoff <= 0 {
		cfg.MaxBackoff = defaults.MaxBackoff
	}
	if cfg.MaxBackoff < cfg.InitialBackoff {
		cfg.MaxBackoff = cfg.InitialBackoff
	}
	cfg.EventTypes = normalizeStringSlice(cfg.EventTypes)
	return cfg
}

func normalizeStringSlice(values []string) []string {
	normalized := make([]string, 0, len(values))
	seen := make(map[string]struct{}, len(values))
	for _, value := range values {
		if value = strings.TrimSpace(value); value != "" {
			if _, exists := seen[value]; exists {
				continue
			}
			seen[value] = struct{}{}
			normalized = append(normalized, value)
		}
	}
	return normalized
}

func defaultRealtimeOutboxEventTypes() []string {
	return []string{
		"WalletRedPacketCreated",
		"WalletRedPacketClaimed",
		"WalletRedPacketRefunded",
	}
}
