package config

import (
	"errors"
	"net/http"
	"strings"
	"time"

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

// Config 描述 notice-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 是 notice-service 自己的投递位点和死信状态库。
	MySQLDSN string `yaml:"mysql_dsn"`
	// WalletDatabase 仅保留给历史离线修复工具使用；运行态 notice 不再直扫 wallet_outbox。
	WalletDatabase string `yaml:"wallet_database"`
	// RoomDatabase 仅保留给历史离线修复工具使用；运行态 notice 不再直扫 room_outbox。
	RoomDatabase string `yaml:"room_database"`
	// TencentIM 是私有 IM 投递通道配置；本地默认关闭，避免误发真实消息。
	TencentIM TencentIMConfig `yaml:"tencent_im"`
	// WalletNoticeWorker 消费 wallet_outbox 中的钱包用户私有通知事件。
	WalletNoticeWorker WalletNoticeWorkerConfig `yaml:"wallet_notice_worker"`
	// RoomNoticeWorker 消费 room_outbox 中需要给单个用户发送的房间通知。
	RoomNoticeWorker WalletNoticeWorkerConfig `yaml:"room_notice_worker"`
	// CPNoticeWorker 控制 CP user_outbox 事实转腾讯 IM 的投递锁和重试策略。
	CPNoticeWorker WalletNoticeWorkerConfig `yaml:"cp_notice_worker"`
	// ConfirmNoticeWorker 控制通用确认消息转腾讯 IM 的投递锁和重试策略。
	ConfirmNoticeWorker WalletNoticeWorkerConfig `yaml:"confirm_notice_worker"`
	// RocketMQ 消费 owner service 发布的 outbox fanout topic。
	RocketMQ         RocketMQConfig `yaml:"rocketmq"`
	MySQLAutoMigrate bool           `yaml:"mysql_auto_migrate"`
	Log              logx.Config    `yaml:"log"`
}

// TencentIMConfig 描述服务端调用腾讯云 IM REST API 的配置。
type TencentIMConfig struct {
	Enabled         bool          `yaml:"enabled"`
	SDKAppID        int64         `yaml:"sdk_app_id"`
	SecretKey       string        `yaml:"secret_key"`
	AdminIdentifier string        `yaml:"admin_identifier"`
	AdminUserSigTTL time.Duration `yaml:"admin_user_sig_ttl"`
	Endpoint        string        `yaml:"endpoint"`
	RequestTimeout  time.Duration `yaml:"request_timeout"`
	GroupIDPrefix   string        `yaml:"group_id_prefix"`
}

// RESTConfig converts YAML config into shared Tencent IM REST config.
func (c TencentIMConfig) RESTConfig() tencentim.RESTConfig {
	timeout := c.RequestTimeout
	if timeout <= 0 {
		timeout = 5 * time.Second
	}
	return tencentim.RESTConfig{
		SDKAppID:        c.SDKAppID,
		SecretKey:       c.SecretKey,
		AdminIdentifier: c.AdminIdentifier,
		AdminUserSigTTL: c.AdminUserSigTTL,
		Endpoint:        c.Endpoint,
		HTTPClient:      &http.Client{Timeout: timeout},
	}
}

// WalletNoticeWorkerConfig 控制钱包私有通知投递 worker。
type WalletNoticeWorkerConfig struct {
	Enabled        bool          `yaml:"enabled"`
	PollInterval   time.Duration `yaml:"poll_interval"`
	BatchSize      int           `yaml:"batch_size"`
	LockTTL        time.Duration `yaml:"lock_ttl"`
	PublishTimeout time.Duration `yaml:"publish_timeout"`
	MaxRetryCount  int           `yaml:"max_retry_count"`
	InitialBackoff time.Duration `yaml:"initial_backoff"`
	MaxBackoff     time.Duration `yaml:"max_backoff"`
}

