import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';

// Mock the logger so log.warn/error route through console (tests spy on console)
vi.mock('../logger.js', () => ({
  createLogger: () => ({
    fatal: (...args: unknown[]) => console.error(...args),
    error: (...args: unknown[]) => console.error(...args),
    warn: (...args: unknown[]) => console.warn(...args),
    info: (...args: unknown[]) => console.log(...args),
    debug: (...args: unknown[]) => console.log(...args),
    trace: (...args: unknown[]) => console.log(...args),
    pino: {},
  }),
}));

import {
  normalizeAgents,
  canonicalizeServerMode,
  isApiServerMode,
  isDockerServerMode,
  type LettaBotConfig,
  type AgentConfig,
} from './types.js';

describe('normalizeAgents', () => {
  const envVars = [
    'TELEGRAM_BOT_TOKEN', 'TELEGRAM_DM_POLICY', 'TELEGRAM_ALLOWED_USERS',
    'SLACK_BOT_TOKEN', 'SLACK_APP_TOKEN', 'SLACK_DM_POLICY', 'SLACK_ALLOWED_USERS',
    'WHATSAPP_ENABLED', 'WHATSAPP_SELF_CHAT_MODE', 'WHATSAPP_DM_POLICY', 'WHATSAPP_ALLOWED_USERS',
    'SIGNAL_PHONE_NUMBER', 'SIGNAL_SELF_CHAT_MODE', 'SIGNAL_READ_RECEIPTS', 'SIGNAL_DM_POLICY', 'SIGNAL_ALLOWED_USERS',
    'DISCORD_BOT_TOKEN', 'DISCORD_DM_POLICY', 'DISCORD_ALLOWED_USERS',
    'BLUESKY_WANTED_DIDS', 'BLUESKY_WANTED_COLLECTIONS', 'BLUESKY_JETSTREAM_URL', 'BLUESKY_CURSOR',
    'BLUESKY_HANDLE', 'BLUESKY_APP_PASSWORD', 'BLUESKY_SERVICE_URL', 'BLUESKY_APPVIEW_URL',
    'BLUESKY_NOTIFICATIONS_ENABLED', 'BLUESKY_NOTIFICATIONS_INTERVAL_SEC', 'BLUESKY_NOTIFICATIONS_LIMIT',
    'BLUESKY_NOTIFICATIONS_PRIORITY', 'BLUESKY_NOTIFICATIONS_REASONS',
    'HEARTBEAT_ENABLED', 'HEARTBEAT_INTERVAL_MIN', 'HEARTBEAT_SKIP_RECENT_USER_MIN',
    'SLEEPTIME_TRIGGER', 'SLEEPTIME_BEHAVIOR', 'SLEEPTIME_STEP_COUNT',
    'CRON_ENABLED',
  ];
  const savedEnv: Record<string, string | undefined> = {};

  beforeEach(() => {
    for (const key of envVars) {
      savedEnv[key] = process.env[key];
      delete process.env[key];
    }
  });

  afterEach(() => {
    for (const key of envVars) {
      if (savedEnv[key] !== undefined) {
        process.env[key] = savedEnv[key];
      } else {
        delete process.env[key];
      }
    }
  });

  it('canonicalizes legacy server mode aliases', () => {
    expect(canonicalizeServerMode('cloud')).toBe('api');
    expect(canonicalizeServerMode('api')).toBe('api');
    expect(canonicalizeServerMode('selfhosted')).toBe('docker');
    expect(canonicalizeServerMode('docker')).toBe('docker');
    expect(isApiServerMode('cloud')).toBe(true);
    expect(isDockerServerMode('selfhosted')).toBe(true);
  });

  it('should normalize legacy single-agent config to one-entry array', () => {
    const config: LettaBotConfig = {
      server: { mode: 'cloud' },
      agent: {
        name: 'TestBot',
        model: 'anthropic/claude-sonnet-4',
      },
      channels: {
        telegram: {
          enabled: true,
          token: 'test-token',
        },
      },
    };

    const agents = normalizeAgents(config);

    expect(agents).toHaveLength(1);
    expect(agents[0].name).toBe('TestBot');
    expect(agents[0].model).toBe('anthropic/claude-sonnet-4');
    expect(agents[0].channels.telegram?.token).toBe('test-token');
  });

  it('should drop channels with enabled: false', () => {
    const config: LettaBotConfig = {
      server: { mode: 'cloud' },
      agent: { name: 'TestBot', model: 'test' },
      channels: {
        telegram: {
          enabled: true,
          token: 'test-token',
        },
        slack: {
          enabled: false,
          botToken: 'should-be-dropped',
        },
      },
    };

    const agents = normalizeAgents(config);

    expect(agents[0].channels.telegram).toBeDefined();
    expect(agents[0].channels.slack).toBeUndefined();
  });

  it('should normalize multi-agent config channels', () => {
    const agentsArray: AgentConfig[] = [
      {
        name: 'Bot1',
        channels: {
          telegram: { enabled: true, token: 'token1' },
          slack: { enabled: true, botToken: 'missing-app-token' },
        },
      },
      {
        name: 'Bot2',
        channels: {
          slack: { enabled: true, botToken: 'token2', appToken: 'app2' },
          discord: { enabled: false, token: 'disabled' },
        },
      },
    ];

    const config: LettaBotConfig = {
      server: { mode: 'cloud' },
      agents: agentsArray,
      // Legacy fields (ignored when agents[] is present)
      agent: { name: 'Unused', model: 'unused' },
      channels: {},
    };

    const agents = normalizeAgents(config);

    expect(agents).toHaveLength(2);
    expect(agents[0].channels.telegram?.token).toBe('token1');
    expect(agents[0].channels.slack).toBeUndefined();
    expect(agents[1].channels.slack?.botToken).toBe('token2');
    expect(agents[1].channels.discord).toBeUndefined();
  });

  it('should produce empty channels object when no channels configured', () => {
    const config: LettaBotConfig = {
      server: { mode: 'cloud' },
      agent: { name: 'TestBot', model: 'test' },
      channels: {},
    };

    const agents = normalizeAgents(config);

    expect(agents[0].channels).toEqual({});
  });

  it('should default agent name to "LettaBot" when not provided', () => {
    const config: LettaBotConfig = {
      server: { mode: 'cloud' },
      agent: { name: '', model: '' }, // Empty name should fall back to 'LettaBot'
      channels: {},
    };

    // Override with empty name to test default
    const agents = normalizeAgents({
      ...config,
      agent: undefined as any, // Test fallback when agent is missing
    });

    expect(agents[0].name).toBe('LettaBot');
  });

  it('should drop channels without required credentials', () => {
    const config: LettaBotConfig = {
      server: { mode: 'cloud' },
      agent: { name: 'TestBot', model: 'test' },
      channels: {
        telegram: {
          enabled: true,
          // Missing token
        },
        slack: {
          enabled: true,
          botToken: 'has-bot-token-only',
          // Missing appToken
        },
        signal: {
          enabled: true,
          // Missing phone
        },
        discord: {
          enabled: true,
          // Missing token
        },
      },
    };

    const agents = normalizeAgents(config);

    expect(agents[0].channels).toEqual({});
  });

  it('should preserve agent id when provided', () => {
    const config: LettaBotConfig = {
      server: { mode: 'cloud' },
      agent: {
        id: 'agent-123',
        name: 'TestBot',
        model: 'test',
      },
      channels: {},
    };

    const agents = normalizeAgents(config);

    expect(agents[0].id).toBe('agent-123');
  });

  it('should normalize legacy listeningGroups + requireMention to groups.mode and warn', () => {
    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

    const config: LettaBotConfig = {
      server: { mode: 'cloud' },
      agent: { name: 'TestBot' },
      channels: {
        telegram: {
          enabled: true,
          token: 'test-token',
          listeningGroups: ['-100123', '-100456'],
          groups: {
            '*': { requireMention: true },
            '-100456': { requireMention: false },
          },
        },
      },
    };

    const agents = normalizeAgents(config);
    const groups = agents[0].channels.telegram?.groups;

    expect(groups?.['*']?.mode).toBe('mention-only');
    expect(groups?.['-100123']?.mode).toBe('listen');
    expect(groups?.['-100456']?.mode).toBe('listen');
    expect((agents[0].channels.telegram as any).listeningGroups).toBeUndefined();
    expect(
      warnSpy.mock.calls.some((args) => String(args[0]).includes('listeningGroups'))
    ).toBe(true);
    expect(
      warnSpy.mock.calls.some((args) => String(args[0]).includes('requireMention'))
    ).toBe(true);

    warnSpy.mockRestore();
  });

  it('should preserve legacy listeningGroups semantics by adding wildcard open', () => {
    const config: LettaBotConfig = {
      server: { mode: 'cloud' },
      agent: { name: 'TestBot' },
      channels: {
        discord: {
          enabled: true,
          token: 'discord-token',
          listeningGroups: ['1234567890'],
        },
      },
    };

    const agents = normalizeAgents(config);
    const groups = agents[0].channels.discord?.groups;

    expect(groups?.['*']?.mode).toBe('open');
    expect(groups?.['1234567890']?.mode).toBe('listen');
  });

  describe('env var fallback (container deploys)', () => {
    it('should pick up channels from env vars when YAML has none', () => {
      process.env.TELEGRAM_BOT_TOKEN = 'env-telegram-token';
      process.env.DISCORD_BOT_TOKEN = 'env-discord-token';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {},
      };

      const agents = normalizeAgents(config);

      expect(agents[0].channels.telegram?.token).toBe('env-telegram-token');
      expect(agents[0].channels.discord?.token).toBe('env-discord-token');
    });

    it('should not override YAML channels with env vars', () => {
      process.env.TELEGRAM_BOT_TOKEN = 'env-token';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {
          telegram: { enabled: true, token: 'yaml-token' },
        },
      };

      const agents = normalizeAgents(config);

      expect(agents[0].channels.telegram?.token).toBe('yaml-token');
    });

    it('should merge env var credential into YAML block missing it', () => {
      process.env.SIGNAL_PHONE_NUMBER = '+15551234567';
      process.env.DISCORD_BOT_TOKEN = 'env-discord-token';
      process.env.TELEGRAM_BOT_TOKEN = 'env-tg-token';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {
          signal: { enabled: true, selfChat: true, dmPolicy: 'pairing' },
          discord: { enabled: true, dmPolicy: 'open' },
          telegram: { enabled: true, dmPolicy: 'pairing' },
        },
      };

      const agents = normalizeAgents(config);

      // Env var should fill in the missing credential
      expect(agents[0].channels.signal?.phone).toBe('+15551234567');
      expect(agents[0].channels.signal?.dmPolicy).toBe('pairing');
      expect(agents[0].channels.discord?.token).toBe('env-discord-token');
      expect(agents[0].channels.discord?.dmPolicy).toBe('open');
      expect(agents[0].channels.telegram?.token).toBe('env-tg-token');
    });

    it('should merge env var credential into YAML block missing it', () => {
      process.env.SIGNAL_PHONE_NUMBER = '+15551234567';
      process.env.DISCORD_BOT_TOKEN = 'env-discord-token';
      process.env.TELEGRAM_BOT_TOKEN = 'env-tg-token';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {
          signal: { enabled: true, selfChat: true, dmPolicy: 'pairing' },
          discord: { enabled: true, dmPolicy: 'open' },
          telegram: { enabled: true, dmPolicy: 'pairing' },
        },
      };

      const agents = normalizeAgents(config);

      // Env var should fill in the missing credential
      expect(agents[0].channels.signal?.phone).toBe('+15551234567');
      expect(agents[0].channels.signal?.dmPolicy).toBe('pairing');
      expect(agents[0].channels.discord?.token).toBe('env-discord-token');
      expect(agents[0].channels.discord?.dmPolicy).toBe('open');
      expect(agents[0].channels.telegram?.token).toBe('env-tg-token');
    });

    it('should not apply env vars in multi-agent mode', () => {
      process.env.TELEGRAM_BOT_TOKEN = 'env-token';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agents: [{ name: 'Bot1', channels: {} }],
        agent: { name: 'Unused', model: 'unused' },
        channels: {},
      };

      const agents = normalizeAgents(config);

      expect(agents[0].channels.telegram).toBeUndefined();
    });

    it('should pick up heartbeat from env vars when YAML features is empty', () => {
      process.env.HEARTBEAT_ENABLED = 'true';
      process.env.HEARTBEAT_INTERVAL_MIN = '15';
      process.env.HEARTBEAT_SKIP_RECENT_USER_MIN = '5';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {},
      };

      const agents = normalizeAgents(config);

      expect(agents[0].features?.heartbeat).toEqual({
        enabled: true,
        intervalMin: 15,
        skipRecentUserMin: 5,
      });
    });

    it('should pick up sleeptime from env vars when YAML features is empty', () => {
      process.env.SLEEPTIME_TRIGGER = 'step-count';
      process.env.SLEEPTIME_BEHAVIOR = 'reminder';
      process.env.SLEEPTIME_STEP_COUNT = '25';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {},
      };

      const agents = normalizeAgents(config);

      expect(agents[0].features?.sleeptime).toEqual({
        trigger: 'step-count',
        behavior: 'reminder',
        stepCount: 25,
      });
    });

    it('should pick up cron from env vars when YAML features is empty', () => {
      process.env.CRON_ENABLED = 'true';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {},
      };

      const agents = normalizeAgents(config);

      expect(agents[0].features?.cron).toBe(true);
    });

    it('should merge env var heartbeat into existing YAML features', () => {
      process.env.HEARTBEAT_ENABLED = 'true';
      process.env.HEARTBEAT_INTERVAL_MIN = '20';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {},
        features: {
          cron: true,
          maxToolCalls: 50,
        },
      };

      const agents = normalizeAgents(config);

      // Env var heartbeat should merge in
      expect(agents[0].features?.heartbeat).toEqual({
        enabled: true,
        intervalMin: 20,
      });
      // Existing YAML features should be preserved
      expect(agents[0].features?.cron).toBe(true);
      expect(agents[0].features?.maxToolCalls).toBe(50);
    });

    it('should merge env var sleeptime into existing YAML features', () => {
      process.env.SLEEPTIME_TRIGGER = 'compaction-event';
      process.env.SLEEPTIME_BEHAVIOR = 'auto-launch';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {},
        features: {
          cron: true,
          maxToolCalls: 50,
        },
      };

      const agents = normalizeAgents(config);

      expect(agents[0].features?.sleeptime).toEqual({
        trigger: 'compaction-event',
        behavior: 'auto-launch',
      });
      expect(agents[0].features?.cron).toBe(true);
      expect(agents[0].features?.maxToolCalls).toBe(50);
    });

    it('should not override YAML heartbeat with env vars', () => {
      process.env.HEARTBEAT_ENABLED = 'true';
      process.env.HEARTBEAT_INTERVAL_MIN = '99';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {},
        features: {
          heartbeat: {
            enabled: true,
            intervalMin: 10,
            skipRecentUserMin: 3,
          },
        },
      };

      const agents = normalizeAgents(config);

      // YAML values should win
      expect(agents[0].features?.heartbeat?.intervalMin).toBe(10);
      expect(agents[0].features?.heartbeat?.skipRecentUserMin).toBe(3);
    });

    it('should not override YAML sleeptime with env vars', () => {
      process.env.SLEEPTIME_TRIGGER = 'step-count';
      process.env.SLEEPTIME_BEHAVIOR = 'reminder';
      process.env.SLEEPTIME_STEP_COUNT = '99';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {},
        features: {
          sleeptime: {
            trigger: 'compaction-event',
            behavior: 'auto-launch',
            stepCount: 10,
          },
        },
      };

      const agents = normalizeAgents(config);

      expect(agents[0].features?.sleeptime).toEqual({
        trigger: 'compaction-event',
        behavior: 'auto-launch',
        stepCount: 10,
      });
    });

    it('should handle heartbeat env var with defaults when interval not set', () => {
      process.env.HEARTBEAT_ENABLED = 'true';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {},
      };

      const agents = normalizeAgents(config);

      expect(agents[0].features?.heartbeat).toEqual({ enabled: true });
    });

    it('should not override YAML cron: false with env var', () => {
      process.env.CRON_ENABLED = 'true';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {},
        features: {
          cron: false,
        },
      };

      const agents = normalizeAgents(config);

      expect(agents[0].features?.cron).toBe(false);
    });

    it('should not enable heartbeat when env var is not true', () => {
      process.env.HEARTBEAT_ENABLED = 'false';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {},
      };

      const agents = normalizeAgents(config);

      expect(agents[0].features?.heartbeat).toBeUndefined();
    });

    it('should pick up all channel types from env vars', () => {
      process.env.TELEGRAM_BOT_TOKEN = 'tg-token';
      process.env.SLACK_BOT_TOKEN = 'slack-bot';
      process.env.SLACK_APP_TOKEN = 'slack-app';
      process.env.WHATSAPP_ENABLED = 'true';
      process.env.SIGNAL_PHONE_NUMBER = '+1234567890';
      process.env.DISCORD_BOT_TOKEN = 'discord-token';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {},
      };

      const agents = normalizeAgents(config);

      expect(agents[0].channels.telegram?.token).toBe('tg-token');
      expect(agents[0].channels.slack?.botToken).toBe('slack-bot');
      expect(agents[0].channels.slack?.appToken).toBe('slack-app');
      expect(agents[0].channels.whatsapp?.enabled).toBe(true);
      expect(agents[0].channels.signal?.phone).toBe('+1234567890');
      expect(agents[0].channels.signal?.readReceipts).toBe(true);
      expect(agents[0].channels.discord?.token).toBe('discord-token');
    });

    it('should allow disabling Signal read receipts via env var', () => {
      process.env.SIGNAL_PHONE_NUMBER = '+1234567890';
      process.env.SIGNAL_READ_RECEIPTS = 'false';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {},
      };

      const agents = normalizeAgents(config);

      expect(agents[0].channels.signal?.readReceipts).toBe(false);
    });

    it('should pick up allowedUsers from env vars for all channels', () => {
      process.env.TELEGRAM_BOT_TOKEN = 'tg-token';
      process.env.TELEGRAM_DM_POLICY = 'allowlist';
      process.env.TELEGRAM_ALLOWED_USERS = '515978553, 123456';

      process.env.SLACK_BOT_TOKEN = 'slack-bot';
      process.env.SLACK_APP_TOKEN = 'slack-app';
      process.env.SLACK_DM_POLICY = 'allowlist';
      process.env.SLACK_ALLOWED_USERS = 'U123,U456';

      process.env.DISCORD_BOT_TOKEN = 'discord-token';
      process.env.DISCORD_DM_POLICY = 'allowlist';
      process.env.DISCORD_ALLOWED_USERS = '999888777';

      process.env.WHATSAPP_ENABLED = 'true';
      process.env.WHATSAPP_DM_POLICY = 'allowlist';
      process.env.WHATSAPP_ALLOWED_USERS = '+1234567890,+0987654321';

      process.env.SIGNAL_PHONE_NUMBER = '+1555000000';
      process.env.SIGNAL_DM_POLICY = 'allowlist';
      process.env.SIGNAL_ALLOWED_USERS = '+1555111111';

      const config: LettaBotConfig = {
        server: { mode: 'cloud' },
        agent: { name: 'TestBot', model: 'test' },
        channels: {},
      };

      const agents = normalizeAgents(config);

      expect(agents[0].channels.telegram?.dmPolicy).toBe('allowlist');
      expect(agents[0].channels.telegram?.allowedUsers).toEqual(['515978553', '123456']);

      expect(agents[0].channels.slack?.dmPolicy).toBe('allowlist');
      expect(agents[0].channels.slack?.allowedUsers).toEqual(['U123', 'U456']);

      expect(agents[0].channels.discord?.dmPolicy).toBe('allowlist');
      expect(agents[0].channels.discord?.allowedUsers).toEqual(['999888777']);

      expect(agents[0].channels.whatsapp?.dmPolicy).toBe('allowlist');
      expect(agents[0].channels.whatsapp?.allowedUsers).toEqual(['+1234567890', '+0987654321']);

      expect(agents[0].channels.signal?.dmPolicy).toBe('allowlist');
      expect(agents[0].channels.signal?.allowedUsers).toEqual(['+1555111111']);
    });
  });

  it('should preserve features, polling, and integrations', () => {
    const config: LettaBotConfig = {
      server: { mode: 'cloud' },
      agent: { name: 'TestBot', model: 'test' },
      channels: {},
      features: {
        cron: true,
        heartbeat: {
          enabled: true,
          intervalMin: 10,
          skipRecentUserMin: 3,
        },
        maxToolCalls: 50,
      },
      polling: {
        enabled: true,
        intervalMs: 30000,
      },
      integrations: {
        google: {
          enabled: true,
          account: 'test@example.com',
        },
      },
    };

    const agents = normalizeAgents(config);

    expect(agents[0].features).toEqual(config.features);
    expect(agents[0].polling).toEqual(config.polling);
    expect(agents[0].integrations).toEqual(config.integrations);
  });

  it('should pass through displayName', () => {
    const config: LettaBotConfig = {
      server: { mode: 'cloud' },
      agent: {
        name: 'Signo',
        displayName: '💜 Signo',
      },
      channels: {
        telegram: { enabled: true, token: 'test-token' },
      },
    };

    const agents = normalizeAgents(config);

    expect(agents[0].displayName).toBe('💜 Signo');
  });

  it('should pass through displayName in multi-agent config', () => {
    const agentsArray: AgentConfig[] = [
      {
        name: 'Signo',
        displayName: '💜 Signo',
        channels: { telegram: { enabled: true, token: 't1' } },
      },
      {
        name: 'DevOps',
        displayName: '👾 DevOps',
        channels: { discord: { enabled: true, token: 'd1' } },
      },
    ];

    const config = {
      server: { mode: 'cloud' as const },
      agents: agentsArray,
    } as LettaBotConfig;

    const agents = normalizeAgents(config);

    expect(agents[0].displayName).toBe('💜 Signo');
    expect(agents[1].displayName).toBe('👾 DevOps');
  });

  it('should pass through conversations config in legacy mode', () => {
    const config: LettaBotConfig = {
      server: { mode: 'cloud' },
      agent: { name: 'TestBot' },
      channels: {},
      conversations: {
        mode: 'per-channel',
        heartbeat: 'dedicated',
      },
    };

    const agents = normalizeAgents(config);

    expect(agents[0].conversations?.mode).toBe('per-channel');
    expect(agents[0].conversations?.heartbeat).toBe('dedicated');
  });

  it('should pass through conversations as undefined when not set', () => {
    const config: LettaBotConfig = {
      server: { mode: 'cloud' },
      agent: { name: 'TestBot' },
      channels: {},
    };

    const agents = normalizeAgents(config);

    expect(agents[0].conversations).toBeUndefined();
  });

  it('should normalize onboarding-generated agents[] config (no legacy agent/channels)', () => {
    // This matches the shape that onboarding now writes: agents[] at top level,
    // with no legacy agent/channels/features fields.
    const config = {
      server: { mode: 'cloud' as const },
      agents: [{
        name: 'LettaBot',
        id: 'agent-abc123',
        channels: {
          telegram: { enabled: true, token: 'tg-token', dmPolicy: 'pairing' as const },
          whatsapp: { enabled: true, selfChat: true },
        },
        features: {
          cron: true,
          heartbeat: { enabled: true, intervalMin: 30 },
        },
      }],
      // loadConfig() merges defaults for agent/channels, so they'll exist at runtime
      agent: { name: 'LettaBot' },
      channels: {},
    } as LettaBotConfig;

    const agents = normalizeAgents(config);

    expect(agents).toHaveLength(1);
    expect(agents[0].name).toBe('LettaBot');
    expect(agents[0].id).toBe('agent-abc123');
    expect(agents[0].channels.telegram?.token).toBe('tg-token');
    expect(agents[0].channels.whatsapp?.enabled).toBe(true);
    expect(agents[0].features?.cron).toBe(true);
    expect(agents[0].features?.heartbeat?.intervalMin).toBe(30);
  });
});
