package config

import (
	"reflect"
	"strings"
	"testing"
	"time"
)

func TestLoadConfigYAML(t *testing.T) {
	cfg, err := Load("../../configs/config.yaml")
	if err != nil {
		t.Fatalf("Load() error = %v", err)
	}

	if cfg.HTTPAddr != ":13100" {
		t.Fatalf("HTTPAddr = %q", cfg.HTTPAddr)
	}
	if !reflect.DeepEqual(cfg.TrustedProxies, []string{"127.0.0.0/8", "::1"}) {
		t.Fatalf("TrustedProxies = %#v", cfg.TrustedProxies)
	}
	if cfg.MySQLDSN != "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_admin?parseTime=true&charset=utf8mb4&loc=UTC" {
		t.Fatalf("MySQLDSN = %q", cfg.MySQLDSN)
	}
	if cfg.UserMySQLDSN != "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_user?parseTime=true&charset=utf8mb4&loc=UTC" {
		t.Fatalf("UserMySQLDSN = %q", cfg.UserMySQLDSN)
	}
	if cfg.AccessTokenTTL != 12*time.Hour {
		t.Fatalf("AccessTokenTTL = %s", cfg.AccessTokenTTL)
	}
	if cfg.RefreshTokenTTL != 7*24*time.Hour {
		t.Fatalf("RefreshTokenTTL = %s", cfg.RefreshTokenTTL)
	}
	if cfg.ExternalAdmin.SessionTTL != 12*time.Hour || cfg.ExternalAdmin.MaxLoginFailures != 5 || cfg.ExternalAdmin.LockDuration != 15*time.Minute {
		t.Fatalf("ExternalAdmin = %#v", cfg.ExternalAdmin)
	}
	if limit := cfg.ExternalAdmin.LoginRateLimit; limit.Window != time.Minute || limit.SystemLimit != 300 || limit.AppLimit != 120 || limit.IPLimit != 30 || limit.IdentityLimit != 10 {
		t.Fatalf("ExternalAdmin.LoginRateLimit = %#v", limit)
	}
	if !cfg.Migrations.AllowChecksumRepair {
		t.Fatalf("Migrations.AllowChecksumRepair = false, want true for local config")
	}
	if !cfg.TencentCOS.Enabled || cfg.TencentCOS.BucketName != "yumi-assets-1420526837" || cfg.TencentCOS.ObjectPrefix != "admin" {
		t.Fatalf("TencentCOS = %#v", cfg.TencentCOS)
	}
}

func TestExternalAdminLoginRateLimitDefaultsAndValidation(t *testing.T) {
	cfg := Default()
	cfg.ExternalAdmin.LoginRateLimit = ExternalAdminLoginRateLimitConfig{}
	cfg.Normalize()
	if limit := cfg.ExternalAdmin.LoginRateLimit; limit.Window != time.Minute || limit.SystemLimit != 300 || limit.AppLimit != 120 || limit.IPLimit != 30 || limit.IdentityLimit != 10 {
		t.Fatalf("normalized login rate limit = %#v", limit)
	}

	cfg.ExternalAdmin.LoginRateLimit.AppLimit = 301
	if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "system_limit") {
		t.Fatalf("Validate() error = %v, want system/app hierarchy error", err)
	}
}

func TestProductionRequiresRedisForExternalLoginRateLimit(t *testing.T) {
	cfg := Default()
	cfg.Environment = "prod"
	cfg.JWTSecret = strings.Repeat("s", 32)
	cfg.MySQLAutoMigrate = false
	cfg.Migrations.AllowChecksumRepair = false
	cfg.Bootstrap.Enabled = false
	cfg.RobotProfileSource.MySQLDSN = "robot:test@tcp(example:3306)/robot?parseTime=true"
	cfg.Redis.Enabled = false
	if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "redis.enabled") {
		t.Fatalf("Validate() error = %v, want production Redis requirement", err)
	}
}

func TestLoadEmptyPathUsesDefault(t *testing.T) {
	cfg, err := Load("")
	if err != nil {
		t.Fatalf("Load() error = %v", err)
	}

	if !reflect.DeepEqual(cfg, Default()) {
		t.Fatalf("Load(\"\") = %#v, want default", cfg)
	}
}