// RocketMQConfig 描述 notice-service 消费 owner outbox 事实的 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"`
	WalletOutbox        RoomOutboxMQConfig `yaml:"wallet_outbox"`
	RoomOutbox          RoomOutboxMQConfig `yaml:"room_outbox"`
	UserOutbox          RoomOutboxMQConfig `yaml:"user_outbox"`
	MessageActionOutbox RoomOutboxMQConfig `yaml:"message_action_outbox"`
}

// RoomOutboxMQConfig 控制 notice 对 room_outbox topic 的消费位点。
type RoomOutboxMQConfig struct {
	Enabled                   bool   `yaml:"enabled"`
	Topic                     string `yaml:"topic"`
	ConsumerGroup             string `yaml:"consumer_group"`
	ConsumerMaxReconsumeTimes int32  `yaml:"consumer_max_reconsume_times"`
}

// Default 返回本地开发默认配置。
func Default() Config {
	return Config{
		ServiceName:      "notice-service",
		NodeID:           "notice-local",
		Environment:      "local",
		GRPCAddr:         ":13009",
		HealthHTTPAddr:   ":13109",
		MySQLDSN:         "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_notice?parseTime=true&charset=utf8mb4&loc=UTC",
		WalletDatabase:   "hyapp_wallet",
		RoomDatabase:     "hyapp_room",
		MySQLAutoMigrate: false,
		TencentIM: TencentIMConfig{
			Enabled:         false,
			AdminIdentifier: "administrator",
			AdminUserSigTTL: 24 * time.Hour,
			Endpoint:        tencentim.DefaultEndpoint,
			RequestTimeout:  5 * time.Second,
		},
		WalletNoticeWorker: WalletNoticeWorkerConfig{
			Enabled:        false,
			PollInterval:   time.Second,
			BatchSize:      100,
			LockTTL:        30 * time.Second,
			PublishTimeout: 3 * time.Second,
			MaxRetryCount:  10,
			InitialBackoff: 5 * time.Second,
			MaxBackoff:     5 * time.Minute,
		},
		RoomNoticeWorker: WalletNoticeWorkerConfig{
			Enabled:        false,
			PollInterval:   time.Second,
			BatchSize:      100,
			LockTTL:        30 * time.Second,
			PublishTimeout: 3 * time.Second,
			MaxRetryCount:  10,
			InitialBackoff: 5 * time.Second,
			MaxBackoff:     5 * time.Minute,
		},
		CPNoticeWorker: WalletNoticeWorkerConfig{
			Enabled:        false,
			PollInterval:   time.Second,
			BatchSize:      100,
			LockTTL:        30 * time.Second,
			PublishTimeout: 3 * time.Second,
			MaxRetryCount:  10,
			InitialBackoff: 5 * time.Second,
			MaxBackoff:     5 * time.Minute,
		},
		ConfirmNoticeWorker: WalletNoticeWorkerConfig{
			Enabled:        false,
			PollInterval:   time.Second,
			BatchSize:      100,
			LockTTL:        30 * time.Second,
			PublishTimeout: 3 * time.Second,
			MaxRetryCount:  10,
			InitialBackoff: 5 * time.Second,
			MaxBackoff:     5 * time.Minute,
		},
		RocketMQ: defaultRocketMQConfig(),
		Log: logx.Config{
			Level:           "info",
			Format:          "json",
			MaxPayloadBytes: 2048,
		},
	}
}

func defaultRocketMQConfig() RocketMQConfig {
	return RocketMQConfig{
		Enabled: false,
		WalletOutbox: RoomOutboxMQConfig{
			Enabled:                   false,
			Topic:                     "hyapp_wallet_outbox",
			ConsumerGroup:             "hyapp-notice-wallet-outbox",
			ConsumerMaxReconsumeTimes: 16,
		},
		RoomOutbox: RoomOutboxMQConfig{
			Enabled:                   false,
			Topic:                     "hyapp_room_outbox",
			ConsumerGroup:             "hyapp-notice-room-outbox",
			ConsumerMaxReconsumeTimes: 16,
		},
		UserOutbox: RoomOutboxMQConfig{
			Enabled:                   false,
			Topic:                     "hyapp_user_outbox",
			ConsumerGroup:             "hyapp-notice-cp-user-outbox",
			ConsumerMaxReconsumeTimes: 16,
		},
		MessageActionOutbox: RoomOutboxMQConfig{
			Enabled:                   false,
			Topic:                     "hyapp_message_action_outbox",
			ConsumerGroup:             "hyapp-notice-message-action-outbox",
			ConsumerMaxReconsumeTimes: 16,
		},
	}
}

