import type { GatewayRequest, OutboundMessage } from '../protocol.js';
import { makeResponse, makeError, ErrorCode } from '../protocol.js';
import { MODEL_PROVIDERS, type Config, type ModelProvider } from '../../config/index.js';
import type { ModelRouter, ModelTier } from '../../models/router.js';
import { createClientFromConfig } from '../../daemon/models.js';

export interface ConfigHandlerDeps {
  config: Config;
  persistConfig?: (nextConfig: Config) => Promise<void> | void;
  modelRouter?: ModelRouter;
}

function ensureCouncilsConfig(config: Config): NonNullable<Config['councils']> {
  config.councils ??= {
    enabled: false,
    defaults: {
      max_rounds: 2,
      ideas_per_round: 6,
      top_ideas_for_bridge: 3,
      bridge_packet_max_chars: 2500,
      bridge_field_max_bullets: 6,
      bridge_entry_max_chars: 300,
      novelty_delta_threshold: 10,
      repetition_threshold: 70,
    },
    strict_grounding: false,
    strict_meta_validation: true,
    groups: {
      D: {
        arbiter_agent: 'council_d_arbiter',
        freethinker_agent: 'council_d_freethinker',
        model_tier: 'complex',
        group_prompt_prefix: 'Optimize for feasibility and speed-to-test. Prefer boring-but-true.',
        novelty_bias: 'low',
        risk_tolerance: 'low',
        forbidden_approaches: ['moonshots', 'handwavy AI claims', 'unverified assumptions'],
      },
      P: {
        arbiter_agent: 'council_p_arbiter',
        freethinker_agent: 'council_p_freethinker',
        model_tier: 'complex',
        group_prompt_prefix: 'Optimize for reframing and non-obvious leverage. Weird is fine; label speculation.',
        novelty_bias: 'high',
        risk_tolerance: 'high',
        forbidden_approaches: ['incremental tweaks', 'obvious best practices', 'purely conventional solutions'],
      },
    },
    meta_arbiter_agent: 'council_meta_arbiter',
    meta_model_tier: 'complex',
  };
  return config.councils;
}

/**
 * Redact sensitive values from config before returning.
 * Replaces API keys, tokens, passwords, and other credentials with "***".
 *
 * Covers: telegram, discord, slack, matrix, mattermost, server, models (tiers + fallbacks + local_providers),
 * web_search, audio, memory.embedding, automation (webhooks + gmail), and mcp server env vars.
 */
export function redactConfig(config: Config): Record<string, unknown> {
  const raw = JSON.parse(JSON.stringify(config)) as Record<string, unknown>;

  // Helper: redact specified keys on an object if they exist and are non-nullish
  const redact = (obj: Record<string, unknown> | undefined, ...keys: string[]) => {
    if (!obj) {return;}
    for (const key of keys) {
      if (obj[key] !== undefined && obj[key] !== null) {obj[key] = '***';}
    }
  };

  // Telegram
  redact(raw.telegram as Record<string, unknown>, 'bot_token');

  // Discord
  redact(raw.discord as Record<string, unknown>, 'bot_token');

  // Slack
  redact(raw.slack as Record<string, unknown>, 'bot_token', 'app_token', 'signing_secret');

  // Matrix
  redact(raw.matrix as Record<string, unknown>, 'access_token');

  // Mattermost
  redact(raw.mattermost as Record<string, unknown>, 'bot_token');

  // Server (gateway bearer token)
  redact(raw.server as Record<string, unknown>, 'token');

  // Models — tiers, their fallbacks, and local_providers (+ their fallbacks)
  const models = raw.models as Record<string, unknown> | undefined;
  if (models) {
    for (const tier of ['default', 'fast', 'complex', 'local']) {
      const m = models[tier] as Record<string, unknown> | undefined;
      redact(m, 'api_key', 'auth_token');
      const fb = m?.fallback as Record<string, unknown> | undefined;
      redact(fb, 'api_key', 'auth_token');
    }
    const localProviders = models.local_providers as Record<string, Record<string, unknown>> | undefined;
    if (localProviders) {
      for (const provider of Object.values(localProviders)) {
        redact(provider, 'api_key', 'auth_token');
        const fb = provider.fallback as Record<string, unknown> | undefined;
        redact(fb, 'api_key', 'auth_token');
      }
    }
  }

  // Web search
  redact(raw.web_search as Record<string, unknown>, 'api_key');

  // Audio
  redact(raw.audio as Record<string, unknown>, 'transcription_api_key');

  // Memory → embedding
  const memory = raw.memory as Record<string, unknown> | undefined;
  if (memory) {
    redact(memory.embedding as Record<string, unknown>, 'api_key');
  }

  // Automation — webhook HMAC secrets and gmail credential paths
  const automation = raw.automation as Record<string, unknown> | undefined;
  if (automation) {
    const webhooks = automation.webhooks as Record<string, unknown>[] | undefined;
    if (webhooks) {
      for (const wh of webhooks) {
        redact(wh, 'secret');
      }
    }
    const gmail = automation.gmail as Record<string, unknown> | undefined;
    redact(gmail, 'credentials_file', 'token_file');
  }

  // MCP server env vars (may contain API keys or other secrets)
  const mcp = raw.mcp as Record<string, unknown> | undefined;
  if (mcp) {
    const servers = mcp.servers as Record<string, unknown>[] | undefined;
    if (servers) {
      for (const srv of servers) {
        if (srv.env && typeof srv.env === 'object') {
          const env = srv.env as Record<string, unknown>;
          for (const key of Object.keys(env)) {
            env[key] = '***';
          }
        }
      }
    }
  }

  return raw;
}

