import { describe, it, expect } from 'vitest';
import type { Config } from '../../config/index.js';
import { listDockerDependencyStatuses, controlDockerDependency } from './dockerDependencies.js';

function createConfig(params?: {
  audioEndpoint?: string;
  audioEnabled?: boolean;
  webSearchProvider?: 'brave' | 'searxng';
  webSearchApiKey?: string;
  webSearchEndpoint?: string;
}): Config {
  return {
    audio: {
      enabled: params?.audioEnabled ?? true,
      provider: {
        type: 'custom',
        endpoint: params?.audioEndpoint ?? 'http://localhost:18801/v1/audio/transcriptions',
      },
    },
    web_search: {
      provider: params?.webSearchProvider ?? 'brave',
      api_key: params?.webSearchApiKey,
      endpoint: params?.webSearchEndpoint,
      max_results: 5,
    },
  } as unknown as Config;
}

function profileArgs(): string[] {
  return ['--profile', 'voice', '--profile', 'search'];
}

describe('listDockerDependencyStatuses', () => {
  it('discovers compose services across profiles and reports whisper + brave statuses', async () => {
    const seenCalls: string[][] = [];
    const runner = async (args: string[]) => {
      seenCalls.push(args);
      if (args.join(' ') === 'config --profiles') {
        return { stdout: 'voice\nsearch\n', stderr: '' };
      }
      if (args.join(' ') === '--profile voice --profile search config --services') {
        return { stdout: 'flynn\nwhisper-server\nbrave-search\n', stderr: '' };
      }
      if (args.join(' ') === '--profile voice --profile search ps --all --format json') {
        return { stdout: '[]', stderr: '' };
      }
      if (args.join(' ') === '--profile voice --profile search ps whisper-server --format json') {
        return {
          stdout: JSON.stringify([{
            Name: 'flynn-whisper-server-1',
            Service: 'whisper-server',
            State: 'running',
            Health: 'healthy',
            Status: 'Up 4 minutes (healthy)',
          }]),
          stderr: '',
        };
      }
      if (args.join(' ') === '--profile voice --profile search ps brave-search --format json') {
        return {
          stdout: JSON.stringify([{
            Name: 'brave-search',
            Service: 'brave-search',
            State: 'running',
            Health: '',
            Status: 'Up 2 minutes',
          }]),
          stderr: '',
        };
      }
      throw new Error(`Unexpected args: ${args.join(' ')}`);
    };

    const statuses = await listDockerDependencyStatuses(createConfig(), runner);
    expect(statuses).toHaveLength(2);

    const whisper = statuses.find((entry) => entry.id === 'whisper');
    expect(whisper).toMatchObject({
      id: 'whisper',
      service: 'whisper-server',
      configured: true,
      state: 'running',
      health: 'healthy',
      statusText: 'Up 4 minutes (healthy)',
      containerName: 'flynn-whisper-server-1',
      availableActions: ['restart', 'stop', 'update'],
    });

    const brave = statuses.find((entry) => entry.id === 'brave-search');
    expect(brave).toMatchObject({
      id: 'brave-search',
      name: 'Brave Search',
      service: 'brave-search',
      configured: false,
      state: 'running',
      statusText: 'Up 2 minutes',
      containerName: 'brave-search',
      availableActions: ['restart', 'stop', 'update'],
    });

    expect(seenCalls[0]).toEqual(['config', '--profiles']);
    expect(seenCalls[1]).toEqual([...profileArgs(), 'config', '--services']);
  });

  it('includes runtime services surfaced by compose ps --all even if not listed in config services', async () => {
    const runner = async (args: string[]) => {
      if (args.join(' ') === 'config --profiles') {
        return { stdout: '', stderr: '' };
      }
      if (args.join(' ') === 'config --services') {
        return { stdout: 'flynn\n', stderr: '' };
      }
      if (args.join(' ') === 'ps --all --format json') {
        return {
          stdout: JSON.stringify([{
            Name: 'brave-search',
            Service: 'brave-search',
            State: 'running',
            Status: 'Up 1 minute',
          }]),
          stderr: '',
        };
      }
      if (args.join(' ') === 'ps brave-search --format json') {
        return {
          stdout: JSON.stringify([{
            Name: 'brave-search',
            Service: 'brave-search',
            State: 'running',
            Status: 'Up 1 minute',
          }]),
          stderr: '',
        };
      }
      throw new Error(`Unexpected args: ${args.join(' ')}`);
    };

    const statuses = await listDockerDependencyStatuses(createConfig(), runner);
    expect(statuses).toHaveLength(1);
    expect(statuses[0]).toMatchObject({
      id: 'brave-search',
      service: 'brave-search',
      state: 'running',
    });
  });

  it('returns empty list when no compose dependencies are defined', async () => {
    const runner = async (args: string[]) => {
      if (args.join(' ') === 'config --profiles') {
        return { stdout: '', stderr: '' };
      }
      if (args.join(' ') === 'config --services') {
        return { stdout: 'flynn\n', stderr: '' };
      }
      if (args.join(' ') === 'ps --all --format json') {
        return { stdout: '[]', stderr: '' };
      }
      throw new Error(`Unexpected args: ${args.join(' ')}`);
    };

    const statuses = await listDockerDependencyStatuses(createConfig(), runner);
    expect(statuses).toEqual([]);
  });

  it('returns unavailable status when docker compose discovery fails', async () => {
    const runner = async () => {
      throw Object.assign(new Error('spawn docker ENOENT'), { stderr: 'docker: command not found' });
    };

    const statuses = await listDockerDependencyStatuses(createConfig(), runner);
    expect(statuses).toHaveLength(1);
    expect(statuses[0]).toMatchObject({
      id: 'compose',
      state: 'unavailable',
      statusText: 'unavailable',
      availableActions: [],
    });
    expect(statuses[0]?.error).toContain('docker: command not found');
  });

  it('marks whisper as not configured for non-local transcription endpoints', async () => {
    const runner = async (args: string[]) => {
      if (args.join(' ') === 'config --profiles') {
        return { stdout: 'voice\n', stderr: '' };
      }
      if (args.join(' ') === '--profile voice config --services') {
        return { stdout: 'flynn\nwhisper-server\n', stderr: '' };
      }
      if (args.join(' ') === '--profile voice ps --all --format json') {
        return { stdout: '[]', stderr: '' };
      }
      if (args.join(' ') === '--profile voice ps whisper-server --format json') {
        return { stdout: '[]', stderr: '' };
      }
      throw new Error(`Unexpected args: ${args.join(' ')}`);
    };

    const statuses = await listDockerDependencyStatuses(
      createConfig({ audioEndpoint: 'https://api.openai.com/v1/audio/transcriptions' }),
      runner,
    );
    expect(statuses[0]?.id).toBe('whisper');
    expect(statuses[0]?.configured).toBe(false);
  });

  it('labels searxng dependency and marks configured when searxng endpoint is set', async () => {
    const runner = async (args: string[]) => {
      if (args.join(' ') === 'config --profiles') {
        return { stdout: 'search\n', stderr: '' };
      }
      if (args.join(' ') === '--profile search config --services') {
        return { stdout: 'flynn\nsearxng\n', stderr: '' };
      }
      if (args.join(' ') === '--profile search ps --all --format json') {
        return { stdout: '[]', stderr: '' };
      }
      if (args.join(' ') === '--profile search ps searxng --format json') {
        return {
          stdout: JSON.stringify([{
            Name: 'searxng',
            Service: 'searxng',
            State: 'running',
            Status: 'Up 5 minutes',
          }]),
          stderr: '',
        };
      }
      throw new Error(`Unexpected args: ${args.join(' ')}`);
    };

    const statuses = await listDockerDependencyStatuses(
      createConfig({ webSearchProvider: 'searxng', webSearchEndpoint: 'http://searxng:8080' }),
      runner,
    );
    expect(statuses).toHaveLength(1);
    expect(statuses[0]).toMatchObject({
      id: 'searxng',
      name: 'SearXNG',
      service: 'searxng',
      configured: true,
      state: 'running',
      statusText: 'Up 5 minutes',
    });
  });
});

