package config

import (
	"encoding/json"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
)

const DefaultListen = ":33550"

var Version = "0.1.0"

type Config struct {
	BaseDir                string            `json:"base_dir"`
	ConfigPath             string            `json:"-"`
	Initialized            bool              `json:"initialized"`
	Listen                 string            `json:"listen"`
	BaseURL                string            `json:"base_url"`
	StorageDir             string            `json:"storage_dir"`
	DataDir                string            `json:"data_dir"`
	UpdatePublicDir        string            `json:"update_public_dir"`
	UpdateNoticeDir        string            `json:"update_notice_dir"`
	DownloadsDir           string            `json:"downloads_dir"`
	AdminWebDir            string            `json:"admin_web_dir"`
	PortalWebDir           string            `json:"portal_web_dir"`
	SetupWebDir            string            `json:"setup_web_dir"`
	LegacyUpdateDir        string            `json:"legacy_update_dir"`
	LegacyFeedbackDir      string            `json:"legacy_feedback_dir"`
	LegacyUpdateNoticeDir  string            `json:"legacy_update_notice_dir"`
	ClientSignatureKey     string            `json:"client_signature_key"`
	PackageEncryptionKey   string            `json:"package_encryption_key"`
	TimestampWindowSeconds int64             `json:"timestamp_window_seconds"`
	MaxRequestBytes        int64             `json:"max_request_bytes"`
	MaxPackageBytes        int64             `json:"max_package_bytes"`
	Database               DatabaseConfig    `json:"database"`
	Mail                   MailConfig        `json:"mail"`
	Branding               BrandingConfig    `json:"branding"`
	UploadGuard            UploadGuardConfig `json:"upload_guard"`
	SourceCheckSeconds     int               `json:"source_check_seconds"`
}

type DatabaseConfig struct {
	Provider               string `json:"provider"`
	SQLitePath             string `json:"sqlite_path"`
	MySQLDSN               string `json:"mysql_dsn"`
	MySQLHost              string `json:"mysql_host"`
	MySQLPort              int    `json:"mysql_port"`
	MySQLDatabase          string `json:"mysql_database"`
	MySQLUser              string `json:"mysql_user"`
	MySQLPassword          string `json:"mysql_password"`
	FailoverEnabled        bool   `json:"failover_enabled"`
	HotSyncEnabled         bool   `json:"hot_sync_enabled"`
	HealthIntervalSec      int    `json:"health_interval_sec"`
	MaxOpenConns           int    `json:"max_open_conns"`
	MaxIdleConns           int    `json:"max_idle_conns"`
	ConnMaxLifetimeSeconds int    `json:"conn_max_lifetime_seconds"`
}

type MailConfig struct {
	Host             string `json:"host"`
	Port             int    `json:"port"`
	Secure           string `json:"secure"`
	Username         string `json:"username"`
	Password         string `json:"password"`
	FromAddress      string `json:"from_address"`
	FromName         string `json:"from_name"`
	DeveloperAddress string `json:"developer_address"`
	TimeoutSeconds   int    `json:"timeout_seconds"`
}

type BrandingConfig struct {
	SiteIconURL        string `json:"site_icon_url"`
	DeveloperAvatarURL string `json:"developer_avatar_url"`
	DeveloperName      string `json:"developer_name"`
	FeedbackEmail      string `json:"feedback_email"`
}

type UploadGuardConfig struct {
	MaxZipFiles             int     `json:"max_zip_files"`
	MaxDecompressedBytes    int64   `json:"max_decompressed_bytes"`
	MaxSingleFileBytes      int64   `json:"max_single_file_bytes"`
	MaxCompressionRatio     float64 `json:"max_compression_ratio"`
	MaxReadableTextBytes    int64   `json:"max_readable_text_bytes"`
	AllowUnexpectedZipFiles bool    `json:"allow_unexpected_zip_files"`
}

