import { describe, it, expect } from 'vitest';
import { configSchema } from './schema.js';

describe('configSchema — sandbox', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults sandbox to disabled', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.sandbox.enabled).toBe(false);
    expect(result.sandbox.image).toBe('node:22-slim');
    expect(result.sandbox.network).toBe('none');
    expect(result.sandbox.memory_limit).toBe('512m');
    expect(result.sandbox.cpu_limit).toBe('1.0');
    expect(result.sandbox.timeout_seconds).toBe(300);
  });

  it('accepts sandbox config', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      sandbox: { enabled: true, image: 'ubuntu:24.04', network: 'bridge' },
    });
    expect(result.sandbox.enabled).toBe(true);
    expect(result.sandbox.image).toBe('ubuntu:24.04');
    expect(result.sandbox.network).toBe('bridge');
  });
});

describe('configSchema — server', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults max_request_body_bytes', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.server.max_request_body_bytes).toBe(1_048_576);
  });

  it('accepts custom max_request_body_bytes', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      server: { max_request_body_bytes: 2048 },
    });
    expect(result.server.max_request_body_bytes).toBe(2048);
  });

  it('defaults ws_rate_limit settings', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.server.ws_rate_limit.enabled).toBe(true);
    expect(result.server.ws_rate_limit.capacity).toBe(30);
    expect(result.server.ws_rate_limit.refill_per_sec).toBe(15);
    expect(result.server.ws_rate_limit.max_violations).toBe(8);
    expect(result.server.ws_rate_limit.violation_window_ms).toBe(10_000);
  });

  it('accepts custom ws_rate_limit settings', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      server: {
        ws_rate_limit: {
          enabled: true,
          capacity: 5,
          refill_per_sec: 2,
          max_violations: 3,
          violation_window_ms: 2000,
        },
      },
    });
    expect(result.server.ws_rate_limit.capacity).toBe(5);
    expect(result.server.ws_rate_limit.refill_per_sec).toBe(2);
    expect(result.server.ws_rate_limit.max_violations).toBe(3);
    expect(result.server.ws_rate_limit.violation_window_ms).toBe(2000);
  });

  it('defaults queue settings', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.server.queue.mode).toBe('collect');
    expect(result.server.queue.cap).toBe(50);
    expect(result.server.queue.overflow).toBe('drop_old');
    expect(result.server.queue.debounce_ms).toBe(0);
    expect(result.server.queue.summarize_overflow).toBe(true);
    expect(result.server.queue.overrides.channels).toEqual({});
    expect(result.server.queue.overrides.sessions).toEqual({});
  });

  it('accepts custom queue settings', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      server: {
        queue: {
          mode: 'steer_backlog',
          cap: 10,
          overflow: 'drop_new',
          debounce_ms: 250,
          summarize_overflow: false,
        },
      },
    });
    expect(result.server.queue.mode).toBe('steer_backlog');
    expect(result.server.queue.cap).toBe(10);
    expect(result.server.queue.overflow).toBe('drop_new');
    expect(result.server.queue.debounce_ms).toBe(250);
    expect(result.server.queue.summarize_overflow).toBe(false);
  });

  it('accepts queue override settings', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      server: {
        queue: {
          overrides: {
            channels: {
              ws: { mode: 'collect', cap: 5 },
            },
            sessions: {
              'ws:vip-user': { mode: 'interrupt', overflow: 'drop_new', debounce_ms: 1000, summarize_overflow: false },
            },
          },
        },
      },
    });
    expect(result.server.queue.overrides.channels.ws.mode).toBe('collect');
    expect(result.server.queue.overrides.channels.ws.cap).toBe(5);
    expect(result.server.queue.overrides.sessions['ws:vip-user'].mode).toBe('interrupt');
    expect(result.server.queue.overrides.sessions['ws:vip-user'].overflow).toBe('drop_new');
    expect(result.server.queue.overrides.sessions['ws:vip-user'].debounce_ms).toBe(1000);
    expect(result.server.queue.overrides.sessions['ws:vip-user'].summarize_overflow).toBe(false);
  });

  it('defaults discovery settings', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.server.discovery.enabled).toBe(false);
    expect(result.server.discovery.service_name).toBe('flynn-gateway');
    expect(result.server.discovery.service_type).toBe('_flynn._tcp');
    expect(result.server.discovery.txt).toEqual({});
  });

  it('defaults node policy settings', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.server.nodes.enabled).toBe(false);
    expect(result.server.nodes.allowed_roles).toEqual(['companion']);
    expect(result.server.nodes.feature_gates).toEqual({});
    expect(result.server.nodes.location.enabled).toBe(false);
    expect(result.server.nodes.push.enabled).toBe(false);
  });

  it('accepts custom node policy settings', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      server: {
        nodes: {
          enabled: true,
          allowed_roles: ['companion', 'observer'],
          feature_gates: {
            'ui.canvas': true,
            'fs.sync': false,
          },
          location: {
            enabled: true,
          },
          push: {
            enabled: true,
          },
        },
      },
    });
    expect(result.server.nodes.enabled).toBe(true);
    expect(result.server.nodes.allowed_roles).toEqual(['companion', 'observer']);
    expect(result.server.nodes.feature_gates['ui.canvas']).toBe(true);
    expect(result.server.nodes.feature_gates['fs.sync']).toBe(false);
    expect(result.server.nodes.location.enabled).toBe(true);
    expect(result.server.nodes.push.enabled).toBe(true);
  });

  it('accepts custom discovery settings', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      server: {
        discovery: {
          enabled: true,
          service_name: 'flynn-dev',
          service_type: '_custom._tcp',
          txt: {
            env: 'dev',
          },
        },
      },
    });
    expect(result.server.discovery.enabled).toBe(true);
    expect(result.server.discovery.service_name).toBe('flynn-dev');
    expect(result.server.discovery.service_type).toBe('_custom._tcp');
    expect(result.server.discovery.txt).toEqual({ env: 'dev' });
  });
});

