package config

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

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

// Config 描述 game-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            string `yaml:"mysql_dsn"`
	WalletServiceAddr   string `yaml:"wallet_service_addr"`
	UserServiceAddr     string `yaml:"user_service_addr"`
	ActivityServiceAddr string `yaml:"activity_service_addr"`
	RobotServiceAddr    string `yaml:"robot_service_addr"`
	// RoomServiceAddr 用于把 room-rps 最终败者礼物提交给 Room Cell；game-service 不持有火箭状态。
	RoomServiceAddr  string        `yaml:"room_service_addr"`
	LaunchSessionTTL time.Duration `yaml:"launch_session_ttl"`
	// TencentIM 是房内猜拳向房间群投递 PK 事件的腾讯 IM REST 配置；缺配置时必须显式关闭，不能靠环境变量隐式漂移。
	TencentIM    TencentIMConfig    `yaml:"tencent_im"`
	RocketMQ     RocketMQConfig     `yaml:"rocketmq"`
	OutboxWorker OutboxWorkerConfig `yaml:"outbox_worker"`
	Log          logx.Config        `yaml:"log"`
}

// TencentIMConfig 描述 game-service 发房内 RPS 群自定义消息需要的服务端 IM 配置。
type TencentIMConfig struct {
	// Enabled 控制房内 RPS 是否向腾讯 IM 房间群投递事件；开启后缺少密钥或管理员账号应启动失败。
	Enabled bool `yaml:"enabled"`
	// SDKAppID 是腾讯云 IM 应用 ID，必须和 gateway 签发 UserSig 使用同一个应用。
	SDKAppID int64 `yaml:"sdk_app_id"`
	// SecretKey 只用于服务端签 admin UserSig，不能出现在响应或业务日志里。
	SecretKey string `yaml:"secret_key"`
	// AdminIdentifier 是服务端 REST 发房间 PK 消息的管理员账号。
	AdminIdentifier string `yaml:"admin_identifier"`
	// AdminUserSigTTL 控制 REST 调用签名有效期，默认 24h。
	AdminUserSigTTL time.Duration `yaml:"admin_user_sig_ttl"`
	// Endpoint 是腾讯 IM REST 地域入口，例如 adminapisgp.im.qcloud.com。
	Endpoint string `yaml:"endpoint"`
	// GroupType 是房间群类型；房内 RPS 复用房间群，默认 ChatRoom。
	GroupType string `yaml:"group_type"`
	// RequestTimeout 是单次腾讯 IM REST 调用预算，避免创建 PK 时无限等待外部服务。
	RequestTimeout time.Duration `yaml:"request_timeout"`
}

// RESTConfig 收敛为共享腾讯 IM client 配置，调用方不用重复处理默认超时和群类型。
func (c TencentIMConfig) RESTConfig() tencentim.RESTConfig {
	timeout := c.RequestTimeout
	if timeout <= 0 {
		timeout = 5 * time.Second
	}
	ttl := c.AdminUserSigTTL
	if ttl <= 0 {
		ttl = 24 * time.Hour
	}
	return tencentim.RESTConfig{
		SDKAppID:        c.SDKAppID,
		SecretKey:       c.SecretKey,
		AdminIdentifier: c.AdminIdentifier,
		AdminUserSigTTL: ttl,
		Endpoint:        c.Endpoint,
		GroupType:       c.GroupType,
		HTTPClient:      &http.Client{Timeout: timeout},
	}
}

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"`
	GameOutbox       GameOutboxConfig `yaml:"game_outbox"`
}

type GameOutboxConfig struct {
	Enabled       bool   `yaml:"enabled"`
	Topic         string `yaml:"topic"`
	ProducerGroup string `yaml:"producer_group"`
}

type OutboxWorkerConfig struct {
	Enabled bool `yaml:"enabled"`
	// AppCodes 是可投递租户 allowlist；空值自动发现，非空用于分阶段回放历史积压。
	AppCodes       []string      `yaml:"app_codes"`
	PollInterval   time.Duration `yaml:"poll_interval"`
	BatchSize      int           `yaml:"batch_size"`
	PublishTimeout time.Duration `yaml:"publish_timeout"`
}