/** Keys that are safe to update at runtime via config.patch. */
const PATCHABLE_KEYS: Record<string, (config: Config, value: unknown) => boolean> = {
  'hooks.confirm': (config, value) => {
    if (!Array.isArray(value) || !value.every((v) => typeof v === 'string')) {return false;}
    config.hooks.confirm = value as string[];
    return true;
  },
  'hooks.log': (config, value) => {
    if (!Array.isArray(value) || !value.every((v) => typeof v === 'string')) {return false;}
    config.hooks.log = value as string[];
    return true;
  },
  'hooks.silent': (config, value) => {
    if (!Array.isArray(value) || !value.every((v) => typeof v === 'string')) {return false;}
    config.hooks.silent = value as string[];
    return true;
  },
  'server.localhost': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.server.localhost = value;
    return true;
  },
  'server.queue.mode': (config, value) => {
    if (!['collect', 'followup', 'steer', 'steer_backlog', 'interrupt'].includes(String(value))) {return false;}
    config.server.queue.mode = value as typeof config.server.queue.mode;
    return true;
  },
  'server.queue.cap': (config, value) => {
    if (typeof value !== 'number' || !Number.isFinite(value) || value < 1 || value > 1000) {return false;}
    config.server.queue.cap = Math.floor(value);
    return true;
  },
  'server.queue.overflow': (config, value) => {
    if (value !== 'drop_old' && value !== 'drop_new') {return false;}
    config.server.queue.overflow = value;
    return true;
  },
  'server.queue.debounce_ms': (config, value) => {
    if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 60_000) {return false;}
    config.server.queue.debounce_ms = Math.floor(value);
    return true;
  },
  'server.queue.summarize_overflow': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.server.queue.summarize_overflow = value;
    return true;
  },
  'server.nodes.location.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.server.nodes.location.enabled = value;
    return true;
  },
  'server.nodes.push.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.server.nodes.push.enabled = value;
    return true;
  },
  'agents.primary_tier': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    config.agents.primary_tier = value;
    return true;
  },
  'agents.delegation.compaction': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    config.agents.delegation.compaction = value;
    return true;
  },
  'agents.delegation.memory_extraction': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    config.agents.delegation.memory_extraction = value;
    return true;
  },
  'agents.delegation.classification': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    config.agents.delegation.classification = value;
    return true;
  },
  'agents.delegation.tool_summarisation': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    config.agents.delegation.tool_summarisation = value;
    return true;
  },
  'agents.delegation.complex_reasoning': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    config.agents.delegation.complex_reasoning = value;
    return true;
  },
  'agents.background_models.compaction.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.agents.background_models.compaction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.compaction.enabled = value;
    return true;
  },
  'agents.background_models.compaction.provider': (config, value) => {
    if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
    config.agents.background_models.compaction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.compaction.provider = value as ModelProvider;
    return true;
  },
  'agents.background_models.compaction.model': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.agents.background_models.compaction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.compaction.model = value.trim();
    return true;
  },
  'agents.background_models.compaction.fallback_tier': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    config.agents.background_models.compaction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.compaction.fallback_tier = value;
    return true;
  },
  'agents.background_models.memory_extraction.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.agents.background_models.memory_extraction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.memory_extraction.enabled = value;
    return true;
  },
  'agents.background_models.memory_extraction.provider': (config, value) => {
    if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
    config.agents.background_models.memory_extraction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.memory_extraction.provider = value as ModelProvider;
    return true;
  },
  'agents.background_models.memory_extraction.model': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.agents.background_models.memory_extraction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.memory_extraction.model = value.trim();
    return true;
  },
  'agents.background_models.memory_extraction.fallback_tier': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    config.agents.background_models.memory_extraction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.memory_extraction.fallback_tier = value;
    return true;
  },
  'agents.background_models.classification.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.agents.background_models.classification ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.classification.enabled = value;
    return true;
  },
  'agents.background_models.classification.provider': (config, value) => {
    if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
    config.agents.background_models.classification ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.classification.provider = value as ModelProvider;
    return true;
  },
  'agents.background_models.classification.model': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.agents.background_models.classification ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.classification.model = value.trim();
    return true;
  },
  'agents.background_models.classification.fallback_tier': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    config.agents.background_models.classification ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.classification.fallback_tier = value;
    return true;
  },
  'agents.background_models.tool_summarisation.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.agents.background_models.tool_summarisation ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.tool_summarisation.enabled = value;
    return true;
  },
  'agents.background_models.tool_summarisation.provider': (config, value) => {
    if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
    config.agents.background_models.tool_summarisation ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.tool_summarisation.provider = value as ModelProvider;
    return true;
  },
  'agents.background_models.tool_summarisation.model': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.agents.background_models.tool_summarisation ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.tool_summarisation.model = value.trim();
    return true;
  },
  'agents.background_models.tool_summarisation.fallback_tier': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    config.agents.background_models.tool_summarisation ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.tool_summarisation.fallback_tier = value;
    return true;
  },
  'agents.background_models.complex_reasoning.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.agents.background_models.complex_reasoning ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.complex_reasoning.enabled = value;
    return true;
  },
  'agents.background_models.complex_reasoning.provider': (config, value) => {
    if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
    config.agents.background_models.complex_reasoning ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.complex_reasoning.provider = value as ModelProvider;
    return true;
  },
  'agents.background_models.complex_reasoning.model': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.agents.background_models.complex_reasoning ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.complex_reasoning.model = value.trim();
    return true;
  },
  'agents.background_models.complex_reasoning.fallback_tier': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    config.agents.background_models.complex_reasoning ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
    config.agents.background_models.complex_reasoning.fallback_tier = value;
    return true;
  },
  'councils.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    const councils = ensureCouncilsConfig(config);
    councils.enabled = value;
    return true;
  },
  'councils.defaults.max_rounds': (config, value) => {
    if (typeof value !== 'number' || !Number.isFinite(value) || value < 1 || value > 6) {return false;}
    const councils = ensureCouncilsConfig(config);
    councils.defaults.max_rounds = Math.floor(value);
    return true;
  },
  'councils.groups.D.model_tier': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    const councils = ensureCouncilsConfig(config);
    councils.groups.D.model_tier = value;
    return true;
  },
  'councils.groups.P.model_tier': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    const councils = ensureCouncilsConfig(config);
    councils.groups.P.model_tier = value;
    return true;
  },
  'councils.meta_model_tier': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    const councils = ensureCouncilsConfig(config);
    councils.meta_model_tier = value;
    return true;
  },
  'councils.groups.D.arbiter_agent': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    const councils = ensureCouncilsConfig(config);
    councils.groups.D.arbiter_agent = value.trim();
    return true;
  },
  'councils.groups.D.freethinker_agent': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    const councils = ensureCouncilsConfig(config);
    councils.groups.D.freethinker_agent = value.trim();
    return true;
  },
  'councils.groups.D.grounder_agent': (config, value) => {
    if (typeof value !== 'string') {return false;}
    const next = value.trim();
    const councils = ensureCouncilsConfig(config);
    councils.groups.D.grounder_agent = next.length > 0 ? next : undefined;
    return true;
  },
  'councils.groups.P.arbiter_agent': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    const councils = ensureCouncilsConfig(config);
    councils.groups.P.arbiter_agent = value.trim();
    return true;
  },
  'councils.groups.P.freethinker_agent': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    const councils = ensureCouncilsConfig(config);
    councils.groups.P.freethinker_agent = value.trim();
    return true;
  },
  'councils.groups.P.grounder_agent': (config, value) => {
    if (typeof value !== 'string') {return false;}
    const next = value.trim();
    const councils = ensureCouncilsConfig(config);
    councils.groups.P.grounder_agent = next.length > 0 ? next : undefined;
    return true;
  },
  'councils.meta_arbiter_agent': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    const councils = ensureCouncilsConfig(config);
    councils.meta_arbiter_agent = value.trim();
    return true;
  },
  'councils.scaffold_path': (config, value) => {
    if (typeof value !== 'string') {return false;}
    const next = value.trim();
    const councils = ensureCouncilsConfig(config);
    councils.scaffold_path = next.length > 0 ? next : undefined;
    return true;
  },
  'models.default.provider': (config, value) => {
    if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
    config.models.default.provider = value as ModelProvider;
    return true;
  },
  'models.default.model': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.models.default.model = value.trim();
    return true;
  },
  'models.fast.provider': (config, value) => {
    if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
    config.models.fast ??= { ...config.models.default };
    config.models.fast.provider = value as ModelProvider;
    return true;
  },
  'models.fast.model': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.models.fast ??= { ...config.models.default };
    config.models.fast.model = value.trim();
    return true;
  },
  'models.complex.provider': (config, value) => {
    if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
    config.models.complex ??= { ...config.models.default };
    config.models.complex.provider = value as ModelProvider;
    return true;
  },
  'models.complex.model': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.models.complex ??= { ...config.models.default };
    config.models.complex.model = value.trim();
    return true;
  },
  'models.local.provider': (config, value) => {
    if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
    config.models.local ??= { ...config.models.default };
    config.models.local.provider = value as ModelProvider;
    return true;
  },
  'models.local.model': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.models.local ??= { ...config.models.default };
    config.models.local.model = value.trim();
    return true;
  },
  'automation.delivery_mode': (config, value) => {
    if (value !== 'shared_session' && value !== 'isolated_job' && value !== 'announce') {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.delivery_mode = value;
    return true;
  },
  'automation.daily_briefing.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.daily_briefing ??= {} as Config['automation']['daily_briefing'];
    config.automation.daily_briefing.enabled = value;
    return true;
  },
  'automation.daily_briefing.output.channel': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.daily_briefing ??= {} as Config['automation']['daily_briefing'];
    config.automation.daily_briefing.output ??= { channel: 'webchat', peer: 'assistant' };
    config.automation.daily_briefing.output.channel = value.trim();
    return true;
  },
  'automation.daily_briefing.output.peer': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.daily_briefing ??= {} as Config['automation']['daily_briefing'];
    config.automation.daily_briefing.output ??= { channel: 'webchat', peer: 'assistant' };
    config.automation.daily_briefing.output.peer = value.trim();
    return true;
  },
  'automation.daily_briefing.prompt': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.daily_briefing ??= {} as Config['automation']['daily_briefing'];
    config.automation.daily_briefing.prompt = value;
    return true;
  },
  'automation.daily_briefing.schedule': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.daily_briefing ??= {} as Config['automation']['daily_briefing'];
    config.automation.daily_briefing.schedule = value.trim();
    return true;
  },
  'automation.daily_briefing.timezone': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.daily_briefing ??= {} as Config['automation']['daily_briefing'];
    config.automation.daily_briefing.timezone = value.trim();
    return true;
  },
  'automation.daily_briefing.model_tier': (config, value) => {
    if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.daily_briefing ??= {} as Config['automation']['daily_briefing'];
    config.automation.daily_briefing.model_tier = value;
    return true;
  },
  'automation.gmail.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.gmail ??= {} as NonNullable<Config['automation']['gmail']>;
    config.automation.gmail.enabled = value;
    return true;
  },
  'automation.gcal.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.gcal ??= {} as NonNullable<Config['automation']['gcal']>;
    config.automation.gcal.enabled = value;
    return true;
  },
  'automation.gdocs.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.gdocs ??= {} as NonNullable<Config['automation']['gdocs']>;
    config.automation.gdocs.enabled = value;
    return true;
  },
  'automation.gdrive.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.gdrive ??= {} as NonNullable<Config['automation']['gdrive']>;
    config.automation.gdrive.enabled = value;
    return true;
  },
  'automation.gtasks.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.gtasks ??= {} as NonNullable<Config['automation']['gtasks']>;
    config.automation.gtasks.enabled = value;
    return true;
  },
  'automation.heartbeat.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.heartbeat ??= {} as Config['automation']['heartbeat'];
    config.automation.heartbeat.enabled = value;
    return true;
  },
  'automation.heartbeat.interval': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.heartbeat ??= {} as Config['automation']['heartbeat'];
    config.automation.heartbeat.interval = value.trim();
    return true;
  },
  'automation.heartbeat.notify_cooldown': (config, value) => {
    if (typeof value !== 'string' || value.trim().length === 0) {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.heartbeat ??= {} as Config['automation']['heartbeat'];
    config.automation.heartbeat.notify_cooldown = value.trim();
    return true;
  },
  'automation.heartbeat.failure_threshold': (config, value) => {
    if (typeof value !== 'number' || !Number.isFinite(value) || value < 1 || value > 10) {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.heartbeat ??= {} as Config['automation']['heartbeat'];
    config.automation.heartbeat.failure_threshold = Math.floor(value);
    return true;
  },
  'automation.heartbeat.disk_threshold_mb': (config, value) => {
    if (typeof value !== 'number' || !Number.isFinite(value) || value < 10) {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.heartbeat ??= {} as Config['automation']['heartbeat'];
    config.automation.heartbeat.disk_threshold_mb = Math.floor(value);
    return true;
  },
  'automation.heartbeat.process_memory_threshold_mb': (config, value) => {
    if (typeof value !== 'number' || !Number.isFinite(value) || value < 64) {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.heartbeat ??= {} as Config['automation']['heartbeat'];
    config.automation.heartbeat.process_memory_threshold_mb = Math.floor(value);
    return true;
  },
  'automation.heartbeat.backup_failure_threshold': (config, value) => {
    if (typeof value !== 'number' || !Number.isFinite(value) || value < 1 || value > 10) {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.heartbeat ??= {} as Config['automation']['heartbeat'];
    config.automation.heartbeat.backup_failure_threshold = Math.floor(value);
    return true;
  },
  'automation.heartbeat.provider_error_rate_threshold': (config, value) => {
    if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 1) {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.heartbeat ??= {} as Config['automation']['heartbeat'];
    config.automation.heartbeat.provider_error_rate_threshold = value;
    return true;
  },
  'automation.heartbeat.provider_error_min_calls': (config, value) => {
    if (typeof value !== 'number' || !Number.isFinite(value) || value < 1) {return false;}
    config.automation ??= {} as Config['automation'];
    config.automation.heartbeat ??= {} as Config['automation']['heartbeat'];
    config.automation.heartbeat.provider_error_min_calls = Math.floor(value);
    return true;
  },
  'automation.heartbeat.checks': (config, value) => {
    const allowed = ['gateway', 'model', 'channels', 'memory', 'disk', 'process_memory', 'backup', 'provider_errors'];
    if (!Array.isArray(value) || !value.every((entry) => typeof entry === 'string' && allowed.includes(entry))) {
      return false;
    }
    config.automation ??= {} as Config['automation'];
    config.automation.heartbeat ??= {} as Config['automation']['heartbeat'];
    config.automation.heartbeat.checks = value as Config['automation']['heartbeat']['checks'];
    return true;
  },
  'backup.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.backup ??= {} as Config['backup'];
    config.backup.enabled = value;
    return true;
  },
  'audio.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.audio ??= {} as Config['audio'];
    config.audio.enabled = value;
    return true;
  },
  'sandbox.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.sandbox ??= {} as Config['sandbox'];
    config.sandbox.enabled = value;
    return true;
  },
  'memory.daily_log.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.memory ??= {} as Config['memory'];
    config.memory.daily_log ??= {} as Config['memory']['daily_log'];
    config.memory.daily_log.enabled = value;
    return true;
  },
  'memory.proactive_extract.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.memory ??= {} as Config['memory'];
    config.memory.proactive_extract ??= {} as Config['memory']['proactive_extract'];
    config.memory.proactive_extract.enabled = value;
    return true;
  },
  'memory.proactive_extract.min_tool_calls': (config, value) => {
    if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 50) {return false;}
    config.memory ??= {} as Config['memory'];
    config.memory.proactive_extract ??= {} as Config['memory']['proactive_extract'];
    config.memory.proactive_extract.min_tool_calls = Math.floor(value);
    return true;
  },
  'tts.enabled': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.tts ??= {} as Config['tts'];
    config.tts.enabled = value;
    return true;
  },
  'tts.enabled_channels': (config, value) => {
    if (!Array.isArray(value) || !value.every((v) => typeof v === 'string' && v.trim().length > 0)) {return false;}
    config.tts ??= {} as Config['tts'];
    config.tts.enabled_channels = value as string[];
    return true;
  },
};

