package config

import (
	"flag"
	"fmt"
	"net"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/joho/godotenv"
)

const (
	DefaultAppName    = "QingYun Cloud Go API"
	DefaultAppVersion = "0.1.0-go-skeleton"
)

// Bootstrap contains values loaded before the database-backed settings are available.
type Bootstrap struct {
	AppName         string
	AppVersion      string
	HTTPAddr        string
	ShutdownTimeout time.Duration

	DBEnabled bool
	DBDSN     string

	RedisEnabled  bool
	RedisAddr     string
	RedisPassword string
	RedisDB       int

	JWTSecretKey              string
	JWTAlgorithm              string
	AccessTokenExpireMinutes  int
	RefreshTokenExpireDays    int
	GeetestCaptchaID          string
	GeetestCaptchaKey         string
	GeetestAPIServer          string
	GeetestFailOpen           bool
	SMTPHost                  string
	SMTPPort                  int
	SMTPUser                  string
	SMTPPassword              string
	SMTPFromEmail             string
	SMTPFromName              string
	SMTPInsecureSkipTLSVerify bool
	PluginDirs                []string
	PluginCallTimeout         time.Duration
	LicenseServerURL          string
	LicenseRequestTimeout     time.Duration
	LicenseHeartbeatInterval  time.Duration
	LicenseKeyFile            string
	LicenseRequirePinnedKey   bool
	LicenseMasterKeyHex       string
	LicenseAuthURLs           []string
	LicenseProbeURLs          []string

	FrontendURL string
	BackendURL  string

	ProvisionWorkerInterval  time.Duration
	ProvisionWorkerBatchSize int
	ProvisionMaxAttempts     int

	SettlementWorkerInterval  time.Duration
	SettlementWorkerBatchSize int

	UpstreamActionWorkerInterval  time.Duration
	UpstreamActionWorkerBatchSize int
}

