import { afterEach, describe, expect, it, vi } from 'vitest';
import { configSchema } from '../config/schema.js';
import { Lifecycle } from './lifecycle.js';
import { startServices } from './services.js';

vi.mock('../automation/index.js', () => {
  return {
    HeartbeatMonitor: class {
      start(): void {}
      stop(): void {}
    },
    MinioSyncScheduler: class {
      start(): void {}
      stop(): void {}
    },
  };
});

function makeConfig(overrides: Record<string, unknown> = {}) {
  return configSchema.parse({
    telegram: { bot_token: 'test-token', allowed_chat_ids: [1] },
    models: { default: { provider: 'anthropic', model: 'claude-3' } },
    ...overrides,
  });
}

describe('startServices startup ordering', () => {
  afterEach(async () => {
    vi.restoreAllMocks();
  });

  it('fails after bounded retries on persistent gateway bind collision before starting channels', async () => {
    const lifecycle = new Lifecycle();
    const config = makeConfig({ server: { localhost: true, port: 18800 } });
    const startError = new Error('listen EADDRINUSE: address already in use 127.0.0.1:18800') as Error & { code?: string };
    startError.code = 'EADDRINUSE';

    const channelRegistry = {
      startAll: vi.fn(async () => {}),
      stopAll: vi.fn(async () => {}),
    };
    const gateway = {
      start: vi.fn(async () => { throw startError; }),
      stop: vi.fn(async () => {}),
      getMetrics: vi.fn(() => ({ getModelMetrics: () => [] })),
    };

    await expect(startServices({
      config,
      lifecycle,
      channelRegistry: channelRegistry as never,
      gateway: gateway as never,
      modelRouter: {} as never,
      memoryDir: '/tmp',
      dataDir: '/tmp',
      gatewayStartRetry: {
        maxAttempts: 3,
        retryDelayMs: 0,
        sleep: async () => {},
      },
    })).rejects.toThrow('Gateway bind failed');

    expect(channelRegistry.startAll).not.toHaveBeenCalled();
    expect(channelRegistry.stopAll).not.toHaveBeenCalled();
    expect(gateway.start).toHaveBeenCalledTimes(3);
    expect(gateway.stop).toHaveBeenCalledTimes(3);
  });

  it('retries gateway bind collisions and then starts channels on success', async () => {
    const lifecycle = new Lifecycle();
    const config = makeConfig({ server: { localhost: true, port: 18800 } });
    const startError = new Error('listen EADDRINUSE: address already in use 127.0.0.1:18800') as Error & { code?: string };
    startError.code = 'EADDRINUSE';
    const order: string[] = [];

    const channelRegistry = {
      startAll: vi.fn(async () => { order.push('channels.start'); }),
      stopAll: vi.fn(async () => {}),
    };
    const gateway = {
      start: vi.fn(async () => {
        order.push('gateway.start');
        if (gateway.start.mock.calls.length === 1) {
          throw startError;
        }
      }),
      stop: vi.fn(async () => { order.push('gateway.stop'); }),
      getMetrics: vi.fn(() => ({ getModelMetrics: () => [] })),
    };

    await startServices({
      config,
      lifecycle,
      channelRegistry: channelRegistry as never,
      gateway: gateway as never,
      modelRouter: {} as never,
      memoryDir: '/tmp',
      dataDir: '/tmp',
      gatewayStartRetry: {
        maxAttempts: 3,
        retryDelayMs: 0,
        sleep: async () => {},
      },
    });

    expect(order).toEqual(['gateway.start', 'gateway.stop', 'gateway.start', 'channels.start']);
    expect(channelRegistry.startAll).toHaveBeenCalledOnce();
    await lifecycle.shutdown();
  });

  it('starts gateway before channels when startup succeeds', async () => {
    const lifecycle = new Lifecycle();
    const config = makeConfig({ server: { localhost: true, port: 18800 } });
    const order: string[] = [];

    const channelRegistry = {
      startAll: vi.fn(async () => { order.push('channels.start'); }),
      stopAll: vi.fn(async () => {}),
    };
    const gateway = {
      start: vi.fn(async () => { order.push('gateway.start'); }),
      stop: vi.fn(async () => {}),
      getMetrics: vi.fn(() => ({ getModelMetrics: () => [] })),
    };

    await startServices({
      config,
      lifecycle,
      channelRegistry: channelRegistry as never,
      gateway: gateway as never,
      modelRouter: {} as never,
      memoryDir: '/tmp',
      dataDir: '/tmp',
    });

    expect(order).toEqual(['gateway.start', 'channels.start']);
    await lifecycle.shutdown();
  });
});
