import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { buildDefaultKtxProjectConfig, type KtxProjectConfig } from '../src/context/project/config.js';
import { readKtxSetupState } from '../src/context/project/setup-config.js';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { runKtxSetupRuntimeStep } from '../src/setup-runtime.js';

function makeIo() {
  let stdout = '';
  let stderr = '';
  return {
    io: {
      stdout: {
        write: (chunk: string) => {
          stdout += chunk;
        },
      },
      stderr: {
        write: (chunk: string) => {
          stderr += chunk;
        },
      },
    },
    stdout: () => stdout,
    stderr: () => stderr,
  };
}

function projectConfig(config: KtxProjectConfig) {
  return vi.fn(async () => ({ config }));
}

describe('runKtxSetupRuntimeStep', () => {
  let tempDir: string;

  beforeEach(async () => {
    tempDir = await mkdtemp(join(tmpdir(), 'ktx-setup-runtime-'));
  });

  afterEach(async () => {
    await rm(tempDir, { recursive: true, force: true });
  });

  it('skips runtime setup when the project has no direct runtime requirements', async () => {
    const io = makeIo();
    const ensureRuntime = vi.fn();

    await expect(
      runKtxSetupRuntimeStep(
        {
          projectDir: tempDir,
          inputMode: 'auto',
          cliVersion: '0.2.0',
          runtimeInstallPolicy: 'prompt',
        },
        io.io,
        {
          loadProject: projectConfig(buildDefaultKtxProjectConfig()),
          ensureRuntime,
          env: {},
        },
      ),
    ).resolves.toMatchObject({ status: 'skipped' });

    expect(ensureRuntime).not.toHaveBeenCalled();
    expect((await readKtxSetupState(tempDir)).completed_steps).not.toContain('runtime');
    expect(io.stdout()).toContain('Runtime setup skipped.');
  });

  it('fails fast when required runtime features cannot be installed in no-input mode', async () => {
    const io = makeIo();
    const ensureRuntime = vi.fn(async () => {
      throw new Error('KTX Python runtime is required for this command. Run: ktx admin runtime install --yes');
    });

    await expect(
      runKtxSetupRuntimeStep(
        {
          projectDir: tempDir,
          inputMode: 'disabled',
          cliVersion: '0.2.0',
          runtimeInstallPolicy: 'never',
          databaseIntrospectionFallback: true,
        },
        io.io,
        {
          loadProject: projectConfig(buildDefaultKtxProjectConfig()),
          ensureRuntime,
          env: {},
        },
      ),
    ).resolves.toMatchObject({ status: 'failed' });

    expect(ensureRuntime).toHaveBeenCalledWith(expect.objectContaining({ installPolicy: 'never' }));
    expect((await readKtxSetupState(tempDir)).completed_steps).not.toContain('runtime');
    expect(io.stderr()).toContain('ktx admin runtime install --yes');
  });

  it('starts the KTX daemon for configured sentence-transformers embeddings', async () => {
    const io = makeIo();
    const ensureLocalEmbeddings = vi.fn(async () => ({
      baseUrl: 'http://127.0.0.1:61234',
      stdoutLog: join(tempDir, '.ktx', 'runtime', 'daemon.stdout.log'),
      stderrLog: join(tempDir, '.ktx', 'runtime', 'daemon.stderr.log'),
    }));
    const config: KtxProjectConfig = {
      ...buildDefaultKtxProjectConfig(),
      ingest: {
        ...buildDefaultKtxProjectConfig().ingest,
        embeddings: {
          backend: 'sentence-transformers',
          model: 'all-MiniLM-L6-v2',
          dimensions: 384,
          sentenceTransformers: { base_url: '' },
        },
      },
    };

    await expect(
      runKtxSetupRuntimeStep(
        {
          projectDir: tempDir,
          inputMode: 'auto',
          cliVersion: '0.2.0',
          runtimeInstallPolicy: 'auto',
        },
        io.io,
        {
          loadProject: projectConfig(config),
          ensureLocalEmbeddings,
          env: {},
        },
      ),
    ).resolves.toMatchObject({ status: 'ready' });

    expect(ensureLocalEmbeddings).toHaveBeenCalledWith(
      expect.objectContaining({
        projectDir: tempDir,
        installPolicy: 'auto',
      }),
    );
    expect(io.stdout()).toContain('Runtime ready: yes (local embeddings)');
  });
});