func Load() (*Config, error) {
	root, err := ResolveBaseDir()
	if err != nil {
		return nil, err
	}
	cfg := defaults(root)
	path := firstNonEmpty(os.Getenv("YMHUT_UNIFIED_CONFIG"), filepath.Join(root, "config.json"))
	var rawConfig []byte
	loaded := false
	if data, err := os.ReadFile(path); err == nil {
		if err := json.Unmarshal(data, cfg); err != nil {
			return nil, err
		}
		cfg.Initialized = true
		rawConfig = data
		loaded = true
	}
	cfg.BaseDir = root
	cfg.ConfigPath = path
	if loaded {
		sanitizeNonPortablePaths(cfg)
	}
	applyEnv(cfg)
	normalize(root, cfg)
	if loaded && shouldRewriteRelativeConfig(rawConfig) {
		if err := Save(cfg); err != nil {
			return nil, err
		}
	}
	return cfg, nil
}

func defaults(root string) *Config {
	return &Config{
		BaseDir:                root,
		ConfigPath:             filepath.Join(root, "config.json"),
		Initialized:            false,
		Listen:                 DefaultListen,
		BaseURL:                "https://update.ymhut.cn",
		StorageDir:             filepath.Join(root, "storage"),
		DataDir:                filepath.Join(root, "data"),
		UpdatePublicDir:        filepath.Join(root, "data", "update", "public"),
		UpdateNoticeDir:        filepath.Join(root, "data", "update-notice"),
		DownloadsDir:           filepath.Join(root, "data", "update", "public", "downloads"),
		AdminWebDir:            filepath.Join(root, "web", "admin", "dist"),
		PortalWebDir:           filepath.Join(root, "web", "portal", "dist"),
		SetupWebDir:            filepath.Join(root, "web", "setup", "dist"),
		LegacyUpdateDir:        filepath.Clean(filepath.Join(root, "..", "update")),
		LegacyFeedbackDir:      filepath.Clean(filepath.Join(root, "..", "feedback-mailer")),
		LegacyUpdateNoticeDir:  filepath.Clean(filepath.Join(root, "..", "..", "update-notice")),
		ClientSignatureKey:     "ymhut-box-feedback-client-v1",
		PackageEncryptionKey:   "ymhut-box-feedback-package-v1",
		TimestampWindowSeconds: 600,
		MaxRequestBytes:        12 * 1024 * 1024,
		MaxPackageBytes:        10 * 1024 * 1024,
		SourceCheckSeconds:     300,
		Database: DatabaseConfig{
			Provider:               "sqlite",
			SQLitePath:             filepath.Join(root, "storage", "unified.sqlite"),
			MySQLHost:              "127.0.0.1",
			MySQLPort:              3306,
			FailoverEnabled:        true,
			HotSyncEnabled:         true,
			HealthIntervalSec:      30,
			MaxOpenConns:           10,
			MaxIdleConns:           4,
			ConnMaxLifetimeSeconds: 300,
		},
		Mail: MailConfig{
			Port:             465,
			Secure:           "ssl",
			FromName:         "YMhut Box Feedback",
			DeveloperAddress: "support@ymhut.cn",
			TimeoutSeconds:   20,
		},
		Branding: BrandingConfig{
			SiteIconURL:        "https://img.ymhut.cn/file/1782108850041_icon.webp",
			DeveloperAvatarURL: "https://img.ymhut.cn/file/1782108780690_b_3db45f3787f19192c8de8e06bc0987ef.webp",
			DeveloperName:      "YMhut",
			FeedbackEmail:      "support@ymhut.cn",
		},
		UploadGuard: UploadGuardConfig{
			MaxZipFiles:             80,
			MaxDecompressedBytes:    30 * 1024 * 1024,
			MaxSingleFileBytes:      8 * 1024 * 1024,
			MaxCompressionRatio:     120,
			MaxReadableTextBytes:    256 * 1024,
			AllowUnexpectedZipFiles: true,
		},
	}
}