func TestYumiWithdrawalSourceEnvOverride(t *testing.T) {
	t.Setenv("HYAPP_ADMIN_YUMI_WITHDRAWAL_ENABLED", "true")
	t.Setenv("HYAPP_ADMIN_YUMI_WITHDRAWAL_SOURCE_SYSTEM", "chatapp3")
	t.Setenv("HYAPP_ADMIN_YUMI_WITHDRAWAL_SUBMIT_TOKEN", "yumi-submit-token")
	t.Setenv("HYAPP_ADMIN_YUMI_WITHDRAWAL_CALLBACK_URL", "https://yumi.example/console/integration/withdrawal-review/decision")
	t.Setenv("HYAPP_ADMIN_YUMI_WITHDRAWAL_CALLBACK_TOKEN", "yumi-callback-token")

	cfg := Default()
	cfg.Normalize()
	for _, source := range cfg.WithdrawalSources {
		if source.AppCode != "yumi" {
			continue
		}
		if !source.Enabled || source.SourceSystem != "chatapp3" ||
			source.SubmitToken != "yumi-submit-token" ||
			source.CallbackURL != "https://yumi.example/console/integration/withdrawal-review/decision" ||
			source.CallbackToken != "yumi-callback-token" {
			t.Fatalf("Yumi withdrawal source override mismatch: %#v", source)
		}
		return
	}
	t.Fatal("Yumi withdrawal source not found")
}

func TestValidateRejectsInvalidTrustedProxy(t *testing.T) {
	cfg := Default()
	cfg.TrustedProxies = []string{"not-a-proxy-range"}
	if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "trusted_proxies") {
		t.Fatalf("Validate() error = %v, want trusted_proxies error", err)
	}
}

func TestAslanMongoEnvOverride(t *testing.T) {
	t.Setenv("HYAPP_ADMIN_ASLAN_MONGO_URI", "mongodb://mongouser:secret@example:27017/test?authSource=admin")
	t.Setenv("HYAPP_ADMIN_ASLAN_DASHBOARD_MONGO_DATABASE", "test_dashboard")
	t.Setenv("HYAPP_ADMIN_ASLAN_DASHBOARD_LEGACY_MYSQL_DSN", "likei:secret@tcp(example:3306)/likei?parseTime=true&loc=Asia%2FShanghai")
	t.Setenv("HYAPP_ADMIN_ASLAN_DASHBOARD_LEGACY_WALLET_DATABASE", "test_wallet")
	t.Setenv("HYAPP_ADMIN_ASLAN_MONEY_REGION_MONGO_DATABASE", "tarab_region")

	cfg := Default()
	cfg.Normalize()

	var dashboardSource DashboardExternalSourceConfig
	for _, source := range cfg.DashboardExternalSources {
		if source.AppCode == "aslan" {
			dashboardSource = source
			break
		}
	}
	if dashboardSource.MongoURI != "mongodb://mongouser:secret@example:27017/test?authSource=admin" {
		t.Fatalf("dashboard mongo uri mismatch: %q", dashboardSource.MongoURI)
	}
	if dashboardSource.MongoDatabase != "test_dashboard" {
		t.Fatalf("dashboard mongo database mismatch: %q", dashboardSource.MongoDatabase)
	}
	if dashboardSource.LegacyMySQLDSN != "likei:secret@tcp(example:3306)/likei?parseTime=true&loc=Asia%2FShanghai" {
		t.Fatalf("dashboard legacy mysql dsn mismatch: %q", dashboardSource.LegacyMySQLDSN)
	}
	if dashboardSource.LegacyWalletDatabase != "test_wallet" {
		t.Fatalf("dashboard legacy wallet database mismatch: %q", dashboardSource.LegacyWalletDatabase)
	}

	var regionSource MoneyRegionSourceConfig
	for _, source := range cfg.MoneyRegionSources {
		if source.AppCode == "aslan" {
			regionSource = source
			break
		}
	}
	if regionSource.MongoURI != "mongodb://mongouser:secret@example:27017/test?authSource=admin" {
		t.Fatalf("region mongo uri mismatch: %q", regionSource.MongoURI)
	}
	if regionSource.MongoDatabase != "tarab_region" {
		t.Fatalf("region mongo database mismatch: %q", regionSource.MongoDatabase)
	}
}

func TestAslanMongoLegacyMySQLFallbackEnvOverride(t *testing.T) {
	t.Setenv("HYAPP_ADMIN_ASLAN_LEGACY_MYSQL_DSN", "likei:secret@tcp(example:3306)/likei?parseTime=true&loc=Asia%2FShanghai")
	t.Setenv("HYAPP_ADMIN_ASLAN_LEGACY_WALLET_DATABASE", "atyou_wallet")

	cfg := Default()
	cfg.Normalize()

	for _, source := range cfg.DashboardExternalSources {
		if source.AppCode != "aslan" {
			continue
		}
		if source.LegacyMySQLDSN != "likei:secret@tcp(example:3306)/likei?parseTime=true&loc=Asia%2FShanghai" {
			t.Fatalf("dashboard legacy mysql fallback dsn mismatch: %q", source.LegacyMySQLDSN)
		}
		if source.LegacyWalletDatabase != "atyou_wallet" {
			t.Fatalf("dashboard legacy wallet database fallback mismatch: %q", source.LegacyWalletDatabase)
		}
		return
	}
	t.Fatal("aslan dashboard source not found")
}

