package config

import (
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/caarlos0/env/v11"
)

type Config struct {
	Agents   AgentsConfig   `json:"agents"`
	Channels ChannelsConfig `json:"channels"`
	Models   ModelsConfig   `json:"models,omitempty"`
	Gateway  GatewayConfig  `json:"gateway"`
	Cron     CronConfig     `json:"cron"`
	Tools    ToolsConfig    `json:"tools"`
	Logging  LoggingConfig  `json:"logging"`
	Sentinel SentinelConfig `json:"sentinel"`
	Memory   MemoryConfig   `json:"memory"`
	mu       sync.RWMutex
}

type AgentsConfig struct {
	Defaults      AgentDefaults             `json:"defaults"`
	Router        AgentRouterConfig         `json:"router,omitempty"`
	Communication AgentCommunicationConfig  `json:"communication,omitempty"`
	Subagents     map[string]SubagentConfig `json:"subagents,omitempty"`
}

type AgentRouterConfig struct {
	Enabled              bool                    `json:"enabled"`
	MainAgentID          string                  `json:"main_agent_id,omitempty"`
	Strategy             string                  `json:"strategy,omitempty"`
	Policy               AgentRouterPolicyConfig `json:"policy,omitempty"`
	Rules                []AgentRouteRule        `json:"rules,omitempty"`
	AllowDirectAgentChat bool                    `json:"allow_direct_agent_chat,omitempty"`
	MaxHops              int                     `json:"max_hops,omitempty"`
	DefaultTimeoutSec    int                     `json:"default_timeout_sec,omitempty"`
	DefaultWaitReply     bool                    `json:"default_wait_reply,omitempty"`
	StickyThreadOwner    bool                    `json:"sticky_thread_owner,omitempty"`
}

type AgentRouterPolicyConfig struct {
	IntentMaxInputChars  int `json:"intent_max_input_chars" env:"CLAWGO_INTENT_MAX_INPUT_CHARS"`
	MaxRoundsWithoutUser int `json:"max_rounds_without_user" env:"CLAWGO_AUTOLEARN_MAX_ROUNDS_WITHOUT_USER"`
}

type AgentRouteRule struct {
	AgentID  string   `json:"agent_id"`
	Keywords []string `json:"keywords,omitempty"`
}

type AgentCommunicationConfig struct {
	Mode                 string `json:"mode,omitempty"`
	PersistThreads       bool   `json:"persist_threads,omitempty"`
	PersistMessages      bool   `json:"persist_messages,omitempty"`
	MaxMessagesPerThread int    `json:"max_messages_per_thread,omitempty"`
	DeadLetterQueue      bool   `json:"dead_letter_queue,omitempty"`
	DefaultMessageTTLSec int    `json:"default_message_ttl_sec,omitempty"`
}

type SubagentConfig struct {
	Enabled               bool                  `json:"enabled"`
	Type                  string                `json:"type,omitempty"`
	Transport             string                `json:"transport,omitempty"`
	NodeID                string                `json:"node_id,omitempty"`
	ParentAgentID         string                `json:"parent_agent_id,omitempty"`
	NotifyMainPolicy      string                `json:"notify_main_policy,omitempty"`
	DisplayName           string                `json:"display_name,omitempty"`
	Role                  string                `json:"role,omitempty"`
	Description           string                `json:"description,omitempty"`
	SystemPromptFile      string                `json:"system_prompt_file,omitempty"`
	MemoryNamespace       string                `json:"memory_namespace,omitempty"`
	AcceptFrom            []string              `json:"accept_from,omitempty"`
	CanTalkTo             []string              `json:"can_talk_to,omitempty"`
	RequiresMainMediation bool                  `json:"requires_main_mediation,omitempty"`
	DefaultReplyTo        string                `json:"default_reply_to,omitempty"`
	Tools                 SubagentToolsConfig   `json:"tools,omitempty"`
	Runtime               SubagentRuntimeConfig `json:"runtime,omitempty"`
}

func (s *SubagentConfig) UnmarshalJSON(data []byte) error {
	type alias SubagentConfig
	var raw struct {
		alias
		LegacySystemPrompt string `json:"system_prompt"`
	}
	if err := json.Unmarshal(data, &raw); err != nil {
		return err
	}
	*s = SubagentConfig(raw.alias)
	return nil
}

type SubagentToolsConfig struct {
	Allowlist        []string `json:"allowlist,omitempty"`
	Denylist         []string `json:"denylist,omitempty"`
	MaxParallelCalls int      `json:"max_parallel_calls,omitempty"`
}