describe('configSchema — browser', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults browser tools to disabled with safe runtime defaults', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.browser.enabled).toBe(false);
    expect(result.browser.headless).toBe(true);
    expect(result.browser.max_pages).toBe(5);
    expect(result.browser.default_timeout).toBe(30000);
  });

  it('accepts explicit browser config', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      browser: {
        enabled: true,
        executable_path: '/usr/bin/chromium',
        headless: false,
        max_pages: 3,
        default_timeout: 45000,
      },
    });

    expect(result.browser.enabled).toBe(true);
    expect(result.browser.executable_path).toBe('/usr/bin/chromium');
    expect(result.browser.headless).toBe(false);
    expect(result.browser.max_pages).toBe(3);
    expect(result.browser.default_timeout).toBe(45000);
  });
});

describe('configSchema — backup', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults backup settings', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.backup.enabled).toBe(false);
    expect(result.backup.schedule).toBeUndefined();
    expect(result.backup.interval).toBe('24h');
    expect(result.backup.run_on_start).toBe(false);
    expect(result.backup.notify).toBeUndefined();
    expect(result.backup.failure_threshold).toBe(1);
    expect(result.backup.notify_recovery).toBe(true);
    expect(result.backup.include_vectors).toBe(true);
    expect(result.backup.minio.enabled).toBe(false);
    expect(result.backup.minio.prefix).toBe('flynn');
    expect(result.backup.minio.secure).toBe(true);
  });

  it('accepts custom backup settings', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      backup: {
        enabled: true,
        schedule: '0 2 * * *',
        interval: '12h',
        run_on_start: true,
        notify: { channel: 'telegram', peer: '123' },
        failure_threshold: 3,
        notify_recovery: false,
        local_dir: '/tmp/flynn-backups',
        include_vectors: false,
        minio: {
          enabled: true,
          endpoint: 'localhost:9000',
          access_key: 'key',
          secret_key: 'secret',
          bucket: 'flynn-backups',
          prefix: 'daily',
          secure: false,
        },
      },
    });

    expect(result.backup.enabled).toBe(true);
    expect(result.backup.schedule).toBe('0 2 * * *');
    expect(result.backup.interval).toBe('12h');
    expect(result.backup.run_on_start).toBe(true);
    expect(result.backup.notify).toEqual({ channel: 'telegram', peer: '123' });
    expect(result.backup.failure_threshold).toBe(3);
    expect(result.backup.notify_recovery).toBe(false);
    expect(result.backup.local_dir).toBe('/tmp/flynn-backups');
    expect(result.backup.include_vectors).toBe(false);
    expect(result.backup.minio.enabled).toBe(true);
    expect(result.backup.minio.endpoint).toBe('localhost:9000');
    expect(result.backup.minio.bucket).toBe('flynn-backups');
    expect(result.backup.minio.prefix).toBe('daily');
    expect(result.backup.minio.secure).toBe(false);
  });
});

describe('configSchema — sessions', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults end_summary settings', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.sessions.ttl).toBe('30d');
    expect(result.sessions.end_summary.enabled).toBe(false);
    expect(result.sessions.end_summary.tier).toBe('fast');
    expect(result.sessions.end_summary.max_messages).toBe(50);
    expect(result.sessions.end_summary.max_input_chars).toBe(20000);
    expect(result.sessions.end_summary.max_tokens).toBe(512);
    expect(result.sessions.end_summary.write_to_memory).toBe(true);
    expect(result.sessions.end_summary.memory_namespace).toBe('session/summaries');
  });

  it('accepts custom end_summary settings', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      sessions: {
        ttl: '7d',
        end_summary: {
          enabled: true,
          tier: 'complex',
          max_messages: 100,
          max_input_chars: 50000,
          max_tokens: 1024,
          write_to_memory: false,
          memory_namespace: 'notes/session',
        },
      },
    });

    expect(result.sessions.ttl).toBe('7d');
    expect(result.sessions.end_summary.enabled).toBe(true);
    expect(result.sessions.end_summary.tier).toBe('complex');
    expect(result.sessions.end_summary.max_messages).toBe(100);
    expect(result.sessions.end_summary.max_input_chars).toBe(50000);
    expect(result.sessions.end_summary.max_tokens).toBe(1024);
    expect(result.sessions.end_summary.write_to_memory).toBe(false);
    expect(result.sessions.end_summary.memory_namespace).toBe('notes/session');
  });
});

