/**
 * Unified wizard integration test — exercises the `skipGateway: false` code
 * path so that wiring between `runWizard` and the two gateway stages is
 * covered. The gateway stages themselves are mocked (they require a real
 * daemon + network) but the dynamic imports and option plumbing are real.
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync, cpSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { HeadlessPrompter } from '../../src/prompter/headless-prompter.js';
import { createConfigService } from '../../src/config/config-service.js';
import type { SelectOption } from '../../src/prompter/interface.js';
import type { MenuSection, WizardState } from '../../src/types.js';

const gatewayConfigMock = vi.fn();
const gatewayBootstrapMock = vi.fn();
const providerSetupMock = vi.fn();
const skillsSelectMock = vi.fn();

class SequencedMenuPrompter extends HeadlessPrompter {
  constructor(
    answers: Record<string, string | boolean | string[]>,
    private readonly menuChoices: string[],
  ) {
    super(answers);
  }

  override async select<T>(opts: {
    message: string;
    options: SelectOption<T>[];
    initialValue?: T;
  }): Promise<T> {
    if (opts.message === 'What would you like to configure?') {
      const next = this.menuChoices.shift();
      if (!next) throw new Error('No queued menu choice left');
      const match = opts.options.find((o) => String(o.value) === next);
      if (!match) throw new Error(`Queued menu choice not available: ${next}`);
      return match.value;
    }
    return super.select(opts);
  }
}

vi.mock('../../src/stages/gateway-config.js', () => ({
  gatewayConfigStage: (...args: unknown[]) => gatewayConfigMock(...args),
}));

vi.mock('../../src/stages/gateway-bootstrap.js', () => ({
  gatewayBootstrapStage: (...args: unknown[]) => gatewayBootstrapMock(...args),
}));

vi.mock('../../src/stages/provider-setup.js', () => ({
  providerSetupStage: (...args: unknown[]) => providerSetupMock(...args),
}));

vi.mock('../../src/stages/skills-select.js', () => ({
  skillsSelectStage: (...args: unknown[]) => skillsSelectMock(...args),
}));

// Import AFTER the mocks so runWizard picks up the mocked stage modules.
import { runWizard } from '../../src/wizard.js';

describe('Unified wizard (runWizard with default skipGateway)', () => {
  let tmpDir: string;
  const repoRoot = join(import.meta.dirname, '..', '..');

  const originalIsTTY = process.stdin.isTTY;
  const originalAssumeYes = process.env['MOSAIC_ASSUME_YES'];

  beforeEach(() => {
    tmpDir = mkdtempSync(join(tmpdir(), 'mosaic-unified-wizard-'));
    const candidates = [join(repoRoot, 'framework', 'templates'), join(repoRoot, 'templates')];
    for (const templatesDir of candidates) {
      if (existsSync(templatesDir)) {
        cpSync(templatesDir, join(tmpDir, 'templates'), { recursive: true });
        break;
      }
    }
    gatewayConfigMock.mockReset();
    gatewayBootstrapMock.mockReset();
    providerSetupMock.mockReset();
    skillsSelectMock.mockReset();
    providerSetupMock.mockImplementation(async (_p: HeadlessPrompter, state: WizardState) => {
      state.providerType = 'none';
      state.completedSections?.add('providers' satisfies MenuSection);
    });
    skillsSelectMock.mockImplementation(async (_p: HeadlessPrompter, state: WizardState) => {
      state.selectedSkills = [];
      state.completedSections?.add('skills' satisfies MenuSection);
    });
    // Pretend we're on an interactive TTY so the wizard's headless-abort
    // branch does not call `process.exit(1)` during these tests.
    Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true });
    delete process.env['MOSAIC_ASSUME_YES'];
  });

  afterEach(() => {
    rmSync(tmpDir, { recursive: true, force: true });
    Object.defineProperty(process.stdin, 'isTTY', {
      value: originalIsTTY,
      configurable: true,
    });
    if (originalAssumeYes === undefined) {
      delete process.env['MOSAIC_ASSUME_YES'];
    } else {
      process.env['MOSAIC_ASSUME_YES'] = originalAssumeYes;
    }
  });

  it('invokes the gateway config + bootstrap stages by default', async () => {
    gatewayConfigMock.mockResolvedValue({ ready: true, host: 'localhost', port: 14242 });
    gatewayBootstrapMock.mockResolvedValue({ completed: true });

    const prompter = new HeadlessPrompter({
      'Installation mode': 'quick',
      'What name should agents use?': 'TestBot',
      'Communication style': 'direct',
      'Your name': 'Tester',
      'Your pronouns': 'They/Them',
      'Your timezone': 'UTC',
    });

    await runWizard({
      mosaicHome: tmpDir,
      sourceDir: tmpDir,
      prompter,
      configService: createConfigService(tmpDir, tmpDir),
      gatewayHost: 'localhost',
      gatewayPort: 14242,
      skipGatewayNpmInstall: true,
    });

    expect(gatewayConfigMock).toHaveBeenCalledTimes(1);
    expect(gatewayBootstrapMock).toHaveBeenCalledTimes(1);
    const configCall = gatewayConfigMock.mock.calls[0];
    expect(configCall[2]).toMatchObject({
      host: 'localhost',
      defaultPort: 14242,
      skipInstall: true,
    });
    const bootstrapCall = gatewayBootstrapMock.mock.calls[0];
    expect(bootstrapCall[2]).toMatchObject({ host: 'localhost', port: 14242 });
  });

  it('prints the success summary only after gateway health succeeds', async () => {
    gatewayConfigMock.mockImplementation(async (p: HeadlessPrompter) => {
      p.log('Gateway is healthy.');
      return { ready: true, host: 'localhost', port: 14242 };
    });
    gatewayBootstrapMock.mockResolvedValue({ completed: true });

    const prompter = new HeadlessPrompter({
      'Installation mode': 'quick',
      'What name should agents use?': 'TestBot',
      'Communication style': 'direct',
      'Your name': 'Tester',
      'Your pronouns': 'They/Them',
      'Your timezone': 'UTC',
    });

    await runWizard({
      mosaicHome: tmpDir,
      sourceDir: tmpDir,
      prompter,
      configService: createConfigService(tmpDir, tmpDir),
      skipGatewayNpmInstall: true,
    });

    const logs = prompter.getLogs();
    const healthIndex = logs.findIndex((line) => line.includes('Gateway is healthy.'));
    const summaryIndex = logs.findIndex((line) => line.includes('Installation Summary'));
    const readyIndex = logs.findIndex((line) => line.includes('Mosaic is ready.'));

    expect(healthIndex).toBeGreaterThanOrEqual(0);
    expect(summaryIndex).toBeGreaterThan(healthIndex);
    expect(readyIndex).toBeGreaterThan(summaryIndex);
  });

  it('does not claim success when gateway health reports not ready', async () => {
    gatewayConfigMock.mockImplementation(async (p: HeadlessPrompter) => {
      p.warn('Gateway did not become healthy within 30 seconds.');
      return { ready: false };
    });

    const prompter = new HeadlessPrompter({
      'Installation mode': 'quick',
      'What name should agents use?': 'TestBot',
      'Communication style': 'direct',
      'Your name': 'Tester',
      'Your pronouns': 'They/Them',
      'Your timezone': 'UTC',
    });

    await expect(
      runWizard({
        mosaicHome: tmpDir,
        sourceDir: tmpDir,
        prompter,
        configService: createConfigService(tmpDir, tmpDir),
        skipGatewayNpmInstall: true,
      }),
    ).rejects.toThrow('Gateway configuration failed');

    const logs = prompter.getLogs();
    expect(logs.some((line) => line.includes('Gateway did not become healthy'))).toBe(true);
    expect(logs.some((line) => line.includes('Gateway configuration failed'))).toBe(true);
    expect(logs.some((line) => line.includes('Installation Summary'))).toBe(false);
    expect(logs.some((line) => line.includes('Mosaic is ready.'))).toBe(false);
    expect(gatewayConfigMock).toHaveBeenCalledTimes(1);
    expect(gatewayBootstrapMock).not.toHaveBeenCalled();
  });

  it('respects skipGateway: true', async () => {
    const prompter = new HeadlessPrompter({
      'Installation mode': 'quick',
      'What name should agents use?': 'TestBot',
      'Communication style': 'direct',
      'Your name': 'Tester',
      'Your pronouns': 'They/Them',
      'Your timezone': 'UTC',
    });

    await runWizard({
      mosaicHome: tmpDir,
      sourceDir: tmpDir,
      prompter,
      configService: createConfigService(tmpDir, tmpDir),
      skipGateway: true,
    });

    expect(gatewayConfigMock).not.toHaveBeenCalled();
    expect(gatewayBootstrapMock).not.toHaveBeenCalled();
  });

  it('does not re-run completed provider or skills menu steps', async () => {
    const prompter = new SequencedMenuPrompter(
      {
        'What name should agents use?': 'TestBot',
        'Communication style': 'direct',
        'Your name': 'Tester',
        'Your pronouns': 'They/Them',
        'Your timezone': 'UTC',
      },
      ['providers', 'providers', 'skills', 'skills', 'finish'],
    );

    await runWizard({
      mosaicHome: tmpDir,
      sourceDir: tmpDir,
      prompter,
      configService: createConfigService(tmpDir, tmpDir),
      skipGateway: true,
    });

    expect(providerSetupMock).toHaveBeenCalledTimes(1);
    expect(skillsSelectMock).toHaveBeenCalledTimes(1);
    expect(prompter.getLogs()).toEqual(
      expect.arrayContaining([
        expect.stringContaining('Providers [done] is already complete; skipping.'),
        expect.stringContaining('Skills [done] is already complete; skipping.'),
      ]),
    );
  });
});