type SubagentRuntimeConfig struct {
	Provider        string  `json:"provider,omitempty"`
	Model           string  `json:"model,omitempty"`
	Temperature     float64 `json:"temperature,omitempty"`
	TimeoutSec      int     `json:"timeout_sec,omitempty"`
	MaxRetries      int     `json:"max_retries,omitempty"`
	RetryBackoffMs  int     `json:"retry_backoff_ms,omitempty"`
	MaxTaskChars    int     `json:"max_task_chars,omitempty"`
	MaxResultChars  int     `json:"max_result_chars,omitempty"`
	MaxParallelRuns int     `json:"max_parallel_runs,omitempty"`
}

type AgentDefaults struct {
	Workspace         string                    `json:"workspace" env:"CLAWGO_AGENTS_DEFAULTS_WORKSPACE"`
	Model             AgentModelDefaults        `json:"model,omitempty"`
	MaxTokens         int                       `json:"max_tokens" env:"CLAWGO_AGENTS_DEFAULTS_MAX_TOKENS"`
	Temperature       float64                   `json:"temperature" env:"CLAWGO_AGENTS_DEFAULTS_TEMPERATURE"`
	MaxToolIterations int                       `json:"max_tool_iterations" env:"CLAWGO_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
	Heartbeat         HeartbeatConfig           `json:"heartbeat"`
	ContextCompaction ContextCompactionConfig   `json:"context_compaction"`
	Execution         AgentExecutionConfig      `json:"execution"`
	SummaryPolicy     SystemSummaryPolicyConfig `json:"summary_policy"`
}

type AgentModelDefaults struct {
	Primary string `json:"primary,omitempty" env:"CLAWGO_AGENTS_DEFAULTS_MODEL_PRIMARY"`
}

type HeartbeatConfig struct {
	Enabled        bool   `json:"enabled" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_ENABLED"`
	EverySec       int    `json:"every_sec" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_EVERY_SEC"`
	AckMaxChars    int    `json:"ack_max_chars" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_ACK_MAX_CHARS"`
	PromptTemplate string `json:"prompt_template" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_PROMPT_TEMPLATE"`
}

type AgentExecutionConfig struct {
	RunStateTTLSeconds    int      `json:"run_state_ttl_seconds" env:"CLAWGO_RUN_STATE_TTL_SECONDS"`
	RunStateMax           int      `json:"run_state_max" env:"CLAWGO_RUN_STATE_MAX"`
	ToolParallelSafeNames []string `json:"tool_parallel_safe_names"`
	ToolMaxParallelCalls  int      `json:"tool_max_parallel_calls"`
}

type SystemSummaryPolicyConfig struct {
	Marker          string `json:"marker"`
	CompletedPrefix string `json:"completed_prefix"`
	ChangesPrefix   string `json:"changes_prefix"`
	OutcomePrefix   string `json:"outcome_prefix"`
	CompletedTitle  string `json:"completed_title"`
	ChangesTitle    string `json:"changes_title"`
	OutcomesTitle   string `json:"outcomes_title"`
}

type ContextCompactionConfig struct {
	Enabled            bool   `json:"enabled" env:"CLAWGO_AGENTS_DEFAULTS_CONTEXT_COMPACTION_ENABLED"`
	Mode               string `json:"mode" env:"CLAWGO_AGENTS_DEFAULTS_CONTEXT_COMPACTION_MODE"`
	TriggerMessages    int    `json:"trigger_messages" env:"CLAWGO_AGENTS_DEFAULTS_CONTEXT_COMPACTION_TRIGGER_MESSAGES"`
	KeepRecentMessages int    `json:"keep_recent_messages" env:"CLAWGO_AGENTS_DEFAULTS_CONTEXT_COMPACTION_KEEP_RECENT_MESSAGES"`
	MaxSummaryChars    int    `json:"max_summary_chars" env:"CLAWGO_AGENTS_DEFAULTS_CONTEXT_COMPACTION_MAX_SUMMARY_CHARS"`
	MaxTranscriptChars int    `json:"max_transcript_chars" env:"CLAWGO_AGENTS_DEFAULTS_CONTEXT_COMPACTION_MAX_TRANSCRIPT_CHARS"`
}

type ChannelsConfig struct {
	InboundMessageIDDedupeTTLSeconds  int            `json:"inbound_message_id_dedupe_ttl_seconds" env:"CLAWGO_CHANNELS_INBOUND_MESSAGE_ID_DEDUPE_TTL_SECONDS"`
	InboundContentDedupeWindowSeconds int            `json:"inbound_content_dedupe_window_seconds" env:"CLAWGO_CHANNELS_INBOUND_CONTENT_DEDUPE_WINDOW_SECONDS"`
	OutboundDedupeWindowSeconds       int            `json:"outbound_dedupe_window_seconds" env:"CLAWGO_CHANNELS_OUTBOUND_DEDUPE_WINDOW_SECONDS"`
	WhatsApp                          WhatsAppConfig `json:"whatsapp"`
	Telegram                          TelegramConfig `json:"telegram"`
	Feishu                            FeishuConfig   `json:"feishu"`
	Discord                           DiscordConfig  `json:"discord"`
	MaixCam                           MaixCamConfig  `json:"maixcam"`
	QQ                                QQConfig       `json:"qq"`
	DingTalk                          DingTalkConfig `json:"dingtalk"`
}