describe('configSchema — agent_configs', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults agent_configs to empty', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.agent_configs).toEqual({});
  });

  it('accepts named agent configs', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      agent_configs: {
        assistant: {
          system_prompt: 'You are helpful.',
          model_tier: 'default',
          backend: 'codex',
          tool_profile: 'messaging',
        },
        coder: {
          model_tier: 'complex',
          tool_profile: 'coding',
          sandbox: true,
        },
      },
    });
    expect(result.agent_configs.assistant.system_prompt).toBe('You are helpful.');
    expect(result.agent_configs.assistant.backend).toBe('codex');
    expect(result.agent_configs.assistant.tool_profile).toBe('messaging');
    expect(result.agent_configs.coder.sandbox).toBe(true);
  });
});

describe('configSchema — backends', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults backend config fields', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.backends.claude_code.enabled).toBe(false);
    expect(result.backends.claude_code.args).toEqual([]);
    expect(result.backends.claude_code.timeout_ms).toBe(120000);
    expect(result.backends.opencode.enabled).toBe(false);
    expect(result.backends.opencode.args).toEqual([]);
    expect(result.backends.codex.enabled).toBe(false);
    expect(result.backends.gemini.enabled).toBe(false);
    expect(result.backends.native.enabled).toBe(true);
  });

  it('accepts explicit codex/gemini backend config', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      backends: {
        default: 'codex',
        codex: { enabled: true, path: '/usr/local/bin/codex', args: ['run'], timeout_ms: 300000 },
        gemini: { enabled: true, path: '/usr/local/bin/gemini', args: ['chat'], timeout_ms: 60000 },
      },
    });

    expect(result.backends.default).toBe('codex');
    expect(result.backends.codex.enabled).toBe(true);
    expect(result.backends.codex.path).toBe('/usr/local/bin/codex');
    expect(result.backends.codex.args).toEqual(['run']);
    expect(result.backends.codex.timeout_ms).toBe(300000);
    expect(result.backends.gemini.enabled).toBe(true);
    expect(result.backends.gemini.path).toBe('/usr/local/bin/gemini');
    expect(result.backends.gemini.args).toEqual(['chat']);
    expect(result.backends.gemini.timeout_ms).toBe(60000);
  });
});

describe('configSchema — routing', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults routing to empty', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.routing.default_agent).toBeUndefined();
    expect(result.routing.channels).toEqual({});
    expect(result.routing.senders).toEqual({});
  });

  it('accepts routing config', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      routing: {
        default_agent: 'assistant',
        channels: { discord: 'coder' },
        senders: { 'telegram:12345': 'coder' },
      },
    });
    expect(result.routing.default_agent).toBe('assistant');
    expect(result.routing.channels.discord).toBe('coder');
    expect(result.routing.senders['telegram:12345']).toBe('coder');
  });
});

describe('configSchema — per-tier fallback', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('accepts per-tier fallback config', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      models: {
        default: {
          provider: 'anthropic',
          model: 'claude-sonnet-4-5-20250929',
          fallback: { provider: 'github', model: 'claude-sonnet-4-5-20250929' },
        },
        fast: {
          provider: 'anthropic',
          model: 'claude-haiku-4-5-20251001',
          fallback: { provider: 'github', model: 'claude-haiku-4-5-20251001' },
        },
      },
    });
    expect(result.models.default.fallback?.provider).toBe('github');
    expect(result.models.fast?.fallback?.provider).toBe('github');
  });

  it('works without fallback field (backward compat)', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.models.default.fallback).toBeUndefined();
  });

  it('fallback does not itself accept a nested fallback', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      models: {
        default: {
          provider: 'anthropic',
          model: 'claude-3',
          fallback: {
            provider: 'github',
            model: 'claude-3',
            fallback: { provider: 'ollama', model: 'llama' },
          },
        },
      },
    });
    // Zod strips unknown keys from the base schema, so nested fallback is dropped
    expect((result.models.default.fallback as Record<string, unknown>)?.fallback).toBeUndefined();
  });
});