// Default 返回本地开发默认配置。
func Default() Config {
	return Config{
		ServiceName:         "game-service",
		NodeID:              "game-local",
		Environment:         "local",
		GRPCAddr:            ":13008",
		HealthHTTPAddr:      ":13108",
		MySQLDSN:            "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_game?parseTime=true&charset=utf8mb4&loc=UTC&multiStatements=true",
		WalletServiceAddr:   "127.0.0.1:13004",
		UserServiceAddr:     "127.0.0.1:13005",
		ActivityServiceAddr: "127.0.0.1:13006",
		RobotServiceAddr:    "127.0.0.1:13011",
		RoomServiceAddr:     "127.0.0.1:13001",
		LaunchSessionTTL:    15 * time.Minute,
		TencentIM: TencentIMConfig{
			AdminIdentifier: "administrator",
			AdminUserSigTTL: 24 * time.Hour,
			Endpoint:        tencentim.DefaultEndpoint,
			GroupType:       tencentim.DefaultGroupType,
			RequestTimeout:  5 * time.Second,
		},
		RocketMQ: defaultRocketMQConfig(),
		OutboxWorker: OutboxWorkerConfig{
			Enabled:        false,
			PollInterval:   time.Second,
			BatchSize:      100,
			PublishTimeout: 3 * time.Second,
		},
		Log: logx.Config{
			Level:           "info",
			Format:          "json",
			MaxPayloadBytes: 2048,
		},
	}
}

// Load 从 YAML 文件读取配置，并填充默认值。
func Load(path string) (Config, error) {
	cfg := Default()
	if path != "" {
		if err := configx.LoadYAML(path, &cfg); err != nil {
			return Config{}, err
		}
	}
	if err := cfg.Normalize(); err != nil {
		return Config{}, err
	}
	return cfg, nil
}

// Normalize 在启动时收敛配置，避免运行期才暴露关键依赖缺失。
func (cfg *Config) Normalize() error {
	cfg.ServiceName = strings.TrimSpace(cfg.ServiceName)
	if cfg.ServiceName == "" {
		cfg.ServiceName = "game-service"
	}
	cfg.NodeID = strings.TrimSpace(cfg.NodeID)
	if cfg.NodeID == "" {
		cfg.NodeID = cfg.ServiceName
	}
	cfg.Environment = strings.TrimSpace(cfg.Environment)
	if cfg.Environment == "" {
		cfg.Environment = "local"
	}
	cfg.GRPCAddr = strings.TrimSpace(cfg.GRPCAddr)
	if cfg.GRPCAddr == "" {
		cfg.GRPCAddr = ":13008"
	}
	cfg.HealthHTTPAddr = strings.TrimSpace(cfg.HealthHTTPAddr)
	if cfg.HealthHTTPAddr == "" {
		cfg.HealthHTTPAddr = ":13108"
	}
	cfg.MySQLDSN = strings.TrimSpace(cfg.MySQLDSN)
	if cfg.MySQLDSN == "" {
		return fmt.Errorf("mysql_dsn is required")
	}
	cfg.WalletServiceAddr = strings.TrimSpace(cfg.WalletServiceAddr)
	if cfg.WalletServiceAddr == "" {
		return fmt.Errorf("wallet_service_addr is required")
	}
	cfg.UserServiceAddr = strings.TrimSpace(cfg.UserServiceAddr)
	if cfg.UserServiceAddr == "" {
		return fmt.Errorf("user_service_addr is required")
	}
	cfg.ActivityServiceAddr = strings.TrimSpace(cfg.ActivityServiceAddr)
	if cfg.ActivityServiceAddr == "" {
		return fmt.Errorf("activity_service_addr is required")
	}
	cfg.RobotServiceAddr = strings.TrimSpace(cfg.RobotServiceAddr)
	if cfg.RobotServiceAddr == "" {
		return fmt.Errorf("robot_service_addr is required")
	}
	cfg.RoomServiceAddr = strings.TrimSpace(cfg.RoomServiceAddr)
	if cfg.RoomServiceAddr == "" {
		return fmt.Errorf("room_service_addr is required")
	}
	if cfg.LaunchSessionTTL <= 0 {
		cfg.LaunchSessionTTL = 15 * time.Minute
	}
	cfg.normalizeTencentIM()
	if cfg.TencentIM.Enabled && (cfg.TencentIM.SDKAppID <= 0 || cfg.TencentIM.SecretKey == "" || cfg.TencentIM.AdminIdentifier == "") {
		return errors.New("tencent_im enabled requires sdk_app_id, secret_key and admin_identifier")
	}
	cfg.RocketMQ = normalizeRocketMQConfig(cfg.RocketMQ)
	if cfg.OutboxWorker.PollInterval <= 0 {
		cfg.OutboxWorker.PollInterval = time.Second
	}
	if cfg.OutboxWorker.BatchSize <= 0 {
		cfg.OutboxWorker.BatchSize = 100
	}
	if cfg.OutboxWorker.PublishTimeout <= 0 {
		cfg.OutboxWorker.PublishTimeout = 3 * time.Second
	}
	cfg.OutboxWorker.AppCodes = outboxpartition.Normalize(cfg.OutboxWorker.AppCodes, false)
	if cfg.OutboxWorker.Enabled && !cfg.RocketMQ.GameOutbox.Enabled {
		return errors.New("outbox_worker requires rocketmq.game_outbox.enabled")
	}
	if cfg.RocketMQ.Enabled {
		if len(cfg.RocketMQ.NameServers) == 0 && cfg.RocketMQ.NameServerDomain == "" {
			return errors.New("rocketmq name_servers or name_server_domain is required")
		}
		if (cfg.RocketMQ.AccessKey == "") != (cfg.RocketMQ.SecretKey == "") {
			return errors.New("rocketmq access_key and secret_key must be configured together")
		}
	}
	return nil
}