type WhatsAppConfig struct {
	Enabled                bool     `json:"enabled" env:"CLAWGO_CHANNELS_WHATSAPP_ENABLED"`
	BridgeURL              string   `json:"bridge_url" env:"CLAWGO_CHANNELS_WHATSAPP_BRIDGE_URL"`
	AllowFrom              []string `json:"allow_from" env:"CLAWGO_CHANNELS_WHATSAPP_ALLOW_FROM"`
	EnableGroups           bool     `json:"enable_groups" env:"CLAWGO_CHANNELS_WHATSAPP_ENABLE_GROUPS"`
	RequireMentionInGroups bool     `json:"require_mention_in_groups" env:"CLAWGO_CHANNELS_WHATSAPP_REQUIRE_MENTION_IN_GROUPS"`
}

type TelegramConfig struct {
	Enabled                bool     `json:"enabled" env:"CLAWGO_CHANNELS_TELEGRAM_ENABLED"`
	Token                  string   `json:"token" env:"CLAWGO_CHANNELS_TELEGRAM_TOKEN"`
	Streaming              bool     `json:"streaming"`
	AllowFrom              []string `json:"allow_from" env:"CLAWGO_CHANNELS_TELEGRAM_ALLOW_FROM"`
	AllowChats             []string `json:"allow_chats" env:"CLAWGO_CHANNELS_TELEGRAM_ALLOW_CHATS"`
	EnableGroups           bool     `json:"enable_groups" env:"CLAWGO_CHANNELS_TELEGRAM_ENABLE_GROUPS"`
	RequireMentionInGroups bool     `json:"require_mention_in_groups" env:"CLAWGO_CHANNELS_TELEGRAM_REQUIRE_MENTION_IN_GROUPS"`
}

type FeishuConfig struct {
	Enabled                bool     `json:"enabled" env:"CLAWGO_CHANNELS_FEISHU_ENABLED"`
	AppID                  string   `json:"app_id" env:"CLAWGO_CHANNELS_FEISHU_APP_ID"`
	AppSecret              string   `json:"app_secret" env:"CLAWGO_CHANNELS_FEISHU_APP_SECRET"`
	EncryptKey             string   `json:"encrypt_key" env:"CLAWGO_CHANNELS_FEISHU_ENCRYPT_KEY"`
	VerificationToken      string   `json:"verification_token" env:"CLAWGO_CHANNELS_FEISHU_VERIFICATION_TOKEN"`
	AllowFrom              []string `json:"allow_from" env:"CLAWGO_CHANNELS_FEISHU_ALLOW_FROM"`
	AllowChats             []string `json:"allow_chats" env:"CLAWGO_CHANNELS_FEISHU_ALLOW_CHATS"`
	EnableGroups           bool     `json:"enable_groups" env:"CLAWGO_CHANNELS_FEISHU_ENABLE_GROUPS"`
	RequireMentionInGroups bool     `json:"require_mention_in_groups" env:"CLAWGO_CHANNELS_FEISHU_REQUIRE_MENTION_IN_GROUPS"`
}

type DiscordConfig struct {
	Enabled   bool     `json:"enabled" env:"CLAWGO_CHANNELS_DISCORD_ENABLED"`
	Token     string   `json:"token" env:"CLAWGO_CHANNELS_DISCORD_TOKEN"`
	AllowFrom []string `json:"allow_from" env:"CLAWGO_CHANNELS_DISCORD_ALLOW_FROM"`
}

type MaixCamConfig struct {
	Enabled   bool     `json:"enabled" env:"CLAWGO_CHANNELS_MAIXCAM_ENABLED"`
	Host      string   `json:"host" env:"CLAWGO_CHANNELS_MAIXCAM_HOST"`
	Port      int      `json:"port" env:"CLAWGO_CHANNELS_MAIXCAM_PORT"`
	AllowFrom []string `json:"allow_from" env:"CLAWGO_CHANNELS_MAIXCAM_ALLOW_FROM"`
}

type QQConfig struct {
	Enabled   bool     `json:"enabled" env:"CLAWGO_CHANNELS_QQ_ENABLED"`
	AppID     string   `json:"app_id" env:"CLAWGO_CHANNELS_QQ_APP_ID"`
	AppSecret string   `json:"app_secret" env:"CLAWGO_CHANNELS_QQ_APP_SECRET"`
	AllowFrom []string `json:"allow_from" env:"CLAWGO_CHANNELS_QQ_ALLOW_FROM"`
}

