import assert from 'node:assert/strict';
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import { test } from 'node:test';
import { fileURLToPath } from 'node:url';

const repoRoot = path.resolve(
  path.dirname(fileURLToPath(import.meta.url)),
  '..',
);
const scriptPath = path.join(repoRoot, 'scripts/check-build-prereqs.mjs');

function createTestRoot({
  nodeModules = false,
  mobileWebDist = false,
  pluginHostDist = false,
  sherpaOnnx = null,
} = {}) {
  const root = mkdtempSync(path.join(tmpdir(), 'bitfun-build-prereqs-'));

  if (nodeModules) {
    mkdirSync(path.join(root, 'node_modules'), { recursive: true });
  }

  if (mobileWebDist) {
    const distDir = path.join(root, 'src', 'mobile-web', 'dist');
    mkdirSync(distDir, { recursive: true });
    writeFileSync(path.join(distDir, 'index.html'), '<html></html>');
  }

  if (pluginHostDist) {
    const distDir = path.join(
      root,
      'src',
      'apps',
      'extension-host',
      'dist',
    );
    mkdirSync(distDir, { recursive: true });
    writeFileSync(path.join(distDir, 'extension-host.js'), '');
  }

  if (sherpaOnnx) {
    for (const version of sherpaOnnx) {
      const libDir = path.join(
        root,
        'target',
        'sherpa-onnx-prebuilt',
        version,
        'lib',
      );
      mkdirSync(libDir, { recursive: true });
      writeFileSync(path.join(libDir, 'libsherpa-onnx-c-api.a'), '');
    }
  }

  return root;
}

function createFakePnpm() {
  const binDir = mkdtempSync(path.join(tmpdir(), 'bitfun-fake-pnpm-'));
  const fakePnpmPath = path.join(binDir, 'fake-pnpm.cjs');
  writeFileSync(
    fakePnpmPath,
    `
const { mkdirSync, writeFileSync } = require('fs');
const args = process.argv.slice(2);
if (args[0] === 'install') {
  mkdirSync('node_modules', { recursive: true });
} else if (args[0] === 'run' && args[1] === 'prepare:mobile-web') {
  mkdirSync('src/mobile-web/dist', { recursive: true });
  writeFileSync('src/mobile-web/dist/index.html', '<html></html>');
} else if (args[0] === 'run' && args[1] === 'plugin-host:prepare') {
  mkdirSync('src/apps/extension-host/dist', { recursive: true });
  writeFileSync('src/apps/extension-host/dist/extension-host.js', '');
}
`,
  );
  if (process.platform === 'win32') {
    writeFileSync(
      path.join(binDir, 'pnpm.cmd'),
      `@echo off\r\n"${process.execPath}" "%~dp0fake-pnpm.cjs" %*\r\n`,
    );
  } else {
    const pnpmPath = path.join(binDir, 'pnpm');
    writeFileSync(
      pnpmPath,
      `#!/usr/bin/env node\nrequire('./fake-pnpm.cjs');\n`,
    );
    chmodSync(pnpmPath, 0o755);
  }
  return binDir;
}

function runCheck(root, { fix = false, extraPath = null, sherpaEnv = null } = {}) {
  const env = {
    ...process.env,
    BITFUN_BUILD_PREREQS_TEST_ROOT: root,
  };
  if (extraPath) {
    env.PATH = `${extraPath}${path.delimiter}${env.PATH || ''}`;
  }
  if (sherpaEnv !== null) {
    if (sherpaEnv === '') {
      delete env.SHERPA_ONNX_LIB_DIR;
    } else {
      env.SHERPA_ONNX_LIB_DIR = sherpaEnv;
    }
  }

  const args = fix ? [scriptPath, '--fix'] : [scriptPath];

  return spawnSync(process.execPath, args, {
    env,
    encoding: 'utf8',
  });
}

test('passes when all prerequisites are present (including sherpa-onnx prebuilt)', (t) => {
  const root = createTestRoot({
    nodeModules: true,
    mobileWebDist: true,
    pluginHostDist: true,
    sherpaOnnx: ['sherpa-onnx-v1.13.4-osx-arm64-static-lib'],
  });
  t.after(() => rmSync(root, { recursive: true, force: true }));

  const result = runCheck(root, { sherpaEnv: '' });

  assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
  assert.match(result.stdout, /Build prerequisite check passed/);
  assert.doesNotMatch(result.stderr, /\[WARN\]/);
});