func LoadBootstrap(args []string) (Bootstrap, error) {
	_ = godotenv.Load(".env")

	cfg := Bootstrap{
		AppName:                  DefaultAppName,
		AppVersion:               DefaultAppVersion,
		HTTPAddr:                 ":8080",
		ShutdownTimeout:          10 * time.Second,
		DBEnabled:                true,
		DBDSN:                    "root:password@tcp(127.0.0.1:3306)/qycw?charset=utf8mb4&parseTime=True&loc=Local",
		RedisEnabled:             true,
		RedisAddr:                "127.0.0.1:6379",
		RedisDB:                  10,
		JWTSecretKey:             "change-me-to-a-random-64-byte-string",
		JWTAlgorithm:             "HS256",
		AccessTokenExpireMinutes: 30,
		RefreshTokenExpireDays:   7,
		GeetestAPIServer:         "http://gcaptcha4.geetest.com",
		SMTPPort:                 465,
		PluginDirs:               []string{"data/plugins", "plugins"},
		PluginCallTimeout:        30 * time.Second,
		LicenseServerURL:         "https://sy.auth.server.cloudqing-net.cn",
		LicenseAuthURLs: []string{
			"https://sy.auth.server.cloudqing-net.cn",
			"https://hk.auth.server.cloudqing-net.cn",
			"https://nb.auth.server.cloudqing-net.cn",
		},
		LicenseRequestTimeout:         5 * time.Second,
		LicenseHeartbeatInterval:      5 * time.Minute,
		LicenseKeyFile:                "data/license/master.key",
		LicenseRequirePinnedKey:       true,
		ProvisionWorkerInterval:       30 * time.Second,
		ProvisionWorkerBatchSize:      20,
		ProvisionMaxAttempts:          3,
		SettlementWorkerInterval:      60 * time.Second,
		SettlementWorkerBatchSize:     100,
		UpstreamActionWorkerInterval:  45 * time.Second,
		UpstreamActionWorkerBatchSize: 100,
	}
	cfg.applyEnv()

	fs := flag.NewFlagSet("go-server", flag.ContinueOnError)
	fs.StringVar(&cfg.AppName, "app.name", cfg.AppName, "application name")
	fs.StringVar(&cfg.AppVersion, "app.version", cfg.AppVersion, "application version")
	fs.StringVar(&cfg.HTTPAddr, "http.addr", cfg.HTTPAddr, "HTTP listen address")
	fs.DurationVar(&cfg.ShutdownTimeout, "shutdown.timeout", cfg.ShutdownTimeout, "graceful shutdown timeout")
	fs.BoolVar(&cfg.DBEnabled, "db.enabled", cfg.DBEnabled, "enable MySQL connection")
	fs.StringVar(&cfg.DBDSN, "db.dsn", cfg.DBDSN, "MySQL DSN")
	fs.BoolVar(&cfg.RedisEnabled, "redis.enabled", cfg.RedisEnabled, "enable Redis connection")
	fs.StringVar(&cfg.RedisAddr, "redis.addr", cfg.RedisAddr, "Redis address")
	fs.StringVar(&cfg.RedisPassword, "redis.password", cfg.RedisPassword, "Redis password")
	fs.IntVar(&cfg.RedisDB, "redis.db", cfg.RedisDB, "Redis database number")
	fs.StringVar(&cfg.JWTSecretKey, "jwt.secret", cfg.JWTSecretKey, "JWT HMAC secret")
	fs.IntVar(&cfg.AccessTokenExpireMinutes, "jwt.access-minutes", cfg.AccessTokenExpireMinutes, "access token lifetime in minutes")
	fs.IntVar(&cfg.RefreshTokenExpireDays, "jwt.refresh-days", cfg.RefreshTokenExpireDays, "refresh token lifetime in days")
	fs.Func("plugin.dirs", "comma-separated plugin install directories", func(value string) error {
		cfg.PluginDirs = splitCSV(value)
		return nil
	})
	fs.DurationVar(&cfg.PluginCallTimeout, "plugin.timeout", cfg.PluginCallTimeout, "plugin call timeout")
	fs.StringVar(&cfg.LicenseServerURL, "license.server-url", cfg.LicenseServerURL, "license service base URL")
	fs.DurationVar(&cfg.LicenseRequestTimeout, "license.timeout", cfg.LicenseRequestTimeout, "license service request timeout")
	fs.DurationVar(&cfg.LicenseHeartbeatInterval, "license.heartbeat-interval", cfg.LicenseHeartbeatInterval, "license heartbeat interval")
	fs.StringVar(&cfg.LicenseKeyFile, "license.key-file", cfg.LicenseKeyFile, "local license encryption key file")
	fs.BoolVar(&cfg.LicenseRequirePinnedKey, "license.require-pinned-key", cfg.LicenseRequirePinnedKey, "require build-time pinned license public key")
	fs.DurationVar(&cfg.ProvisionWorkerInterval, "provision.interval", cfg.ProvisionWorkerInterval, "provision worker scan interval")
	fs.IntVar(&cfg.ProvisionWorkerBatchSize, "provision.batch-size", cfg.ProvisionWorkerBatchSize, "provision worker batch size")
	fs.IntVar(&cfg.ProvisionMaxAttempts, "provision.max-attempts", cfg.ProvisionMaxAttempts, "maximum provision attempts before marking provision_failed")
	fs.DurationVar(&cfg.SettlementWorkerInterval, "settlement.interval", cfg.SettlementWorkerInterval, "settlement release worker scan interval")
	fs.IntVar(&cfg.SettlementWorkerBatchSize, "settlement.batch-size", cfg.SettlementWorkerBatchSize, "settlement release worker batch size")
	fs.DurationVar(&cfg.UpstreamActionWorkerInterval, "upstream-actions.interval", cfg.UpstreamActionWorkerInterval, "upstream action sync worker scan interval")
	fs.IntVar(&cfg.UpstreamActionWorkerBatchSize, "upstream-actions.batch-size", cfg.UpstreamActionWorkerBatchSize, "upstream action sync worker batch size")

	if err := fs.Parse(args); err != nil {
		return Bootstrap{}, err
	}
	if cfg.HTTPAddr == "" {
		return Bootstrap{}, fmt.Errorf("http.addr cannot be empty")
	}
	if cfg.DBEnabled && cfg.DBDSN == "" {
		return Bootstrap{}, fmt.Errorf("db.dsn cannot be empty when db.enabled=true")
	}
	if cfg.RedisEnabled && cfg.RedisAddr == "" {
		return Bootstrap{}, fmt.Errorf("redis.addr cannot be empty when redis.enabled=true")
	}
	if cfg.JWTAlgorithm != "HS256" {
		return Bootstrap{}, fmt.Errorf("only JWT_ALGORITHM=HS256 is currently supported")
	}
	if cfg.PluginCallTimeout <= 0 {
		return Bootstrap{}, fmt.Errorf("plugin.timeout must be positive")
	}
	if strings.TrimSpace(cfg.LicenseServerURL) == "" {
		return Bootstrap{}, fmt.Errorf("license.server-url cannot be empty")
	}
	if cfg.LicenseRequestTimeout <= 0 {
		return Bootstrap{}, fmt.Errorf("license.timeout must be positive")
	}
	if cfg.LicenseHeartbeatInterval <= 0 {
		return Bootstrap{}, fmt.Errorf("license.heartbeat-interval must be positive")
	}
	if strings.TrimSpace(cfg.LicenseKeyFile) == "" {
		return Bootstrap{}, fmt.Errorf("license.key-file cannot be empty")
	}
	if cfg.ProvisionWorkerInterval <= 0 {
		return Bootstrap{}, fmt.Errorf("provision.interval must be positive")
	}
	if cfg.ProvisionWorkerBatchSize <= 0 {
		return Bootstrap{}, fmt.Errorf("provision.batch-size must be positive")
	}
	if cfg.ProvisionMaxAttempts <= 0 {
		return Bootstrap{}, fmt.Errorf("provision.max-attempts must be positive")
	}
	if cfg.SettlementWorkerInterval <= 0 {
		return Bootstrap{}, fmt.Errorf("settlement.interval must be positive")
	}
	if cfg.SettlementWorkerBatchSize <= 0 {
		return Bootstrap{}, fmt.Errorf("settlement.batch-size must be positive")
	}
	if cfg.UpstreamActionWorkerInterval <= 0 {
		return Bootstrap{}, fmt.Errorf("upstream-actions.interval must be positive")
	}
	if cfg.UpstreamActionWorkerBatchSize <= 0 {
		return Bootstrap{}, fmt.Errorf("upstream-actions.batch-size must be positive")
	}
	return cfg, nil
}