type DingTalkConfig struct {
	Enabled      bool     `json:"enabled" env:"CLAWGO_CHANNELS_DINGTALK_ENABLED"`
	ClientID     string   `json:"client_id" env:"CLAWGO_CHANNELS_DINGTALK_CLIENT_ID"`
	ClientSecret string   `json:"client_secret" env:"CLAWGO_CHANNELS_DINGTALK_CLIENT_SECRET"`
	AllowFrom    []string `json:"allow_from" env:"CLAWGO_CHANNELS_DINGTALK_ALLOW_FROM"`
}

type ModelsConfig struct {
	Providers map[string]ProviderConfig `json:"providers,omitempty"`
}

type ProviderConfig struct {
	APIKey                   string                  `json:"api_key" env:"CLAWGO_PROVIDERS_{{.Name}}_API_KEY"`
	APIBase                  string                  `json:"api_base" env:"CLAWGO_PROVIDERS_{{.Name}}_API_BASE"`
	Models                   []string                `json:"models" env:"CLAWGO_PROVIDERS_{{.Name}}_MODELS"`
	SupportsResponsesCompact bool                    `json:"supports_responses_compact" env:"CLAWGO_PROVIDERS_{{.Name}}_SUPPORTS_RESPONSES_COMPACT"`
	Auth                     string                  `json:"auth" env:"CLAWGO_PROVIDERS_{{.Name}}_AUTH"`
	TimeoutSec               int                     `json:"timeout_sec" env:"CLAWGO_PROVIDERS_PROXY_TIMEOUT_SEC"`
	RuntimePersist           bool                    `json:"runtime_persist,omitempty"`
	RuntimeHistoryFile       string                  `json:"runtime_history_file,omitempty"`
	RuntimeHistoryMax        int                     `json:"runtime_history_max,omitempty"`
	OAuth                    ProviderOAuthConfig     `json:"oauth,omitempty"`
	Responses                ProviderResponsesConfig `json:"responses"`
}

type ProviderOAuthConfig struct {
	Provider        string   `json:"provider,omitempty"`
	NetworkProxy    string   `json:"network_proxy,omitempty"`
	CredentialFile  string   `json:"credential_file,omitempty"`
	CredentialFiles []string `json:"credential_files,omitempty"`
	CallbackPort    int      `json:"callback_port,omitempty"`
	ClientID        string   `json:"client_id,omitempty"`
	ClientSecret    string   `json:"client_secret,omitempty"`
	AuthURL         string   `json:"auth_url,omitempty"`
	TokenURL        string   `json:"token_url,omitempty"`
	RedirectURL     string   `json:"redirect_url,omitempty"`
	Scopes          []string `json:"scopes,omitempty"`
	CooldownSec     int      `json:"cooldown_sec,omitempty"`
	RefreshScanSec  int      `json:"refresh_scan_sec,omitempty"`
	RefreshLeadSec  int      `json:"refresh_lead_sec,omitempty"`
}

type ProviderResponsesConfig struct {
	WebSearchEnabled         bool     `json:"web_search_enabled"`
	WebSearchContextSize     string   `json:"web_search_context_size"`
	FileSearchVectorStoreIDs []string `json:"file_search_vector_store_ids"`
	FileSearchMaxNumResults  int      `json:"file_search_max_num_results"`
	Include                  []string `json:"include"`
	StreamIncludeUsage       bool     `json:"stream_include_usage"`
}

type GatewayConfig struct {
	Host  string             `json:"host" env:"CLAWGO_GATEWAY_HOST"`
	Port  int                `json:"port" env:"CLAWGO_GATEWAY_PORT"`
	Token string             `json:"token" env:"CLAWGO_GATEWAY_TOKEN"`
	Nodes GatewayNodesConfig `json:"nodes,omitempty"`
}

type GatewayNodesConfig struct {
	P2P       GatewayNodesP2PConfig       `json:"p2p,omitempty"`
	Dispatch  GatewayNodesDispatchConfig  `json:"dispatch,omitempty"`
	Artifacts GatewayNodesArtifactsConfig `json:"artifacts,omitempty"`
}

type GatewayICEConfig struct {
	URLs       []string `json:"urls,omitempty"`
	Username   string   `json:"username,omitempty"`
	Credential string   `json:"credential,omitempty"`
}

type GatewayNodesP2PConfig struct {
	Enabled     bool               `json:"enabled"`
	Transport   string             `json:"transport,omitempty"`
	STUNServers []string           `json:"stun_servers,omitempty"`
	ICEServers  []GatewayICEConfig `json:"ice_servers,omitempty"`
}