test('fails when root node_modules is missing', (t) => {
  const root = createTestRoot({
    mobileWebDist: true,
    pluginHostDist: true,
    sherpaOnnx: ['sherpa-onnx-v1.13.4-osx-arm64-static-lib'],
  });
  t.after(() => rmSync(root, { recursive: true, force: true }));

  const result = runCheck(root, { sherpaEnv: '' });

  assert.notEqual(result.status, 0);
  assert.match(result.stderr, /\[FAIL\] root node_modules/);
  assert.match(result.stderr, /Fix: pnpm install/);
});

test('fails when mobile-web dist is missing', (t) => {
  const root = createTestRoot({
    nodeModules: true,
    pluginHostDist: true,
    sherpaOnnx: ['sherpa-onnx-v1.13.4-osx-arm64-static-lib'],
  });
  t.after(() => rmSync(root, { recursive: true, force: true }));

  const result = runCheck(root, { sherpaEnv: '' });

  assert.notEqual(result.status, 0);
  assert.match(result.stderr, /\[FAIL\] mobile-web dist/);
  assert.match(result.stderr, /Fix: pnpm run prepare:mobile-web/);
});

test('does not require the DeepSeek profile for cargo check', (t) => {
  const root = createTestRoot({
    nodeModules: true,
    mobileWebDist: true,
    pluginHostDist: true,
    sherpaOnnx: ['sherpa-onnx-v1.13.4-osx-arm64-static-lib'],
  });
  t.after(() => rmSync(root, { recursive: true, force: true }));

  const result = runCheck(root, { sherpaEnv: '' });

  assert.equal(result.status, 0);
  assert.doesNotMatch(result.stderr, /dsh bridge profile/);
  assert.doesNotMatch(result.stderr, /prepare:dsh-profile/);
});

test('fails when OpenCode extension Host dist is missing', (t) => {
  const root = createTestRoot({
    nodeModules: true,
    mobileWebDist: true,
    sherpaOnnx: ['sherpa-onnx-v1.13.4-osx-arm64-static-lib'],
  });
  t.after(() => rmSync(root, { recursive: true, force: true }));

  const result = runCheck(root, { sherpaEnv: '' });

  assert.notEqual(result.status, 0);
  assert.match(result.stderr, /\[FAIL\] OpenCode extension Host dist/);
  assert.match(result.stderr, /Fix: pnpm run plugin-host:prepare/);
});

test('warns when sherpa-onnx prebuilt dir does not exist (first build)', (t) => {
  const root = createTestRoot({
    nodeModules: true,
    mobileWebDist: true,
    pluginHostDist: true,
  });
  t.after(() => rmSync(root, { recursive: true, force: true }));

  const result = runCheck(root, { sherpaEnv: '' });

  assert.equal(result.status, 0);
  assert.match(result.stderr, /\[WARN\] sherpa-onnx/);
  assert.match(result.stderr, /first build will download from GitHub/);
});

test('exits with error code when both errors and warnings are present', (t) => {
  const root = createTestRoot({ mobileWebDist: false });
  t.after(() => rmSync(root, { recursive: true, force: true }));

  const result = runCheck(root, { sherpaEnv: '' });

  assert.notEqual(result.status, 0);
  assert.match(result.stderr, /\[FAIL\]/);
  assert.match(result.stderr, /\[WARN\] sherpa-onnx/);
});

test('--fix runs fix commands, re-verifies, and exits 0 when errors resolved', (t) => {
  const binDir = createFakePnpm();
  const root = createTestRoot();
  t.after(() => {
    rmSync(root, { recursive: true, force: true });
    rmSync(binDir, { recursive: true, force: true });
  });

  const result = runCheck(root, { fix: true, extraPath: binDir, sherpaEnv: '' });

  assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
  assert.match(result.stdout, /Attempting fixes/);
  assert.match(result.stdout, /\$ pnpm install/);
  assert.match(result.stdout, /\$ pnpm run prepare:mobile-web/);
  assert.match(result.stdout, /\$ pnpm run plugin-host:prepare/);
  assert.doesNotMatch(result.stdout, /prepare:dsh-profile/);
  assert.match(result.stdout, /Re-checking prerequisites/);
  assert.match(result.stdout, /All errors resolved/);
});