func TestTencentExampleRequiresRuntimeLegacyMySQLDSN(t *testing.T) {
	t.Setenv("HYAPP_ADMIN_ASLAN_DASHBOARD_LEGACY_MYSQL_DSN", "")
	t.Setenv("HYAPP_ADMIN_ASLAN_LEGACY_MYSQL_DSN", "")
	// 腾讯云示例默认开启 Yumi 提现来源；用例只验证 legacy MySQL 必须运行时注入，因此同时提供不落盘的测试提现凭据。
	t.Setenv("HYAPP_ADMIN_YUMI_WITHDRAWAL_SUBMIT_TOKEN", "test-yumi-submit-token")
	t.Setenv("HYAPP_ADMIN_YUMI_WITHDRAWAL_CALLBACK_URL", "https://yumi.example/console/integration/withdrawal-review/decision")
	t.Setenv("HYAPP_ADMIN_YUMI_WITHDRAWAL_CALLBACK_TOKEN", "test-yumi-callback-token")

	_, err := Load("../../configs/config.tencent.example.yaml")
	if err == nil || !strings.Contains(err.Error(), "legacy_mysql_dsn must be injected by environment") {
		t.Fatalf("Load() error = %v, want legacy mysql placeholder rejection", err)
	}

	t.Setenv("HYAPP_ADMIN_ASLAN_DASHBOARD_LEGACY_MYSQL_DSN", "aslan:runtime-secret@tcp(example:3306)/atyou?parseTime=true&loc=Asia%2FShanghai")
	cfg, err := Load("../../configs/config.tencent.example.yaml")
	if err != nil {
		t.Fatalf("Load() with runtime legacy dsn error = %v", err)
	}
	for _, source := range cfg.DashboardExternalSources {
		if source.AppCode != "aslan" {
			continue
		}
		if source.LegacyMySQLDSN != "aslan:runtime-secret@tcp(example:3306)/atyou?parseTime=true&loc=Asia%2FShanghai" {
			t.Fatalf("dashboard legacy mysql dsn mismatch: %q", source.LegacyMySQLDSN)
		}
		if source.LegacyWalletDatabase != "atyou_wallet" {
			t.Fatalf("dashboard legacy wallet database mismatch: %q", source.LegacyWalletDatabase)
		}
		return
	}
	t.Fatal("aslan dashboard source not found")
}

func TestAslanMongoLegacyWalletDatabaseFollowsMongoDatabaseEnv(t *testing.T) {
	t.Setenv("HYAPP_ADMIN_ASLAN_DASHBOARD_MONGO_DATABASE", "atyou")

	cfg := Default()
	cfg.Normalize()

	for _, source := range cfg.DashboardExternalSources {
		if source.AppCode != "aslan" {
			continue
		}
		if source.MongoDatabase != "atyou" {
			t.Fatalf("dashboard mongo database mismatch: %q", source.MongoDatabase)
		}
		if source.LegacyWalletDatabase != "atyou_wallet" {
			t.Fatalf("dashboard legacy wallet database should follow mongo database, got %q", source.LegacyWalletDatabase)
		}
		return
	}
	t.Fatal("aslan dashboard source not found")
}

func TestAslanMongoRequiresLegacyMySQLDSNWhenEnabled(t *testing.T) {
	cfg := Default()
	for index := range cfg.DashboardExternalSources {
		source := &cfg.DashboardExternalSources[index]
		if source.AppCode != "aslan" {
			continue
		}
		source.Enabled = true
		source.MongoURI = "mongodb://example:27017/test"
		source.MongoDatabase = "atyou"
		source.LegacyMySQLDSN = ""
		break
	}
	cfg.Normalize()

	err := cfg.Validate()
	if err == nil || !strings.Contains(err.Error(), "legacy_mysql_dsn is required") {
		t.Fatalf("Validate() error = %v, want legacy mysql requirement", err)
	}
}

func TestAslanMongoRejectsPlaceholderLegacyMySQLDSN(t *testing.T) {
	cfg := Default()
	for index := range cfg.DashboardExternalSources {
		source := &cfg.DashboardExternalSources[index]
		if source.AppCode != "aslan" {
			continue
		}
		source.Enabled = true
		source.MongoURI = "mongodb://example:27017/test"
		source.MongoDatabase = "atyou"
		source.LegacyMySQLDSN = "aslan:REPLACE_ME@tcp(example:3306)/atyou?parseTime=true&loc=Asia%2FShanghai"
		source.LegacyWalletDatabase = "atyou_wallet"
		break
	}
	cfg.Normalize()

	err := cfg.Validate()
	if err == nil || !strings.Contains(err.Error(), "not left as a placeholder") {
		t.Fatalf("Validate() error = %v, want placeholder rejection", err)
	}
}