type GatewayNodesDispatchConfig struct {
	PreferLocal        bool                `json:"prefer_local,omitempty"`
	PreferP2P          bool                `json:"prefer_p2p,omitempty"`
	AllowRelayFallback bool                `json:"allow_relay_fallback,omitempty"`
	ActionTags         map[string][]string `json:"action_tags,omitempty"`
	AgentTags          map[string][]string `json:"agent_tags,omitempty"`
	AllowActions       map[string][]string `json:"allow_actions,omitempty"`
	DenyActions        map[string][]string `json:"deny_actions,omitempty"`
	AllowAgents        map[string][]string `json:"allow_agents,omitempty"`
	DenyAgents         map[string][]string `json:"deny_agents,omitempty"`
}

type GatewayNodesArtifactsConfig struct {
	Enabled     bool `json:"enabled,omitempty"`
	KeepLatest  int  `json:"keep_latest,omitempty"`
	RetainDays  int  `json:"retain_days,omitempty"`
	PruneOnRead bool `json:"prune_on_read,omitempty"`
}

type CronConfig struct {
	MinSleepSec                  int `json:"min_sleep_sec" env:"CLAWGO_CRON_MIN_SLEEP_SEC"`
	MaxSleepSec                  int `json:"max_sleep_sec" env:"CLAWGO_CRON_MAX_SLEEP_SEC"`
	RetryBackoffBaseSec          int `json:"retry_backoff_base_sec" env:"CLAWGO_CRON_RETRY_BACKOFF_BASE_SEC"`
	RetryBackoffMaxSec           int `json:"retry_backoff_max_sec" env:"CLAWGO_CRON_RETRY_BACKOFF_MAX_SEC"`
	MaxConsecutiveFailureRetries int `json:"max_consecutive_failure_retries" env:"CLAWGO_CRON_MAX_CONSECUTIVE_FAILURE_RETRIES"`
	MaxWorkers                   int `json:"max_workers" env:"CLAWGO_CRON_MAX_WORKERS"`
}

type WebSearchConfig struct {
	APIKey     string `json:"api_key" env:"CLAWGO_TOOLS_WEB_SEARCH_API_KEY"`
	MaxResults int    `json:"max_results" env:"CLAWGO_TOOLS_WEB_SEARCH_MAX_RESULTS"`
}

type WebToolsConfig struct {
	Search WebSearchConfig `json:"search"`
}

type ShellConfig struct {
	Enabled            bool          `json:"enabled" env:"CLAWGO_TOOLS_SHELL_ENABLED"`
	WorkingDir         string        `json:"working_dir" env:"CLAWGO_TOOLS_SHELL_WORKING_DIR"`
	Timeout            time.Duration `json:"timeout" env:"CLAWGO_TOOLS_SHELL_TIMEOUT"`
	AutoInstallMissing bool          `json:"auto_install_missing" env:"CLAWGO_TOOLS_SHELL_AUTO_INSTALL_MISSING"`
	Sandbox            SandboxConfig `json:"sandbox"`
}

type SandboxConfig struct {
	Enabled bool   `json:"enabled" env:"CLAWGO_TOOLS_SHELL_SANDBOX_ENABLED"`
	Image   string `json:"image" env:"CLAWGO_TOOLS_SHELL_SANDBOX_IMAGE"`
}

type FilesystemConfig struct{}

type MCPServerConfig struct {
	Enabled     bool              `json:"enabled"`
	Transport   string            `json:"transport"`
	URL         string            `json:"url,omitempty"`
	Command     string            `json:"command"`
	Args        []string          `json:"args,omitempty"`
	Env         map[string]string `json:"env,omitempty"`
	Permission  string            `json:"permission,omitempty"`
	WorkingDir  string            `json:"working_dir,omitempty"`
	Description string            `json:"description,omitempty"`
	Package     string            `json:"package,omitempty"`
}

type MCPToolsConfig struct {
	Enabled           bool                       `json:"enabled"`
	RequestTimeoutSec int                        `json:"request_timeout_sec"`
	Servers           map[string]MCPServerConfig `json:"servers,omitempty"`
}

type ToolsConfig struct {
	Web        WebToolsConfig   `json:"web"`
	Shell      ShellConfig      `json:"shell"`
	Filesystem FilesystemConfig `json:"filesystem"`
	MCP        MCPToolsConfig   `json:"mcp"`
}

type LoggingConfig struct {
	Enabled       bool   `json:"enabled" env:"CLAWGO_LOGGING_ENABLED"`
	Dir           string `json:"dir" env:"CLAWGO_LOGGING_DIR"`
	Filename      string `json:"filename" env:"CLAWGO_LOGGING_FILENAME"`
	MaxSizeMB     int    `json:"max_size_mb" env:"CLAWGO_LOGGING_MAX_SIZE_MB"`
	RetentionDays int    `json:"retention_days" env:"CLAWGO_LOGGING_RETENTION_DAYS"`
}