// Load 从独立 config.yaml 读取 notice-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 = "notice-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 = ":13009"
	}
	cfg.HealthHTTPAddr = strings.TrimSpace(cfg.HealthHTTPAddr)
	if cfg.HealthHTTPAddr == "" {
		cfg.HealthHTTPAddr = ":13109"
	}
	cfg.WalletDatabase = strings.TrimSpace(cfg.WalletDatabase)
	if cfg.WalletDatabase == "" {
		cfg.WalletDatabase = "hyapp_wallet"
	}
	cfg.RoomDatabase = strings.TrimSpace(cfg.RoomDatabase)
	if cfg.RoomDatabase == "" {
		cfg.RoomDatabase = "hyapp_room"
	}
	cfg.WalletNoticeWorker = normalizeWalletNoticeWorker(cfg.WalletNoticeWorker)
	cfg.RoomNoticeWorker = normalizeWalletNoticeWorker(cfg.RoomNoticeWorker)
	cfg.CPNoticeWorker = normalizeWalletNoticeWorker(cfg.CPNoticeWorker)
	cfg.ConfirmNoticeWorker = normalizeWalletNoticeWorker(cfg.ConfirmNoticeWorker)
	rocketMQ, err := normalizeRocketMQConfig(cfg.RocketMQ)
	if err != nil {
		return Config{}, err
	}
	cfg.RocketMQ = rocketMQ
	if cfg.WalletNoticeWorker.Enabled {
		return Config{}, errors.New("wallet_notice_worker direct wallet_outbox polling is disabled; enable rocketmq.wallet_outbox")
	}
	if cfg.RoomNoticeWorker.Enabled {
		return Config{}, errors.New("room_notice_worker direct room_outbox polling is disabled; enable rocketmq.room_outbox")
	}
	if cfg.CPNoticeWorker.Enabled {
		return Config{}, errors.New("cp_notice_worker direct user_outbox polling is disabled; enable rocketmq.user_outbox")
	}
	if cfg.ConfirmNoticeWorker.Enabled {
		return Config{}, errors.New("confirm_notice_worker direct message_action_outbox polling is disabled; enable rocketmq.message_action_outbox")
	}
	if strings.TrimSpace(cfg.TencentIM.AdminIdentifier) == "" {
		cfg.TencentIM.AdminIdentifier = "administrator"
	}
	if cfg.TencentIM.AdminUserSigTTL <= 0 {
		cfg.TencentIM.AdminUserSigTTL = 24 * time.Hour
	}
	if strings.TrimSpace(cfg.TencentIM.Endpoint) == "" {
		cfg.TencentIM.Endpoint = tencentim.DefaultEndpoint
	}
	cfg.TencentIM.GroupIDPrefix = strings.TrimSpace(cfg.TencentIM.GroupIDPrefix)
	if cfg.TencentIM.RequestTimeout <= 0 {
		cfg.TencentIM.RequestTimeout = 5 * time.Second
	}
	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.RoomOutbox.Topic = strings.TrimSpace(cfg.RoomOutbox.Topic); cfg.RoomOutbox.Topic == "" {
		cfg.RoomOutbox.Topic = defaults.RoomOutbox.Topic
	}
	if cfg.RoomOutbox.ConsumerGroup = strings.TrimSpace(cfg.RoomOutbox.ConsumerGroup); cfg.RoomOutbox.ConsumerGroup == "" {
		cfg.RoomOutbox.ConsumerGroup = defaults.RoomOutbox.ConsumerGroup
	}
	if cfg.RoomOutbox.ConsumerMaxReconsumeTimes <= 0 {
		cfg.RoomOutbox.ConsumerMaxReconsumeTimes = defaults.RoomOutbox.ConsumerMaxReconsumeTimes
	}
	if cfg.WalletOutbox.Topic = strings.TrimSpace(cfg.WalletOutbox.Topic); cfg.WalletOutbox.Topic == "" {
		cfg.WalletOutbox.Topic = defaults.WalletOutbox.Topic
	}
	if cfg.WalletOutbox.ConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.ConsumerGroup); cfg.WalletOutbox.ConsumerGroup == "" {
		cfg.WalletOutbox.ConsumerGroup = defaults.WalletOutbox.ConsumerGroup
	}
	if cfg.WalletOutbox.ConsumerMaxReconsumeTimes <= 0 {
		cfg.WalletOutbox.ConsumerMaxReconsumeTimes = defaults.WalletOutbox.ConsumerMaxReconsumeTimes
	}
	if cfg.UserOutbox.Topic = strings.TrimSpace(cfg.UserOutbox.Topic); cfg.UserOutbox.Topic == "" {
		cfg.UserOutbox.Topic = defaults.UserOutbox.Topic
	}
	if cfg.UserOutbox.ConsumerGroup = strings.TrimSpace(cfg.UserOutbox.ConsumerGroup); cfg.UserOutbox.ConsumerGroup == "" {
		cfg.UserOutbox.ConsumerGroup = defaults.UserOutbox.ConsumerGroup
	}
	if cfg.UserOutbox.ConsumerMaxReconsumeTimes <= 0 {
		cfg.UserOutbox.ConsumerMaxReconsumeTimes = defaults.UserOutbox.ConsumerMaxReconsumeTimes
	}
	if cfg.MessageActionOutbox.Topic = strings.TrimSpace(cfg.MessageActionOutbox.Topic); cfg.MessageActionOutbox.Topic == "" {
		cfg.MessageActionOutbox.Topic = defaults.MessageActionOutbox.Topic
	}
	if cfg.MessageActionOutbox.ConsumerGroup = strings.TrimSpace(cfg.MessageActionOutbox.ConsumerGroup); cfg.MessageActionOutbox.ConsumerGroup == "" {
		cfg.MessageActionOutbox.ConsumerGroup = defaults.MessageActionOutbox.ConsumerGroup
	}
	if cfg.MessageActionOutbox.ConsumerMaxReconsumeTimes <= 0 {
		cfg.MessageActionOutbox.ConsumerMaxReconsumeTimes = defaults.MessageActionOutbox.ConsumerMaxReconsumeTimes
	}
	if cfg.RoomOutbox.Enabled || cfg.WalletOutbox.Enabled || cfg.UserOutbox.Enabled || cfg.MessageActionOutbox.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 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
}

func normalizeWalletNoticeWorker(cfg WalletNoticeWorkerConfig) WalletNoticeWorkerConfig {
	def := Default().WalletNoticeWorker
	if cfg.PollInterval <= 0 {
		cfg.PollInterval = def.PollInterval
	}
	if cfg.BatchSize <= 0 {
		cfg.BatchSize = def.BatchSize
	}
	if cfg.LockTTL <= 0 {
		cfg.LockTTL = def.LockTTL
	}
	if cfg.PublishTimeout <= 0 {
		cfg.PublishTimeout = def.PublishTimeout
	}
	if cfg.MaxRetryCount <= 0 {
		cfg.MaxRetryCount = def.MaxRetryCount
	}
	if cfg.InitialBackoff <= 0 {
		cfg.InitialBackoff = def.InitialBackoff
	}
	if cfg.MaxBackoff <= 0 {
		cfg.MaxBackoff = def.MaxBackoff
	}
	return cfg
}