func applyEnv(cfg *Config) {
	if value := os.Getenv("YMHUT_BASE_DIR"); value != "" {
		cfg.BaseDir = value
	}
	if value := os.Getenv("PORT"); value != "" {
		cfg.Listen = ":" + value
	}
	if value := os.Getenv("YMHUT_LISTEN"); value != "" {
		cfg.Listen = value
	}
	if value := os.Getenv("YMHUT_BASE_URL"); value != "" {
		cfg.BaseURL = value
	}
	if value := os.Getenv("YMHUT_STORAGE_DIR"); value != "" {
		cfg.StorageDir = value
	}
	if value := os.Getenv("YMHUT_DATA_DIR"); value != "" {
		cfg.DataDir = value
	}
	if value := os.Getenv("YMHUT_UPDATE_PUBLIC_DIR"); value != "" {
		cfg.UpdatePublicDir = value
	}
	if value := os.Getenv("YMHUT_UPDATE_NOTICE_DIR"); value != "" {
		cfg.UpdateNoticeDir = value
	}
	if value := os.Getenv("YMHUT_DOWNLOADS_DIR"); value != "" {
		cfg.DownloadsDir = value
	}
	if value := os.Getenv("YMHUT_LEGACY_UPDATE_DIR"); value != "" {
		cfg.LegacyUpdateDir = value
	}
	if value := os.Getenv("YMHUT_LEGACY_FEEDBACK_DIR"); value != "" {
		cfg.LegacyFeedbackDir = value
	}
	if value := os.Getenv("YMHUT_LEGACY_UPDATE_NOTICE_DIR"); value != "" {
		cfg.LegacyUpdateNoticeDir = value
	}
	if value := os.Getenv("YMHUT_DB_PROVIDER"); value != "" {
		cfg.Database.Provider = value
	}
	if value := os.Getenv("YMHUT_SQLITE_PATH"); value != "" {
		cfg.Database.SQLitePath = value
	}
	if value := os.Getenv("YMHUT_MYSQL_DSN"); value != "" {
		cfg.Database.MySQLDSN = value
	}
	if value := os.Getenv("YMHUT_MYSQL_HOST"); value != "" {
		cfg.Database.MySQLHost = value
	}
	if value := os.Getenv("YMHUT_MYSQL_PORT"); value != "" {
		if parsed, err := strconv.Atoi(value); err == nil {
			cfg.Database.MySQLPort = parsed
		}
	}
	if value := os.Getenv("YMHUT_MYSQL_DATABASE"); value != "" {
		cfg.Database.MySQLDatabase = value
	}
	if value := os.Getenv("YMHUT_MYSQL_USER"); value != "" {
		cfg.Database.MySQLUser = value
	}
	if value := os.Getenv("YMHUT_MYSQL_PASSWORD"); value != "" {
		cfg.Database.MySQLPassword = value
	}
	if value := os.Getenv("YMHUT_MAIL_HOST"); value != "" {
		cfg.Mail.Host = value
	}
	if value := os.Getenv("YMHUT_MAIL_PORT"); value != "" {
		if parsed, err := strconv.Atoi(value); err == nil {
			cfg.Mail.Port = parsed
		}
	}
	if value := os.Getenv("YMHUT_MAIL_SECURE"); value != "" {
		cfg.Mail.Secure = value
	}
	if value := os.Getenv("YMHUT_MAIL_USERNAME"); value != "" {
		cfg.Mail.Username = value
	}
	if value := os.Getenv("YMHUT_MAIL_PASSWORD"); value != "" {
		cfg.Mail.Password = value
	}
	if value := os.Getenv("YMHUT_MAIL_FROM_ADDRESS"); value != "" {
		cfg.Mail.FromAddress = value
	}
	if value := os.Getenv("YMHUT_MAIL_FROM_NAME"); value != "" {
		cfg.Mail.FromName = value
	}
	if value := os.Getenv("YMHUT_MAIL_DEVELOPER_ADDRESS"); value != "" {
		cfg.Mail.DeveloperAddress = value
	}
	if value := os.Getenv("YMHUT_MAIL_TIMEOUT_SECONDS"); value != "" {
		if parsed, err := strconv.Atoi(value); err == nil {
			cfg.Mail.TimeoutSeconds = parsed
		}
	}
	if value := os.Getenv("YMHUT_BRAND_ICON_URL"); value != "" {
		cfg.Branding.SiteIconURL = value
	}
	if value := os.Getenv("YMHUT_BRAND_DEVELOPER_AVATAR_URL"); value != "" {
		cfg.Branding.DeveloperAvatarURL = value
	}
	if value := os.Getenv("YMHUT_BRAND_DEVELOPER_NAME"); value != "" {
		cfg.Branding.DeveloperName = value
	}
	if value := os.Getenv("YMHUT_BRAND_FEEDBACK_EMAIL"); value != "" {
		cfg.Branding.FeedbackEmail = value
	}
	if value := os.Getenv("YMHUT_CLIENT_SIGNATURE_KEY"); value != "" {
		cfg.ClientSignatureKey = value
	}
	if value := os.Getenv("YMHUT_PACKAGE_ENCRYPTION_KEY"); value != "" {
		cfg.PackageEncryptionKey = value
	}
	if value := os.Getenv("YMHUT_TIMESTAMP_WINDOW_SECONDS"); value != "" {
		if parsed, err := strconv.ParseInt(value, 10, 64); err == nil {
			cfg.TimestampWindowSeconds = parsed
		}
	}
	if value := os.Getenv("YMHUT_MAX_REQUEST_BYTES"); value != "" {
		if parsed, err := strconv.ParseInt(value, 10, 64); err == nil {
			cfg.MaxRequestBytes = parsed
		}
	}
	if value := os.Getenv("YMHUT_MAX_PACKAGE_BYTES"); value != "" {
		if parsed, err := strconv.ParseInt(value, 10, 64); err == nil {
			cfg.MaxPackageBytes = parsed
		}
	}
	if value := os.Getenv("YMHUT_SOURCE_CHECK_SECONDS"); value != "" {
		if parsed, err := strconv.Atoi(value); err == nil {
			cfg.SourceCheckSeconds = parsed
		}
	}
}