func (cfg *Config) normalizeTencentIM() {
	cfg.TencentIM.SecretKey = strings.TrimSpace(cfg.TencentIM.SecretKey)
	cfg.TencentIM.AdminIdentifier = strings.TrimSpace(cfg.TencentIM.AdminIdentifier)
	if cfg.TencentIM.AdminIdentifier == "" {
		cfg.TencentIM.AdminIdentifier = "administrator"
	}
	cfg.TencentIM.Endpoint = strings.TrimSpace(cfg.TencentIM.Endpoint)
	if cfg.TencentIM.Endpoint == "" {
		cfg.TencentIM.Endpoint = tencentim.DefaultEndpoint
	}
	cfg.TencentIM.GroupType = strings.TrimSpace(cfg.TencentIM.GroupType)
	if cfg.TencentIM.GroupType == "" {
		cfg.TencentIM.GroupType = tencentim.DefaultGroupType
	}
	if cfg.TencentIM.AdminUserSigTTL <= 0 {
		cfg.TencentIM.AdminUserSigTTL = 24 * time.Hour
	}
	if cfg.TencentIM.RequestTimeout <= 0 {
		cfg.TencentIM.RequestTimeout = 5 * time.Second
	}
}

func defaultRocketMQConfig() RocketMQConfig {
	return RocketMQConfig{
		Enabled:     false,
		SendTimeout: 3 * time.Second,
		Retry:       2,
		GameOutbox: GameOutboxConfig{
			Enabled:       false,
			Topic:         "hyapp_game_outbox",
			ProducerGroup: "hyapp-game-outbox-producer",
		},
	}
}

func normalizeRocketMQConfig(cfg RocketMQConfig) RocketMQConfig {
	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.GameOutbox.Topic = strings.TrimSpace(cfg.GameOutbox.Topic); cfg.GameOutbox.Topic == "" {
		cfg.GameOutbox.Topic = defaults.GameOutbox.Topic
	}
	if cfg.GameOutbox.ProducerGroup = strings.TrimSpace(cfg.GameOutbox.ProducerGroup); cfg.GameOutbox.ProducerGroup == "" {
		cfg.GameOutbox.ProducerGroup = defaults.GameOutbox.ProducerGroup
	}
	if cfg.GameOutbox.Enabled {
		cfg.Enabled = true
	}
	return cfg
}

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