export function createConfigHandlers(deps: ConfigHandlerDeps) {
  const syncModelRouterFromConfig = (nextConfig: Config): void => {
    const modelRouter = deps.modelRouter;
    if (!modelRouter) {
      return;
    }

    const updateTier = (tier: ModelTier, cfg: Config['models']['default'] | undefined) => {
      if (!cfg) {
        return;
      }
      try {
        const client = createClientFromConfig(cfg);
        modelRouter.setClient(tier, client, `${cfg.provider}/${cfg.model}`);
        modelRouter.setTierStrict(tier, false);
      } catch (error) {
        // Keep config.patch successful even when runtime credentials are unavailable for a selected provider.
        console.warn(
          `[gateway.config] Skipping runtime model router update for tier "${tier}": ${
            error instanceof Error ? error.message : String(error)
          }`,
        );
      }
    };

    updateTier('default', nextConfig.models.default);
    updateTier('fast', nextConfig.models.fast);
    updateTier('complex', nextConfig.models.complex);
    updateTier('local', nextConfig.models.local);
  };

  return {
    'config.get': async (request: GatewayRequest): Promise<OutboundMessage> => {
      return makeResponse(request.id, redactConfig(deps.config));
    },

    'config.patch': async (request: GatewayRequest): Promise<OutboundMessage> => {
      const patches = request.params?.patches;
      if (!patches || typeof patches !== 'object' || Array.isArray(patches)) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'params.patches must be an object of { key: value } pairs');
      }

      const applied: string[] = [];
      const rejected: string[] = [];
      const draft = JSON.parse(JSON.stringify(deps.config)) as Config;

      for (const [key, value] of Object.entries(patches as Record<string, unknown>)) {
        const patcher = PATCHABLE_KEYS[key];
        if (!patcher) {
          rejected.push(key);
          continue;
        }
        const ok = patcher(draft, value);
        if (ok) {
          applied.push(key);
        } else {
          rejected.push(key);
        }
      }

      if (applied.length === 0) {
        return makeResponse(request.id, { applied, rejected, persisted: false });
      }

      if (deps.persistConfig) {
        try {
          await deps.persistConfig(draft);
        } catch (err) {
          return makeResponse(request.id, {
            applied: [],
            rejected,
            persisted: false,
            persistError: err instanceof Error ? err.message : String(err),
          });
        }
      }

      // Update in-memory runtime config only after a successful persist (or when persistence is not configured).
      for (const key of Object.keys(deps.config)) {
        delete (deps.config as Record<string, unknown>)[key];
      }
      Object.assign(deps.config as Record<string, unknown>, draft as Record<string, unknown>);
      syncModelRouterFromConfig(draft);

      return makeResponse(request.id, { applied, rejected, persisted: Boolean(deps.persistConfig) });
    },
  };
}