describe('configSchema — models auth_mode', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('accepts auth_mode values per tier', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      models: {
        default: { provider: 'openai', model: 'gpt-4o', auth_mode: 'api_key' },
        fast: { provider: 'openai', model: 'gpt-4o-mini', auth_mode: 'oauth' },
      },
    });

    expect(result.models.default.auth_mode).toBe('api_key');
    expect(result.models.fast?.auth_mode).toBe('oauth');
  });

  it('rejects invalid auth_mode values', () => {
    expect(() => {
      configSchema.parse({
        ...minimalConfig,
        models: {
          default: { provider: 'openai', model: 'gpt-4o', auth_mode: 'bogus' },
        },
      });
    }).toThrow(/auth_mode/i);
  });

  it('accepts vercel provider id', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      models: {
        default: { provider: 'vercel', model: 'openai/gpt-4.1', endpoint: 'https://ai-gateway.vercel.sh/v1', api_key: 'test-key' },
      },
    });
    expect(result.models.default.provider).toBe('vercel');
  });

  it('accepts minimax and moonshot provider ids', () => {
    const minimax = configSchema.parse({
      ...minimalConfig,
      models: {
        default: { provider: 'minimax', model: 'MiniMax-M1', api_key: 'test-key' },
      },
    });
    expect(minimax.models.default.provider).toBe('minimax');

    const moonshot = configSchema.parse({
      ...minimalConfig,
      models: {
        default: { provider: 'moonshot', model: 'moonshot-v1-8k', api_key: 'test-key' },
      },
    });
    expect(moonshot.models.default.provider).toBe('moonshot');
  });
});

describe('configSchema — matrix', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('accepts matrix config and defaults sync_timeout_ms', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      matrix: {
        homeserver_url: 'https://matrix.example.org',
        access_token: 'syt_test_token',
        allowed_room_ids: ['!room1:example.org'],
        require_mention: true,
      },
    });

    expect(result.matrix).toBeDefined();
    if (!result.matrix) {
      throw new Error('Expected matrix config');
    }
    expect(result.matrix.homeserver_url).toBe('https://matrix.example.org');
    expect(result.matrix.access_token).toBe('syt_test_token');
    expect(result.matrix.sync_timeout_ms).toBe(30000);
  });

  it('matrix config is optional', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.matrix).toBeUndefined();
  });

  it('rejects invalid homeserver_url', () => {
    expect(() => {
      configSchema.parse({
        ...minimalConfig,
        matrix: {
          homeserver_url: 'not-a-url',
          access_token: 'token',
        },
      });
    }).toThrow(/homeserver/i);
  });
});

describe('configSchema — signal', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('accepts signal config and defaults polling fields', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      signal: {
        account: '+15551234567',
      },
    });

    expect(result.signal).toBeDefined();
    if (!result.signal) {
      throw new Error('Expected signal config');
    }
    expect(result.signal.account).toBe('+15551234567');
    expect(result.signal.signal_cli_path).toBe('signal-cli');
    expect(result.signal.poll_interval_ms).toBe(5000);
    expect(result.signal.send_timeout_ms).toBe(15000);
    expect(result.signal.require_mention).toBe(true);
  });

  it('signal config is optional', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.signal).toBeUndefined();
  });
});

describe('configSchema — audio talk mode', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults talk_mode fields', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.audio.talk_mode.enabled).toBe(false);
    expect(result.audio.talk_mode.wake_phrase).toBe('hey flynn');
    expect(result.audio.talk_mode.timeout_ms).toBe(120000);
    expect(result.audio.talk_mode.allow_manual_toggle).toBe(true);
  });

  it('accepts custom talk_mode settings', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      audio: {
        talk_mode: {
          enabled: true,
          wake_phrase: 'ok flynn',
          timeout_ms: 300000,
          allow_manual_toggle: false,
        },
      },
    });
    expect(result.audio.talk_mode.enabled).toBe(true);
    expect(result.audio.talk_mode.wake_phrase).toBe('ok flynn');
    expect(result.audio.talk_mode.timeout_ms).toBe(300000);
    expect(result.audio.talk_mode.allow_manual_toggle).toBe(false);
  });
});

describe('configSchema — mattermost', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('accepts mattermost config and defaults optional fields', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      mattermost: {
        server_url: 'https://mattermost.example.com',
        bot_token: 'mm-token',
      },
    });

    expect(result.mattermost).toBeDefined();
    if (!result.mattermost) {
      throw new Error('Expected mattermost config');
    }
    expect(result.mattermost.server_url).toBe('https://mattermost.example.com');
    expect(result.mattermost.allowed_channel_ids).toEqual([]);
    expect(result.mattermost.require_mention).toBe(true);
    expect(result.mattermost.mention_name).toBe('flynn');
    expect(result.mattermost.poll_interval_ms).toBe(3000);
  });

  it('mattermost config is optional', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.mattermost).toBeUndefined();
  });
});

describe('configSchema — teams', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('accepts teams config and defaults allowlist/mention fields', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      teams: {
        app_id: 'app-id',
        app_password: 'app-password',
      },
    });

    expect(result.teams).toBeDefined();
    if (!result.teams) {
      throw new Error('Expected teams config');
    }
    expect(result.teams.app_id).toBe('app-id');
    expect(result.teams.allowed_conversation_ids).toEqual([]);
    expect(result.teams.require_mention).toBe(true);
  });
});

describe('configSchema — google_chat', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('accepts google_chat config and defaults filters', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      google_chat: {
        service_account_key_file: '/tmp/gchat-service-account.json',
      },
    });

    expect(result.google_chat).toBeDefined();
    if (!result.google_chat) {
      throw new Error('Expected google_chat config');
    }
    expect(result.google_chat.service_account_key_file).toBe('/tmp/gchat-service-account.json');
    expect(result.google_chat.allowed_space_names).toEqual([]);
    expect(result.google_chat.require_mention).toBe(true);
  });
});