type SentinelConfig struct {
	Enabled       bool   `json:"enabled" env:"CLAWGO_SENTINEL_ENABLED"`
	IntervalSec   int    `json:"interval_sec" env:"CLAWGO_SENTINEL_INTERVAL_SEC"`
	AutoHeal      bool   `json:"auto_heal" env:"CLAWGO_SENTINEL_AUTO_HEAL"`
	NotifyChannel string `json:"notify_channel" env:"CLAWGO_SENTINEL_NOTIFY_CHANNEL"`
	NotifyChatID  string `json:"notify_chat_id" env:"CLAWGO_SENTINEL_NOTIFY_CHAT_ID"`
	WebhookURL    string `json:"webhook_url" env:"CLAWGO_SENTINEL_WEBHOOK_URL"`
}

type MemoryConfig struct {
	Layered    bool `json:"layered" env:"CLAWGO_MEMORY_LAYERED"`
	RecentDays int  `json:"recent_days" env:"CLAWGO_MEMORY_RECENT_DAYS"`
	Layers     struct {
		Profile    bool `json:"profile" env:"CLAWGO_MEMORY_LAYERS_PROFILE"`
		Project    bool `json:"project" env:"CLAWGO_MEMORY_LAYERS_PROJECT"`
		Procedures bool `json:"procedures" env:"CLAWGO_MEMORY_LAYERS_PROCEDURES"`
	} `json:"layers"`
}

var (
	isDebug bool
	muDebug sync.RWMutex
)

func SetDebugMode(debug bool) {
	muDebug.Lock()
	defer muDebug.Unlock()
	isDebug = debug
}

func IsDebugMode() bool {
	muDebug.RLock()
	defer muDebug.RUnlock()
	return isDebug
}

func GetConfigDir() string {
	if IsDebugMode() {
		return ".clawgo"
	}
	home, _ := os.UserHomeDir()
	return filepath.Join(home, ".clawgo")
}