func (cfg *Bootstrap) applyEnv() {
	cfg.AppName = envString("APP_NAME", cfg.AppName)
	cfg.AppVersion = envString("APP_VERSION", cfg.AppVersion)
	cfg.HTTPAddr = envString("GO_HTTP_ADDR", envString("HTTP_ADDR", cfg.HTTPAddr))
	cfg.ShutdownTimeout = envDuration("GO_SHUTDOWN_TIMEOUT", cfg.ShutdownTimeout)

	cfg.DBEnabled = envBool("GO_DB_ENABLED", envBool("DB_ENABLED", cfg.DBEnabled))
	cfg.DBDSN = envString("GO_DB_DSN", envString("DB_DSN", cfg.DBDSN))
	if cfg.DBDSN == "" || cfg.DBDSN == "root:password@tcp(127.0.0.1:3306)/qycw?charset=utf8mb4&parseTime=True&loc=Local" {
		cfg.DBDSN = mysqlDSNFromEnv()
	}

	cfg.RedisEnabled = envBool("GO_REDIS_ENABLED", envBool("REDIS_ENABLED", cfg.RedisEnabled))
	cfg.RedisAddr = envString("GO_REDIS_ADDR", redisAddrFromEnv(cfg.RedisAddr))
	cfg.RedisPassword = envString("REDIS_PASSWORD", cfg.RedisPassword)
	cfg.RedisDB = envInt("REDIS_DB", cfg.RedisDB)

	cfg.JWTSecretKey = envString("JWT_SECRET_KEY", cfg.JWTSecretKey)
	cfg.JWTAlgorithm = envString("JWT_ALGORITHM", cfg.JWTAlgorithm)
	cfg.AccessTokenExpireMinutes = envInt("ACCESS_TOKEN_EXPIRE_MINUTES", cfg.AccessTokenExpireMinutes)
	cfg.RefreshTokenExpireDays = envInt("REFRESH_TOKEN_EXPIRE_DAYS", cfg.RefreshTokenExpireDays)
	cfg.GeetestCaptchaID = envString("GEETEST_CAPTCHA_ID", cfg.GeetestCaptchaID)
	cfg.GeetestCaptchaKey = envString("GEETEST_CAPTCHA_KEY", cfg.GeetestCaptchaKey)
	cfg.GeetestAPIServer = envString("GEETEST_API_SERVER", cfg.GeetestAPIServer)
	cfg.GeetestFailOpen = envBool("GEETEST_FAIL_OPEN", cfg.GeetestFailOpen)
	cfg.SMTPHost = envString("SMTP_HOST", cfg.SMTPHost)
	cfg.SMTPPort = envInt("SMTP_PORT", cfg.SMTPPort)
	cfg.SMTPUser = envString("SMTP_USER", cfg.SMTPUser)
	cfg.SMTPPassword = envString("SMTP_PASSWORD", cfg.SMTPPassword)
	cfg.SMTPFromEmail = envString("SMTP_FROM_EMAIL", cfg.SMTPFromEmail)
	cfg.SMTPFromName = envString("SMTP_FROM_NAME", cfg.SMTPFromName)
	cfg.SMTPInsecureSkipTLSVerify = envBool("SMTP_INSECURE_SKIP_TLS_VERIFY", cfg.SMTPInsecureSkipTLSVerify)
	if value := envString("GO_PLUGIN_DIRS", ""); value != "" {
		cfg.PluginDirs = splitCSV(value)
	}
	cfg.PluginCallTimeout = envDuration("GO_PLUGIN_TIMEOUT", cfg.PluginCallTimeout)
	cfg.LicenseServerURL = envString("GO_LICENSE_SERVER_URL", cfg.LicenseServerURL)
	cfg.LicenseRequestTimeout = envDuration("GO_LICENSE_TIMEOUT", cfg.LicenseRequestTimeout)
	cfg.LicenseHeartbeatInterval = envDuration("GO_LICENSE_HEARTBEAT_INTERVAL", cfg.LicenseHeartbeatInterval)
	cfg.LicenseKeyFile = envString("GO_LICENSE_KEY_FILE", cfg.LicenseKeyFile)
	cfg.LicenseRequirePinnedKey = envBool("GO_LICENSE_REQUIRE_PINNED_KEY", cfg.LicenseRequirePinnedKey)
	cfg.LicenseMasterKeyHex = envString("GO_LICENSE_MASTER_KEY_HEX", cfg.LicenseMasterKeyHex)
	if value := envString("GO_LICENSE_AUTH_URLS", ""); value != "" {
		cfg.LicenseAuthURLs = splitCSV(value)
	}
	if value := envString("GO_LICENSE_PROBE_URLS", ""); value != "" {
		cfg.LicenseProbeURLs = splitCSV(value)
	}
	cfg.FrontendURL = envString("FRONTEND_URL", "http://localhost:5173")
	cfg.BackendURL = envString("PUBLIC_BACKEND_URL", "")
	cfg.ProvisionWorkerInterval = envDuration("GO_PROVISION_WORKER_INTERVAL", cfg.ProvisionWorkerInterval)
	cfg.ProvisionWorkerBatchSize = envInt("GO_PROVISION_WORKER_BATCH_SIZE", cfg.ProvisionWorkerBatchSize)
	cfg.ProvisionMaxAttempts = envInt("GO_PROVISION_MAX_ATTEMPTS", cfg.ProvisionMaxAttempts)
	cfg.SettlementWorkerInterval = envDuration("GO_SETTLEMENT_WORKER_INTERVAL", cfg.SettlementWorkerInterval)
	cfg.SettlementWorkerBatchSize = envInt("GO_SETTLEMENT_WORKER_BATCH_SIZE", cfg.SettlementWorkerBatchSize)
	cfg.UpstreamActionWorkerInterval = envDuration("GO_UPSTREAM_ACTION_WORKER_INTERVAL", cfg.UpstreamActionWorkerInterval)
	cfg.UpstreamActionWorkerBatchSize = envInt("GO_UPSTREAM_ACTION_WORKER_BATCH_SIZE", cfg.UpstreamActionWorkerBatchSize)
}