func normalize(root string, cfg *Config) {
	cfg.BaseDir = absPath(root, firstNonEmpty(cfg.BaseDir, root))
	if cfg.ConfigPath == "" {
		cfg.ConfigPath = filepath.Join(cfg.BaseDir, "config.json")
	}
	if cfg.Listen == "" {
		cfg.Listen = DefaultListen
	}
	if cfg.StorageDir == "" {
		cfg.StorageDir = filepath.Join(cfg.BaseDir, "storage")
	}
	cfg.StorageDir = absPath(cfg.BaseDir, cfg.StorageDir)
	if cfg.DataDir == "" {
		cfg.DataDir = filepath.Join(cfg.BaseDir, "data")
	}
	cfg.DataDir = absPath(cfg.BaseDir, cfg.DataDir)
	if cfg.UpdatePublicDir == "" {
		cfg.UpdatePublicDir = filepath.Join(cfg.DataDir, "update", "public")
	}
	cfg.UpdatePublicDir = absPath(cfg.BaseDir, cfg.UpdatePublicDir)
	if cfg.UpdateNoticeDir == "" {
		cfg.UpdateNoticeDir = filepath.Join(cfg.DataDir, "update-notice")
	}
	cfg.UpdateNoticeDir = absPath(cfg.BaseDir, cfg.UpdateNoticeDir)
	if cfg.DownloadsDir == "" {
		cfg.DownloadsDir = filepath.Join(cfg.UpdatePublicDir, "downloads")
	}
	cfg.DownloadsDir = absPath(cfg.BaseDir, cfg.DownloadsDir)
	if cfg.AdminWebDir == "" {
		cfg.AdminWebDir = filepath.Join(cfg.BaseDir, "web", "admin", "dist")
	}
	cfg.AdminWebDir = absPath(cfg.BaseDir, cfg.AdminWebDir)
	if cfg.PortalWebDir == "" {
		cfg.PortalWebDir = filepath.Join(cfg.BaseDir, "web", "portal", "dist")
	}
	cfg.PortalWebDir = absPath(cfg.BaseDir, cfg.PortalWebDir)
	if cfg.SetupWebDir == "" {
		cfg.SetupWebDir = filepath.Join(cfg.BaseDir, "web", "setup", "dist")
	}
	cfg.SetupWebDir = absPath(cfg.BaseDir, cfg.SetupWebDir)
	if cfg.LegacyUpdateDir == "" {
		cfg.LegacyUpdateDir = filepath.Clean(filepath.Join(cfg.BaseDir, "..", "update"))
	}
	cfg.LegacyUpdateDir = absPath(cfg.BaseDir, cfg.LegacyUpdateDir)
	if cfg.LegacyFeedbackDir == "" {
		cfg.LegacyFeedbackDir = filepath.Clean(filepath.Join(cfg.BaseDir, "..", "feedback-mailer"))
	}
	cfg.LegacyFeedbackDir = absPath(cfg.BaseDir, cfg.LegacyFeedbackDir)
	if cfg.LegacyUpdateNoticeDir == "" {
		cfg.LegacyUpdateNoticeDir = filepath.Clean(filepath.Join(cfg.BaseDir, "..", "..", "update-notice"))
	}
	cfg.LegacyUpdateNoticeDir = absPath(cfg.BaseDir, cfg.LegacyUpdateNoticeDir)
	if cfg.Database.Provider == "" {
		cfg.Database.Provider = "sqlite"
	}
	cfg.Database.Provider = strings.ToLower(strings.TrimSpace(cfg.Database.Provider))
	if cfg.Database.SQLitePath == "" {
		cfg.Database.SQLitePath = filepath.Join(cfg.StorageDir, "unified.sqlite")
	}
	cfg.Database.SQLitePath = absPath(cfg.BaseDir, cfg.Database.SQLitePath)
	if cfg.Database.MySQLHost == "" {
		cfg.Database.MySQLHost = "127.0.0.1"
	}
	if cfg.Database.MySQLPort <= 0 {
		cfg.Database.MySQLPort = 3306
	}
	if cfg.Database.Provider == "mysql" && cfg.Database.MySQLDSN == "" && cfg.Database.MySQLDatabase != "" && cfg.Database.MySQLUser != "" {
		if dsn, err := BuildMySQLDSN(MySQLInput{
			Host:      cfg.Database.MySQLHost,
			Port:      cfg.Database.MySQLPort,
			Database:  cfg.Database.MySQLDatabase,
			Username:  cfg.Database.MySQLUser,
			Password:  cfg.Database.MySQLPassword,
			Charset:   "utf8mb4",
			ParseTime: true,
		}); err == nil {
			cfg.Database.MySQLDSN = dsn
		}
	}
	if cfg.Database.HealthIntervalSec <= 0 {
		cfg.Database.HealthIntervalSec = 30
	}
	if cfg.Database.MaxOpenConns <= 0 {
		cfg.Database.MaxOpenConns = 10
	}
	if cfg.Database.MaxIdleConns <= 0 {
		cfg.Database.MaxIdleConns = 4
	}
	if cfg.Database.ConnMaxLifetimeSeconds <= 0 {
		cfg.Database.ConnMaxLifetimeSeconds = 300
	}
	if cfg.ClientSignatureKey == "" {
		cfg.ClientSignatureKey = "ymhut-box-feedback-client-v1"
	}
	if cfg.PackageEncryptionKey == "" {
		cfg.PackageEncryptionKey = "ymhut-box-feedback-package-v1"
	}
	if cfg.TimestampWindowSeconds <= 0 {
		cfg.TimestampWindowSeconds = 600
	}
	if cfg.MaxRequestBytes <= 0 {
		cfg.MaxRequestBytes = 12 * 1024 * 1024
	}
	if cfg.MaxPackageBytes <= 0 {
		cfg.MaxPackageBytes = 10 * 1024 * 1024
	}
	if cfg.UploadGuard.MaxZipFiles <= 0 {
		cfg.UploadGuard.MaxZipFiles = 80
	}
	if cfg.UploadGuard.MaxDecompressedBytes <= 0 {
		cfg.UploadGuard.MaxDecompressedBytes = 30 * 1024 * 1024
	}
	if cfg.UploadGuard.MaxSingleFileBytes <= 0 {
		cfg.UploadGuard.MaxSingleFileBytes = 8 * 1024 * 1024
	}
	if cfg.UploadGuard.MaxCompressionRatio <= 0 {
		cfg.UploadGuard.MaxCompressionRatio = 120
	}
	if cfg.UploadGuard.MaxReadableTextBytes <= 0 {
		cfg.UploadGuard.MaxReadableTextBytes = 256 * 1024
	}
	if cfg.SourceCheckSeconds <= 0 {
		cfg.SourceCheckSeconds = 300
	}
	if cfg.Mail.Port <= 0 {
		cfg.Mail.Port = 465
	}
	cfg.Mail.Secure = strings.ToLower(strings.TrimSpace(cfg.Mail.Secure))
	if cfg.Mail.Secure == "" {
		cfg.Mail.Secure = "ssl"
	}
	if cfg.Mail.FromName == "" {
		cfg.Mail.FromName = "YMhut Box Feedback"
	}
	if cfg.Mail.FromAddress == "" {
		cfg.Mail.FromAddress = cfg.Mail.Username
	}
	if cfg.Mail.TimeoutSeconds <= 0 {
		cfg.Mail.TimeoutSeconds = 20
	}
	if cfg.Branding.SiteIconURL == "" {
		cfg.Branding.SiteIconURL = "https://img.ymhut.cn/file/1782108850041_icon.webp"
	}
	if cfg.Branding.DeveloperAvatarURL == "" {
		cfg.Branding.DeveloperAvatarURL = "https://img.ymhut.cn/file/1782108780690_b_3db45f3787f19192c8de8e06bc0987ef.webp"
	}
	if cfg.Branding.DeveloperName == "" {
		cfg.Branding.DeveloperName = "YMhut"
	}
	if cfg.Branding.FeedbackEmail == "" {
		cfg.Branding.FeedbackEmail = "support@ymhut.cn"
	}
	if cfg.Mail.DeveloperAddress == "" {
		cfg.Mail.DeveloperAddress = cfg.Branding.FeedbackEmail
	}
}