func DefaultConfig() *Config {
	configDir := GetConfigDir()
	cfg := &Config{
		Agents: AgentsConfig{
			Defaults: AgentDefaults{
				Workspace:         filepath.Join(configDir, "workspace"),
				Model:             AgentModelDefaults{Primary: "openai/gpt-5.4"},
				MaxTokens:         8192,
				Temperature:       0.7,
				MaxToolIterations: 20,
				Heartbeat: HeartbeatConfig{
					Enabled:        true,
					EverySec:       30 * 60,
					AckMaxChars:    64,
					PromptTemplate: "",
				},
				ContextCompaction: ContextCompactionConfig{
					Enabled:            true,
					Mode:               "summary",
					TriggerMessages:    60,
					KeepRecentMessages: 20,
					MaxSummaryChars:    6000,
					MaxTranscriptChars: 20000,
				},
				Execution: AgentExecutionConfig{
					RunStateTTLSeconds:    1800,
					RunStateMax:           500,
					ToolParallelSafeNames: []string{"read_file", "list_files", "find_files", "grep_files", "memory_search", "web_search", "repo_map", "system_info"},
					ToolMaxParallelCalls:  2,
				},
				SummaryPolicy: SystemSummaryPolicyConfig{
					Marker:          "## System Task Summary",
					CompletedPrefix: "- Completed:",
					ChangesPrefix:   "- Changes:",
					OutcomePrefix:   "- Outcome:",
					CompletedTitle:  "Completed Actions",
					ChangesTitle:    "Change Summaries",
					OutcomesTitle:   "Execution Outcomes",
				},
			},
			Router: AgentRouterConfig{
				Enabled:     false,
				MainAgentID: "main",
				Strategy:    "rules_first",
				Policy: AgentRouterPolicyConfig{
					IntentMaxInputChars:  1200,
					MaxRoundsWithoutUser: 200,
				},
				Rules:                []AgentRouteRule{},
				AllowDirectAgentChat: false,
				MaxHops:              6,
				DefaultTimeoutSec:    600,
				DefaultWaitReply:     true,
				StickyThreadOwner:    true,
			},
			Communication: AgentCommunicationConfig{
				Mode:                 "mediated",
				PersistThreads:       true,
				PersistMessages:      true,
				MaxMessagesPerThread: 100,
				DeadLetterQueue:      true,
				DefaultMessageTTLSec: 86400,
			},
			Subagents: map[string]SubagentConfig{},
		},
		Channels: ChannelsConfig{
			InboundMessageIDDedupeTTLSeconds:  600,
			InboundContentDedupeWindowSeconds: 12,
			OutboundDedupeWindowSeconds:       12,
			WhatsApp: WhatsAppConfig{
				Enabled:                false,
				BridgeURL:              "",
				AllowFrom:              []string{},
				EnableGroups:           true,
				RequireMentionInGroups: true,
			},
			Telegram: TelegramConfig{
				Enabled:                false,
				Token:                  "",
				Streaming:              false,
				AllowFrom:              []string{},
				AllowChats:             []string{},
				EnableGroups:           true,
				RequireMentionInGroups: true,
			},
			Feishu: FeishuConfig{
				Enabled:                false,
				AppID:                  "",
				AppSecret:              "",
				EncryptKey:             "",
				VerificationToken:      "",
				AllowFrom:              []string{},
				AllowChats:             []string{},
				EnableGroups:           true,
				RequireMentionInGroups: true,
			},
			Discord: DiscordConfig{
				Enabled:   false,
				Token:     "",
				AllowFrom: []string{},
			},
			MaixCam: MaixCamConfig{
				Enabled:   false,
				Host:      "0.0.0.0",
				Port:      18790,
				AllowFrom: []string{},
			},
			QQ: QQConfig{
				Enabled:   false,
				AppID:     "",
				AppSecret: "",
				AllowFrom: []string{},
			},
			DingTalk: DingTalkConfig{
				Enabled:      false,
				ClientID:     "",
				ClientSecret: "",
				AllowFrom:    []string{},
			},
		},
		Models: ModelsConfig{
			Providers: map[string]ProviderConfig{
				"openai": {
					APIBase:    "https://api.openai.com/v1",
					Models:     []string{"gpt-5.4"},
					TimeoutSec: 90,
				},
			},
		},
		Gateway: GatewayConfig{
			Host:  "0.0.0.0",
			Port:  18790,
			Token: generateGatewayToken(),
			Nodes: GatewayNodesConfig{
				P2P: GatewayNodesP2PConfig{
					Enabled:     false,
					Transport:   "websocket_tunnel",
					STUNServers: []string{},
					ICEServers:  []GatewayICEConfig{},
				},
				Dispatch: GatewayNodesDispatchConfig{
					PreferLocal:        false,
					PreferP2P:          true,
					AllowRelayFallback: true,
					ActionTags:         map[string][]string{},
					AgentTags:          map[string][]string{},
					AllowActions:       map[string][]string{},
					DenyActions:        map[string][]string{},
					AllowAgents:        map[string][]string{},
					DenyAgents:         map[string][]string{},
				},
				Artifacts: GatewayNodesArtifactsConfig{
					Enabled:     false,
					KeepLatest:  500,
					RetainDays:  7,
					PruneOnRead: true,
				},
			},
		},
		Cron: CronConfig{
			MinSleepSec:                  1,
			MaxSleepSec:                  30,
			RetryBackoffBaseSec:          30,
			RetryBackoffMaxSec:           1800,
			MaxConsecutiveFailureRetries: 5,
			MaxWorkers:                   4,
		},
		Tools: ToolsConfig{
			Web: WebToolsConfig{
				Search: WebSearchConfig{
					APIKey:     "",
					MaxResults: 5,
				},
			},
			Shell: ShellConfig{
				Enabled:            true,
				Timeout:            60 * time.Second,
				AutoInstallMissing: false,
				Sandbox: SandboxConfig{
					Enabled: false,
					Image:   "alpine:3.20",
				},
			},
			Filesystem: FilesystemConfig{},
			MCP: MCPToolsConfig{
				Enabled:           false,
				RequestTimeoutSec: 20,
				Servers:           map[string]MCPServerConfig{},
			},
		},
		Logging: LoggingConfig{
			Enabled:       true,
			Dir:           filepath.Join(configDir, "logs"),
			Filename:      "clawgo.log",
			MaxSizeMB:     20,
			RetentionDays: 3,
		},
		Sentinel: SentinelConfig{
			Enabled:       true,
			IntervalSec:   60,
			AutoHeal:      true,
			NotifyChannel: "",
			NotifyChatID:  "",
			WebhookURL:    "",
		},
		Memory: MemoryConfig{
			Layered:    true,
			RecentDays: 3,
			Layers: struct {
				Profile    bool `json:"profile" env:"CLAWGO_MEMORY_LAYERS_PROFILE"`
				Project    bool `json:"project" env:"CLAWGO_MEMORY_LAYERS_PROJECT"`
				Procedures bool `json:"procedures" env:"CLAWGO_MEMORY_LAYERS_PROCEDURES"`
			}{
				Profile:    true,
				Project:    true,
				Procedures: true,
			},
		},
	}
	cfg.Normalize()
	return cfg
}