describe('configSchema — bluebubbles', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('accepts bluebubbles config and defaults optional fields', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      bluebubbles: {
        endpoint: 'http://localhost:1234',
        api_key: 'bb-key',
      },
    });

    expect(result.bluebubbles).toBeDefined();
    if (!result.bluebubbles) {
      throw new Error('Expected bluebubbles config');
    }
    expect(result.bluebubbles.allowed_chat_guids).toEqual([]);
    expect(result.bluebubbles.require_mention).toBe(true);
    expect(result.bluebubbles.mention_name).toBe('flynn');
  });
});

describe('configSchema — line', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('accepts line config and defaults optional fields', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      line: {
        channel_access_token: 'line-token',
        channel_secret: 'line-secret',
      },
    });

    expect(result.line).toBeDefined();
    if (!result.line) {
      throw new Error('Expected line config');
    }
    expect(result.line.channel_access_token).toBe('line-token');
    expect(result.line.channel_secret).toBe('line-secret');
    expect(result.line.allowed_source_ids).toEqual([]);
    expect(result.line.require_mention).toBe(true);
    expect(result.line.mention_name).toBe('flynn');
    expect(result.line.minio.enabled).toBe(false);
    expect(result.line.minio.prefix).toBe('flynn/channels/line');
    expect(result.line.minio.secure).toBe(true);
    expect(result.line.minio.expires).toBe('24h');
  });

  it('accepts line-specific minio overrides', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      line: {
        channel_access_token: 'line-token',
        channel_secret: 'line-secret',
        minio: {
          enabled: true,
          endpoint: 'localhost:9000',
          access_key: 'line-key',
          secret_key: 'line-secret',
          bucket: 'line-attachments',
          prefix: 'line/files',
          secure: false,
          expires: '12h',
          mc_path: '/usr/local/bin/mc',
        },
      },
    });

    if (!result.line) {
      throw new Error('Expected line config');
    }
    expect(result.line.minio.enabled).toBe(true);
    expect(result.line.minio.endpoint).toBe('localhost:9000');
    expect(result.line.minio.access_key).toBe('line-key');
    expect(result.line.minio.secret_key).toBe('line-secret');
    expect(result.line.minio.bucket).toBe('line-attachments');
    expect(result.line.minio.prefix).toBe('line/files');
    expect(result.line.minio.secure).toBe(false);
    expect(result.line.minio.expires).toBe('12h');
    expect(result.line.minio.mc_path).toBe('/usr/local/bin/mc');
  });
});

describe('configSchema — feishu', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('accepts feishu config and defaults optional fields', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      feishu: {
        app_id: 'cli_a1b2c3',
        app_secret: 'secret',
      },
    });

    expect(result.feishu).toBeDefined();
    if (!result.feishu) {
      throw new Error('Expected feishu config');
    }
    expect(result.feishu.app_id).toBe('cli_a1b2c3');
    expect(result.feishu.app_secret).toBe('secret');
    expect(result.feishu.allowed_chat_ids).toEqual([]);
    expect(result.feishu.require_mention).toBe(true);
    expect(result.feishu.mention_name).toBe('flynn');
  });
});

describe('configSchema — zalo', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('accepts zalo config and defaults optional fields', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      zalo: {
        oa_access_token: 'oa-token',
      },
    });

    expect(result.zalo).toBeDefined();
    if (!result.zalo) {
      throw new Error('Expected zalo config');
    }
    expect(result.zalo.oa_access_token).toBe('oa-token');
    expect(result.zalo.allowed_user_ids).toEqual([]);
    expect(result.zalo.require_mention).toBe(true);
    expect(result.zalo.mention_name).toBe('flynn');
    expect(result.zalo.minio.enabled).toBe(false);
    expect(result.zalo.minio.prefix).toBe('flynn/channels/zalo');
    expect(result.zalo.minio.secure).toBe(true);
    expect(result.zalo.minio.expires).toBe('24h');
  });

  it('accepts zalo-specific minio overrides', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      zalo: {
        oa_access_token: 'oa-token',
        minio: {
          enabled: true,
          endpoint: 'localhost:9000',
          access_key: 'zalo-key',
          secret_key: 'zalo-secret',
          bucket: 'zalo-attachments',
          prefix: 'zalo/files',
          secure: false,
          expires: '8h',
          mc_path: '/usr/local/bin/mc',
        },
      },
    });

    if (!result.zalo) {
      throw new Error('Expected zalo config');
    }
    expect(result.zalo.minio.enabled).toBe(true);
    expect(result.zalo.minio.endpoint).toBe('localhost:9000');
    expect(result.zalo.minio.access_key).toBe('zalo-key');
    expect(result.zalo.minio.secret_key).toBe('zalo-secret');
    expect(result.zalo.minio.bucket).toBe('zalo-attachments');
    expect(result.zalo.minio.prefix).toBe('zalo/files');
    expect(result.zalo.minio.secure).toBe(false);
    expect(result.zalo.minio.expires).toBe('8h');
    expect(result.zalo.minio.mc_path).toBe('/usr/local/bin/mc');
  });
});