func ResolveBaseDir() (string, error) {
	if value := os.Getenv("YMHUT_BASE_DIR"); value != "" {
		return filepath.Abs(value)
	}
	if cwd, err := os.Getwd(); err == nil {
		if filepath.Base(cwd) == "unified-management" {
			return filepath.Abs(cwd)
		}
		candidate := filepath.Join(cwd, "server", "unified-management")
		if info, err := os.Stat(candidate); err == nil && info.IsDir() {
			return filepath.Abs(candidate)
		}
	}
	exe, err := os.Executable()
	if err != nil {
		return os.Getwd()
	}
	return filepath.Abs(filepath.Dir(exe))
}

func Save(cfg *Config) error {
	if cfg == nil {
		return nil
	}
	normalize(firstNonEmpty(cfg.BaseDir, "."), cfg)
	if err := os.MkdirAll(filepath.Dir(cfg.ConfigPath), 0o750); err != nil {
		return err
	}
	persisted := cfg.relativeCopy()
	data, err := json.MarshalIndent(persisted, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(cfg.ConfigPath, data, 0o600)
}

func (cfg *Config) relativeCopy() Config {
	next := *cfg
	base := cfg.BaseDir
	next.BaseDir = "."
	next.StorageDir = relativePath(base, cfg.StorageDir)
	next.DataDir = relativePath(base, cfg.DataDir)
	next.UpdatePublicDir = relativePath(base, cfg.UpdatePublicDir)
	next.UpdateNoticeDir = relativePath(base, cfg.UpdateNoticeDir)
	next.DownloadsDir = relativePath(base, cfg.DownloadsDir)
	next.AdminWebDir = relativePath(base, cfg.AdminWebDir)
	next.PortalWebDir = relativePath(base, cfg.PortalWebDir)
	next.SetupWebDir = relativePath(base, cfg.SetupWebDir)
	next.LegacyUpdateDir = relativePath(base, cfg.LegacyUpdateDir)
	next.LegacyFeedbackDir = relativePath(base, cfg.LegacyFeedbackDir)
	next.LegacyUpdateNoticeDir = relativePath(base, cfg.LegacyUpdateNoticeDir)
	next.Database.SQLitePath = relativePath(base, cfg.Database.SQLitePath)
	return next
}

func relativePath(base, value string) string {
	if strings.TrimSpace(value) == "" || strings.HasPrefix(strings.ToLower(value), "file:") {
		return value
	}
	rel, err := filepath.Rel(base, value)
	if err != nil || rel == "" {
		return value
	}
	if strings.HasPrefix(rel, "..") {
		return filepath.ToSlash(rel)
	}
	return filepath.ToSlash(rel)
}

func sanitizeNonPortablePaths(cfg *Config) {
	if runtime.GOOS == "windows" {
		return
	}
	for _, target := range []*string{
		&cfg.StorageDir,
		&cfg.DataDir,
		&cfg.UpdatePublicDir,
		&cfg.UpdateNoticeDir,
		&cfg.DownloadsDir,
		&cfg.AdminWebDir,
		&cfg.PortalWebDir,
		&cfg.SetupWebDir,
		&cfg.LegacyUpdateDir,
		&cfg.LegacyFeedbackDir,
		&cfg.LegacyUpdateNoticeDir,
		&cfg.Database.SQLitePath,
	} {
		if isWindowsAbsolutePath(*target) {
			*target = ""
		}
	}
}

func shouldRewriteRelativeConfig(data []byte) bool {
	var payload any
	if len(data) == 0 || json.Unmarshal(data, &payload) != nil {
		return false
	}
	return containsAbsolutePath(payload)
}

func containsAbsolutePath(value any) bool {
	switch typed := value.(type) {
	case map[string]any:
		for _, item := range typed {
			if containsAbsolutePath(item) {
				return true
			}
		}
	case []any:
		for _, item := range typed {
			if containsAbsolutePath(item) {
				return true
			}
		}
	case string:
		return filepath.IsAbs(typed) || isWindowsAbsolutePath(typed)
	}
	return false
}

func isWindowsAbsolutePath(value string) bool {
	value = strings.TrimSpace(value)
	if len(value) >= 3 {
		drive := value[0]
		if ((drive >= 'A' && drive <= 'Z') || (drive >= 'a' && drive <= 'z')) && value[1] == ':' && (value[2] == '\\' || value[2] == '/') {
			return true
		}
	}
	return strings.HasPrefix(value, `\\`)
}

func absPath(base, value string) string {
	if strings.TrimSpace(value) == "" {
		return value
	}
	if filepath.IsAbs(value) || strings.HasPrefix(strings.ToLower(value), "file:") {
		return filepath.Clean(value)
	}
	return filepath.Clean(filepath.Join(base, value))
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if value != "" {
			return value
		}
	}
	return ""
}