func normalizeProviderNameAlias(name string) string {
	switch strings.ToLower(strings.TrimSpace(name)) {
	case "geminicli", "gemini_cli":
		return "gemini-cli"
	case "aistudio", "ai-studio", "ai_studio", "google-ai-studio", "google_ai_studio", "googleaistudio":
		return "aistudio"
	case "google", "gemini-api-key", "gemini_api_key":
		return "gemini"
	case "anthropic", "claude-code", "claude_code", "claude-api-key", "claude_api_key":
		return "claude"
	case "openai-compatibility", "openai_compatibility", "openai-compat", "openai_compat":
		return "openai-compatibility"
	case "vertex-api-key", "vertex_api_key", "vertex-compat", "vertex_compat", "vertex-compatibility", "vertex_compatibility":
		return "vertex"
	case "codex-api-key", "codex_api_key":
		return "codex"
	case "i-flow", "i_flow":
		return "iflow"
	default:
		return strings.TrimSpace(name)
	}
}

func ParseProviderModelRef(raw string) (provider string, model string) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return "", ""
	}
	if idx := strings.Index(trimmed, "/"); idx > 0 {
		return normalizeProviderNameAlias(trimmed[:idx]), strings.TrimSpace(trimmed[idx+1:])
	}
	return "", trimmed
}

func AllProviderConfigs(cfg *Config) map[string]ProviderConfig {
	out := map[string]ProviderConfig{}
	if cfg == nil {
		return out
	}
	for name, pc := range cfg.Models.Providers {
		trimmed := strings.TrimSpace(name)
		if trimmed == "" {
			continue
		}
		out[trimmed] = pc
	}
	return out
}

func ProviderConfigByName(cfg *Config, name string) (ProviderConfig, bool) {
	if cfg == nil {
		return ProviderConfig{}, false
	}
	configs := AllProviderConfigs(cfg)
	trimmed := strings.TrimSpace(name)
	if pc, ok := configs[trimmed]; ok {
		return pc, true
	}
	pc, ok := configs[normalizeProviderNameAlias(trimmed)]
	return pc, ok
}

func ProviderExists(cfg *Config, name string) bool {
	_, ok := ProviderConfigByName(cfg, name)
	return ok
}

func PrimaryProviderName(cfg *Config) string {
	if cfg == nil {
		return "openai"
	}
	if provider, _ := ParseProviderModelRef(cfg.Agents.Defaults.Model.Primary); provider != "" {
		return normalizeProviderNameAlias(provider)
	}
	for name := range cfg.Models.Providers {
		if trimmed := normalizeProviderNameAlias(name); trimmed != "" {
			return trimmed
		}
	}
	return "openai"
}

func generateGatewayToken() string {
	var buf [16]byte
	if _, err := rand.Read(buf[:]); err != nil {
		return ""
	}
	return "cg_" + hex.EncodeToString(buf[:])
}

func LoadConfig(path string) (*Config, error) {
	cfg := DefaultConfig()

	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return cfg, nil
		}
		return nil, err
	}

	if err := unmarshalConfigStrict(data, cfg); err != nil {
		return nil, err
	}

	if err := env.Parse(cfg); err != nil {
		return nil, err
	}
	cfg.Normalize()

	return cfg, nil
}

func unmarshalConfigStrict(data []byte, cfg *Config) error {
	dec := json.NewDecoder(bytes.NewReader(data))
	dec.DisallowUnknownFields()
	if err := dec.Decode(cfg); err != nil {
		return err
	}
	var extra json.RawMessage
	if err := dec.Decode(&extra); err != io.EOF {
		if err == nil {
			return fmt.Errorf("invalid config: trailing JSON content")
		}
		return err
	}
	return nil
}

func SaveConfig(path string, cfg *Config) error {
	cfg.mu.RLock()
	defer cfg.mu.RUnlock()

	data, err := json.MarshalIndent(cfg, "", "  ")
	if err != nil {
		return err
	}

	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return err
	}

	return os.WriteFile(path, data, 0644)
}

func (c *Config) WorkspacePath() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return expandHome(c.Agents.Defaults.Workspace)
}

func (c *Config) GetAPIKey() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	if pc, ok := c.Models.Providers[PrimaryProviderName(c)]; ok {
		return pc.APIKey
	}
	return ""
}

func (c *Config) GetAPIBase() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	if pc, ok := c.Models.Providers[PrimaryProviderName(c)]; ok {
		return pc.APIBase
	}
	return ""
}

func (c *Config) LogFilePath() string {
	c.mu.RLock()
	defer c.mu.RUnlock()

	dir := expandHome(c.Logging.Dir)
	filename := c.Logging.Filename
	if filename == "" {
		filename = "clawgo.log"
	}
	return filepath.Join(dir, filename)
}

func expandHome(path string) string {
	if path == "" {
		return path
	}
	if path[0] == '~' {
		home, _ := os.UserHomeDir()
		if len(path) > 1 && path[1] == '/' {
			return home + path[1:]
		}
		return home
	}
	return path
}