describe('configSchema — whatsapp', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults whatsapp no_sandbox to false', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      whatsapp: {},
    });
    expect(result.whatsapp?.no_sandbox).toBe(false);
  });

  it('accepts whatsapp no_sandbox override', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      whatsapp: { no_sandbox: true },
    });
    expect(result.whatsapp?.no_sandbox).toBe(true);
  });
});

describe('configSchema — skills watcher', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults skills watcher settings', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.skills.load.watch).toBe(false);
    expect(result.skills.load.watch_debounce_ms).toBe(250);
    expect(result.skills.installation_execution).toBe('disabled');
    expect(result.skills.allow_shell_runner).toBe(false);
    expect(result.skills.shell_runner_allowlist).toEqual([]);
    expect(result.skills.shell_runner_governance.owner).toBeUndefined();
    expect(result.skills.shell_runner_governance.review_cadence_days).toBe(7);
    expect(result.skills.shell_runner_governance.promotion_min_success_rate).toBe(0.9);
  });

  it('accepts explicit installation execution policy', () => {
    const enabled = configSchema.parse({
      ...minimalConfig,
      skills: {
        installation_execution: 'enabled',
        allow_shell_runner: true,
        shell_runner_allowlist: ['npm install*'],
        shell_runner_governance: {
          owner: 'skills-team',
          review_cadence_days: 14,
          promotion_min_success_rate: 0.95,
        },
      },
    });

    expect(enabled.skills.installation_execution).toBe('enabled');
    expect(enabled.skills.allow_shell_runner).toBe(true);
    expect(enabled.skills.shell_runner_allowlist).toEqual(['npm install*']);
    expect(enabled.skills.shell_runner_governance.owner).toBe('skills-team');
    expect(enabled.skills.shell_runner_governance.review_cadence_days).toBe(14);
    expect(enabled.skills.shell_runner_governance.promotion_min_success_rate).toBe(0.95);
  });

  it('accepts explicit watcher settings', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      skills: {
        load: {
          watch: true,
          watch_debounce_ms: 500,
        },
      },
    });

    expect(result.skills.load.watch).toBe(true);
    expect(result.skills.load.watch_debounce_ms).toBe(500);
  });
});

describe('configSchema — k8s', () => {
  const baseConfig = {
    telegram: { bot_token: 'test-token', allowed_chat_ids: [123] },
    models: { default: { provider: 'anthropic', model: 'claude-sonnet' } },
  };

  it('accepts config without k8s section', () => {
    const result = configSchema.parse(baseConfig);
    expect(result.k8s).toBeUndefined();
  });

  it('accepts k8s config with namespace restrictions', () => {
    const result = configSchema.parse({
      ...baseConfig,
      k8s: {
        enabled: true,
        kubectl_path: '/usr/local/bin/kubectl',
        default_namespace: 'observability',
        allowed_namespaces: ['observability', 'platform'],
      },
    });

    expect(result.k8s?.enabled).toBe(true);
    expect(result.k8s?.kubectl_path).toBe('/usr/local/bin/kubectl');
    expect(result.k8s?.default_namespace).toBe('observability');
    expect(result.k8s?.allowed_namespaces).toEqual(['observability', 'platform']);
  });
});

