import { describe, it, expect } from 'vitest';
import { discoverServices } from './services.js';
import { ChannelRegistry } from '../../channels/registry.js';
import type { Config } from '../../config/index.js';
import type { CronJobConfig } from '../../config/schema.js';

function withMutableConfig(config: Config): Config & Record<string, unknown> {
  return config as Config & Record<string, unknown>;
}

function makeBaseConfig(): Config {
  return {
    server: { localhost: true, port: 18800 },
    models: { default: { provider: 'anthropic', model: 'claude-sonnet-4', api_key: 'sk-test' }, fallback_chain: [] },
    backends: { native: { enabled: true }, opencode: { enabled: false }, claude_code: { enabled: false } },
    hooks: { confirm: [], log: [], silent: [] },
    mcp: { servers: [] },
    backup: {
      enabled: false,
      schedule: undefined,
      interval: '24h',
      run_on_start: false,
      notify: undefined,
      failure_threshold: 1,
      notify_recovery: true,
      local_dir: '~/.local/share/flynn/backups',
      include_vectors: true,
      minio: {
        enabled: false,
        endpoint: undefined,
        access_key: undefined,
        secret_key: undefined,
        bucket: undefined,
        prefix: 'flynn',
        secure: true,
      },
    },
    automation: {
      cron: [],
      webhooks: [],
      gmail: undefined,
      heartbeat: undefined,
      gcal: undefined,
      gdocs: undefined,
      gdrive: undefined,
      gtasks: undefined,
    },
    telegram: undefined,
    discord: undefined,
    slack: undefined,
    whatsapp: undefined,
    matrix: undefined,
    signal: undefined,
    mattermost: undefined,
    teams: undefined,
    google_chat: undefined,
    bluebubbles: undefined,
    line: undefined,
    feishu: undefined,
    zalo: undefined,
  } as unknown as Config;
}

describe('discoverServices', () => {
  it('includes known services and marks not_configured when disabled', () => {
    const cfg = makeBaseConfig();
    const reg = new ChannelRegistry();

    const services = discoverServices(cfg, reg);

    expect(services).toEqual(expect.arrayContaining([
      expect.objectContaining({ name: 'telegram', status: 'not_configured' }),
      expect.objectContaining({ name: 'matrix', status: 'not_configured' }),
      expect.objectContaining({ name: 'signal', status: 'not_configured' }),
      expect.objectContaining({ name: 'mattermost', status: 'not_configured' }),
      expect.objectContaining({ name: 'teams', status: 'not_configured' }),
      expect.objectContaining({ name: 'google_chat', status: 'not_configured' }),
      expect.objectContaining({ name: 'bluebubbles', status: 'not_configured' }),
      expect.objectContaining({ name: 'line', status: 'not_configured' }),
      expect.objectContaining({ name: 'feishu', status: 'not_configured' }),
      expect.objectContaining({ name: 'zalo', status: 'not_configured' }),
      expect.objectContaining({ name: 'cron', status: 'not_configured' }),
      expect.objectContaining({ name: 'daily_briefing', status: 'not_configured' }),
      expect.objectContaining({ name: 'backup', status: 'not_configured' }),
      expect.objectContaining({ name: 'mcp', status: 'not_configured' }),
      expect.objectContaining({ name: 'web_search', status: 'configured' }),
      expect.objectContaining({ name: 'audio_transcription', status: 'not_configured' }),
    ]));
  });

  it('marks configured channels as disconnected when adapter is not registered', () => {
    const cfg = makeBaseConfig();
    withMutableConfig(cfg).telegram = { bot_token: 'x', allowed_chat_ids: [123], require_mention: false };

    const reg = new ChannelRegistry();
    const services = discoverServices(cfg, reg);

    expect(services.find(s => s.name === 'telegram')?.status).toBe('disconnected');
  });

  it('uses adapter status when channel adapter is registered', () => {
    const cfg = makeBaseConfig();
    withMutableConfig(cfg).telegram = { bot_token: 'x', allowed_chat_ids: [123], require_mention: false };

    const reg = new ChannelRegistry();
    reg.register({
      name: 'telegram',
      status: 'connected',
      connect: async () => {},
      disconnect: async () => {},
      send: async () => {},
      onMessage: () => {},
    });

    const services = discoverServices(cfg, reg);
    expect(services.find(s => s.name === 'telegram')?.status).toBe('connected');
  });

  it('surfaces adapter error details when channel is in error state', () => {
    const cfg = makeBaseConfig();
    withMutableConfig(cfg).telegram = { bot_token: 'x', allowed_chat_ids: [123], require_mention: false };

    const reg = new ChannelRegistry();
    reg.register({
      name: 'telegram',
      status: 'error',
      lastError: 'Telegram polling error: 429 Too Many Requests',
      lastErrorAt: 1708080000000,
      connect: async () => {},
      disconnect: async () => {},
      send: async () => {},
      onMessage: () => {},
    });

    const services = discoverServices(cfg, reg);
    const telegram = services.find(s => s.name === 'telegram');
    expect(telegram?.status).toBe('error');
    expect(telegram?.error).toContain('429');
    expect(telegram?.metadata).toMatchObject({ lastErrorAt: 1708080000000 });
  });

  it('marks enabled automation subsystems as configured and carries item counts', () => {
    const cfg = makeBaseConfig();
    cfg.automation.cron = [
      { name: 'job', schedule: '0 0 * * *', message: 'hi', output: { channel: 'webchat', peer: 'x' }, enabled: true },
    ] as CronJobConfig[];
    (cfg.automation as Record<string, unknown>).daily_briefing = {
      enabled: true,
      output: { channel: 'webchat', peer: 'x' },
    };
    cfg.mcp.servers = [{ name: 'srv', command: 'x', args: [] }];
    cfg.backup.enabled = true;
    cfg.backup.schedule = '0 2 * * *';

    const reg = new ChannelRegistry();
    const services = discoverServices(cfg, reg);

    expect(services.find(s => s.name === 'cron')?.status).toBe('configured');
    expect(services.find(s => s.name === 'cron')?.itemCount).toBe(2);
    expect(services.find(s => s.name === 'daily_briefing')?.status).toBe('configured');
    expect(services.find(s => s.name === 'backup')?.status).toBe('configured');
    expect(services.find(s => s.name === 'backup')?.metadata).toMatchObject({ schedule: '0 2 * * *' });
    expect(services.find(s => s.name === 'mcp')?.metadata).toEqual({ serverCount: 1 });
  });

  it('marks audio transcription as configured and includes endpoint metadata', () => {
    const cfg = makeBaseConfig();
    withMutableConfig(cfg).audio = {
      enabled: true,
      provider: {
        type: 'custom',
        endpoint: 'http://localhost:18801/v1/audio/transcriptions',
        model: 'whisper-1',
      },
      talk_mode: {
        enabled: false,
        wake_phrase: 'hey flynn',
        timeout_ms: 120000,
        allow_manual_toggle: true,
      },
    };

    const reg = new ChannelRegistry();
    const services = discoverServices(cfg, reg);

    const audio = services.find(s => s.name === 'audio_transcription');
    expect(audio?.status).toBe('configured');
    expect(audio?.metadata).toMatchObject({
      provider: 'custom',
      endpoint: 'http://localhost:18801/v1/audio/transcriptions',
      model: 'whisper-1',
    });
  });
});