describe('controlDockerDependency', () => {
  it('starts whisper via compose up and returns refreshed status', async () => {
    const calls: string[][] = [];
    const runner = async (args: string[]) => {
      calls.push(args);
      if (args.join(' ') === 'config --profiles') {
        return { stdout: 'voice\nsearch\n', stderr: '' };
      }
      if (args.join(' ') === '--profile voice --profile search config --services') {
        return { stdout: 'flynn\nwhisper-server\nbrave-search\n', stderr: '' };
      }
      if (args.join(' ') === '--profile voice --profile search ps --all --format json') {
        return { stdout: '[]', stderr: '' };
      }
      if (args.join(' ') === '--profile voice --profile search up -d whisper-server') {
        return { stdout: '', stderr: '' };
      }
      if (args.join(' ') === '--profile voice --profile search ps whisper-server --format json') {
        return {
          stdout: JSON.stringify([{
            Name: 'whisper-server',
            Service: 'whisper-server',
            State: 'running',
            Health: 'healthy',
            Status: 'Up 2 seconds (healthy)',
          }]),
          stderr: '',
        };
      }
      if (args.join(' ') === '--profile voice --profile search ps brave-search --format json') {
        return { stdout: '[]', stderr: '' };
      }
      throw new Error(`Unexpected args: ${args.join(' ')}`);
    };

    const result = await controlDockerDependency(
      createConfig(),
      'whisper',
      'start',
      runner,
    );
    expect(result.action).toBe('start');
    expect(result.dependency).toBe('whisper');
    expect(result.status.state).toBe('running');
    expect(result.status.availableActions).toEqual(['restart', 'stop', 'update']);
    expect(result.message).toContain('Started whisper-server');
    expect(calls).toContainEqual([...profileArgs(), 'up', '-d', 'whisper-server']);
  });

  it('updates brave-search by pulling image then reconciling container', async () => {
    const calls: string[][] = [];
    const runner = async (args: string[]) => {
      calls.push(args);
      if (args.join(' ') === 'config --profiles') {
        return { stdout: 'voice\nsearch\n', stderr: '' };
      }
      if (args.join(' ') === '--profile voice --profile search config --services') {
        return { stdout: 'flynn\nwhisper-server\nbrave-search\n', stderr: '' };
      }
      if (args.join(' ') === '--profile voice --profile search ps --all --format json') {
        return { stdout: '[]', stderr: '' };
      }
      if (args.join(' ') === '--profile voice --profile search pull brave-search') {
        return { stdout: 'Pulled', stderr: '' };
      }
      if (args.join(' ') === '--profile voice --profile search up -d brave-search') {
        return { stdout: 'Started', stderr: '' };
      }
      if (args.join(' ') === '--profile voice --profile search ps whisper-server --format json') {
        return { stdout: '[]', stderr: '' };
      }
      if (args.join(' ') === '--profile voice --profile search ps brave-search --format json') {
        return {
          stdout: JSON.stringify([{
            Name: 'brave-search',
            Service: 'brave-search',
            State: 'running',
            Status: 'Up 1 minute',
          }]),
          stderr: '',
        };
      }
      throw new Error(`Unexpected args: ${args.join(' ')}`);
    };

    const result = await controlDockerDependency(
      createConfig(),
      'brave-search',
      'update',
      runner,
    );
    expect(result.action).toBe('update');
    expect(result.dependency).toBe('brave-search');
    expect(result.message).toContain('Pulled latest brave-search image');
    expect(calls).toContainEqual([...profileArgs(), 'pull', 'brave-search']);
    expect(calls).toContainEqual([...profileArgs(), 'up', '-d', 'brave-search']);
  });

  it('rejects unsupported dependency ids', async () => {
    const runner = async (args: string[]) => {
      if (args.join(' ') === 'config --profiles') {
        return { stdout: 'voice\n', stderr: '' };
      }
      if (args.join(' ') === '--profile voice config --services') {
        return { stdout: 'flynn\nwhisper-server\n', stderr: '' };
      }
      if (args.join(' ') === '--profile voice ps --all --format json') {
        return { stdout: '[]', stderr: '' };
      }
      throw new Error(`Unexpected args: ${args.join(' ')}`);
    };

    await expect(controlDockerDependency(
      createConfig(),
      'not-real',
      'restart',
      runner,
    )).rejects.toThrow('Unsupported dependency');
  });
});