describe('configSchema automation', () => {
  const baseConfig = {
    telegram: { bot_token: 'test-token', allowed_chat_ids: [123] },
    models: { default: { provider: 'anthropic', model: 'claude-sonnet' } },
  };

  it('accepts config without automation section', () => {
    const result = configSchema.parse(baseConfig);
    expect(result.automation).toBeDefined();
    expect(result.automation.delivery_mode).toBe('shared_session');
    expect(result.automation.cron).toEqual([]);
    expect(result.automation.daily_briefing.enabled).toBe(false);
    expect(result.automation.daily_briefing.schedule).toBe('0 8 * * *');
    expect(result.automation.daily_briefing.name).toBe('daily-briefing');
    expect(result.automation.daily_briefing.dedupe_per_local_day).toBe(true);
    expect(result.automation.minio_sync.enabled).toBe(false);
    expect(result.automation.minio_sync.interval).toBe('6h');
    expect(result.automation.minio_sync.tasks).toEqual([]);
  });

  it('accepts isolated automation delivery mode', () => {
    const result = configSchema.parse({
      ...baseConfig,
      automation: {
        delivery_mode: 'isolated_job',
      },
    });
    expect(result.automation.delivery_mode).toBe('isolated_job');
  });

  it('accepts config with cron jobs', () => {
    const result = configSchema.parse({
      ...baseConfig,
      automation: {
        cron: [{
          name: 'morning-briefing',
          schedule: '0 9 * * *',
          message: 'Good morning!',
          output: { channel: 'telegram', peer: '123' },
        }],
      },
    });
    expect(result.automation.cron).toHaveLength(1);
    expect(result.automation.cron[0].name).toBe('morning-briefing');
    expect(result.automation.cron[0].enabled).toBe(true); // default
  });

  it('rejects cron job with empty name', () => {
    expect(() => configSchema.parse({
      ...baseConfig,
      automation: {
        cron: [{
          name: '',
          schedule: '0 9 * * *',
          message: 'test',
          output: { channel: 'telegram', peer: '123' },
        }],
      },
    })).toThrow();
  });

  it('rejects cron job with empty schedule', () => {
    expect(() => configSchema.parse({
      ...baseConfig,
      automation: {
        cron: [{
          name: 'test',
          schedule: '',
          message: 'test',
          output: { channel: 'telegram', peer: '123' },
        }],
      },
    })).toThrow();
  });

  it('accepts cron job with optional fields', () => {
    const result = configSchema.parse({
      ...baseConfig,
      automation: {
        cron: [{
          name: 'test',
          schedule: '0 9 * * *',
          message: 'test',
          output: { channel: 'telegram', peer: '123' },
          enabled: false,
          timezone: 'America/New_York',
          once_per_local_day: true,
        }],
      },
    });
    expect(result.automation.cron[0].enabled).toBe(false);
    expect(result.automation.cron[0].timezone).toBe('America/New_York');
    expect(result.automation.cron[0].once_per_local_day).toBe(true);
  });

  it('accepts daily briefing automation config', () => {
    const result = configSchema.parse({
      ...baseConfig,
      automation: {
        daily_briefing: {
          enabled: true,
          name: 'weekday-briefing',
          schedule: '0 7 * * 1-5',
          timezone: 'America/New_York',
          dedupe_per_local_day: false,
          output: { channel: 'telegram', peer: '123' },
          prompt: 'Custom briefing prompt',
          model_tier: 'fast',
        },
      },
    });

    expect(result.automation.daily_briefing.enabled).toBe(true);
    expect(result.automation.daily_briefing.name).toBe('weekday-briefing');
    expect(result.automation.daily_briefing.schedule).toBe('0 7 * * 1-5');
    expect(result.automation.daily_briefing.timezone).toBe('America/New_York');
    expect(result.automation.daily_briefing.dedupe_per_local_day).toBe(false);
    expect(result.automation.daily_briefing.output).toEqual({ channel: 'telegram', peer: '123' });
    expect(result.automation.daily_briefing.prompt).toBe('Custom briefing prompt');
    expect(result.automation.daily_briefing.model_tier).toBe('fast');
  });

  it('defaults heartbeat extended thresholds and checks', () => {
    const result = configSchema.parse(baseConfig);
    expect(result.automation.heartbeat.notify_cooldown).toBe('30m');
    expect(result.automation.heartbeat.process_memory_threshold_mb).toBe(1500);
    expect(result.automation.heartbeat.backup_failure_threshold).toBe(1);
    expect(result.automation.heartbeat.provider_error_rate_threshold).toBe(0.5);
    expect(result.automation.heartbeat.provider_error_min_calls).toBe(5);
    expect(result.automation.heartbeat.checks).toContain('process_memory');
    expect(result.automation.heartbeat.checks).toContain('backup');
    expect(result.automation.heartbeat.checks).toContain('provider_errors');
  });

  it('accepts scheduled minio sync automation config', () => {
    const result = configSchema.parse({
      ...baseConfig,
      automation: {
        minio_sync: {
          enabled: true,
          interval: '3h',
          run_on_start: true,
          notify_on_success: true,
          notify: { channel: 'telegram', peer: '123' },
          tasks: [{
            prefix: 'knowledge/',
            namespace_base: 'global/knowledge/minio',
            mode: 'replace',
            max_objects: 50,
            max_chars_per_object: 12000,
            force: true,
          }],
        },
      },
    });

    expect(result.automation.minio_sync.enabled).toBe(true);
    expect(result.automation.minio_sync.interval).toBe('3h');
    expect(result.automation.minio_sync.run_on_start).toBe(true);
    expect(result.automation.minio_sync.notify_on_success).toBe(true);
    expect(result.automation.minio_sync.notify).toEqual({ channel: 'telegram', peer: '123' });
    expect(result.automation.minio_sync.tasks).toHaveLength(1);
    expect(result.automation.minio_sync.tasks[0]).toMatchObject({
      prefix: 'knowledge/',
      namespace_base: 'global/knowledge/minio',
      mode: 'replace',
      max_objects: 50,
      max_chars_per_object: 12000,
      force: true,
    });
  });
});

describe('configSchema — intents', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults intents to disabled with no rules', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.intents.enabled).toBe(false);
    expect(result.intents.rules).toEqual([]);
  });

  it('accepts intent rule config', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      intents: {
        enabled: true,
        match_threshold: 0.6,
        rules: [
          {
            name: 'deploy-rule',
            patterns: ['deploy *'],
            target: { type: 'agent', name: 'coder' },
            priority: 5,
            enabled: true,
          },
        ],
      },
    });

    expect(result.intents.enabled).toBe(true);
    expect(result.intents.rules[0].target.type).toBe('agent');
    expect(result.intents.rules[0].target.name).toBe('coder');
  });
});