func mysqlDSNFromEnv() string {
	host := envString("DB_HOST", "127.0.0.1")
	port := envString("DB_PORT", "3306")
	user := envString("DB_USER", "root")
	password := os.Getenv("DB_PASSWORD")
	name := envString("DB_NAME", "qycw")
	return fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, password, net.JoinHostPort(host, port), name)
}

func redisAddrFromEnv(fallback string) string {
	host := os.Getenv("REDIS_HOST")
	port := os.Getenv("REDIS_PORT")
	if strings.TrimSpace(host) == "" && strings.TrimSpace(port) == "" {
		return fallback
	}
	if strings.TrimSpace(host) == "" {
		host = "127.0.0.1"
	}
	if strings.TrimSpace(port) == "" {
		port = "6379"
	}
	return net.JoinHostPort(host, port)
}

func envString(key string, fallback string) string {
	value, ok := os.LookupEnv(key)
	if !ok {
		return fallback
	}
	value = strings.TrimSpace(value)
	if value == "" {
		return fallback
	}
	return value
}

func envBool(key string, fallback bool) bool {
	value, ok := os.LookupEnv(key)
	if !ok {
		return fallback
	}
	parsed, err := strconv.ParseBool(strings.TrimSpace(value))
	if err != nil {
		return fallback
	}
	return parsed
}

func envInt(key string, fallback int) int {
	value, ok := os.LookupEnv(key)
	if !ok {
		return fallback
	}
	parsed, err := strconv.Atoi(strings.TrimSpace(value))
	if err != nil {
		return fallback
	}
	return parsed
}

func envDuration(key string, fallback time.Duration) time.Duration {
	value, ok := os.LookupEnv(key)
	if !ok {
		return fallback
	}
	parsed, err := time.ParseDuration(strings.TrimSpace(value))
	if err != nil {
		return fallback
	}
	return parsed
}

func splitCSV(value string) []string {
	parts := strings.Split(value, ",")
	out := make([]string, 0, len(parts))
	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part != "" {
			out = append(out, part)
		}
	}
	return out
}