describe('configSchema — routing_policy', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults routing_policy values', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.routing_policy.enabled).toBe(false);
    expect(result.routing_policy.fast_path_threshold).toBe(0.85);
    expect(result.routing_policy.llm_threshold).toBe(0.5);
    expect(result.routing_policy.default_path).toBe('llm');
  });
});

describe('configSchema — history_index', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults history indexing config', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.history_index.enabled).toBe(false);
    expect(result.history_index.max_keywords).toBe(8);
    expect(result.history_index.search_limit).toBe(10);
  });
});

describe('configSchema — memory injection strategy', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults memory injection settings', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.memory.injection_strategy).toBe('all');
    expect(result.memory.max_injection_tokens).toBe(2000);
    expect(result.memory.qmd.enabled).toBe(false);
    expect(result.memory.qmd.top_k).toBe(8);
    expect(result.memory.qmd.min_score).toBe(0.15);
  });

  it('accepts adaptive memory injection settings', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      memory: {
        injection_strategy: 'adaptive',
        max_injection_tokens: 1200,
      },
    });
    expect(result.memory.injection_strategy).toBe('adaptive');
    expect(result.memory.max_injection_tokens).toBe(1200);
  });

  it('accepts qmd backend settings', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      memory: {
        qmd: {
          enabled: true,
          top_k: 12,
          min_score: 0.2,
        },
      },
    });
    expect(result.memory.qmd.enabled).toBe(true);
    expect(result.memory.qmd.top_k).toBe(12);
    expect(result.memory.qmd.min_score).toBe(0.2);
  });
});

describe('configSchema — compaction importance threshold', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults compaction importance threshold to disabled behavior', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.compaction.importance_threshold).toBe(1);
    expect(result.compaction.proactive.enabled).toBe(false);
    expect(result.compaction.proactive.warn_pct).toBe(75);
    expect(result.compaction.proactive.checkpoint_pct).toBe(85);
    expect(result.compaction.proactive.auto_compact_pct).toBe(95);
    expect(result.compaction.proactive.checkpoint_cooldown_ms).toBe(300000);
    expect(result.compaction.proactive.memory_namespace).toBe('session/checkpoints');
  });

  it('accepts a custom importance threshold', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      compaction: {
        importance_threshold: 0.5,
        proactive: {
          enabled: true,
          warn_pct: 70,
          checkpoint_pct: 82,
          auto_compact_pct: 93,
          checkpoint_cooldown_ms: 120000,
          memory_namespace: 'notes/checkpoints',
        },
      },
    });
    expect(result.compaction.importance_threshold).toBe(0.5);
    expect(result.compaction.proactive.enabled).toBe(true);
    expect(result.compaction.proactive.warn_pct).toBe(70);
    expect(result.compaction.proactive.checkpoint_pct).toBe(82);
    expect(result.compaction.proactive.auto_compact_pct).toBe(93);
    expect(result.compaction.proactive.checkpoint_cooldown_ms).toBe(120000);
    expect(result.compaction.proactive.memory_namespace).toBe('notes/checkpoints');
  });
});

describe('configSchema — prompt context level', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults prompt.context_level to normal', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.prompt.context_level).toBe('normal');
  });

  it('accepts valid context levels', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      prompt: {
        context_level: 'debug',
      },
    });

    expect(result.prompt.context_level).toBe('debug');
  });

  it('rejects invalid context levels', () => {
    expect(() => configSchema.parse({
      ...minimalConfig,
      prompt: {
        context_level: 'verbose',
      },
    })).toThrow();
  });
});

describe('configSchema — agents truthfulness/autonomy', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('defaults to standard truthfulness and autonomy', () => {
    const result = configSchema.parse(minimalConfig);
    expect(result.agents.truthfulness_mode).toBe('standard');
    expect(result.agents.autonomy_level).toBe('standard');
  });

  it('accepts explicit truthfulness and autonomy modes', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      agents: {
        truthfulness_mode: 'strict',
        autonomy_level: 'conservative',
      },
    });

    expect(result.agents.truthfulness_mode).toBe('strict');
    expect(result.agents.autonomy_level).toBe('conservative');
  });

  it('rejects invalid truthfulness_mode', () => {
    expect(() => configSchema.parse({
      ...minimalConfig,
      agents: {
        truthfulness_mode: 'always',
      },
    })).toThrow();
  });

  it('rejects invalid autonomy_level', () => {
    expect(() => configSchema.parse({
      ...minimalConfig,
      agents: {
        autonomy_level: 'manual',
      },
    })).toThrow();
  });
});

describe('configSchema — skills registry source', () => {
  const minimalConfig = {
    telegram: { bot_token: 'test', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
  };

  it('accepts skills.registry_source when provided', () => {
    const result = configSchema.parse({
      ...minimalConfig,
      skills: {
        registry_source: 'https://registry.example/catalog.json',
      },
    });

    expect(result.skills.registry_source).toBe('https://registry.example/catalog.json');
  });
});
