import assert from 'node:assert/strict';
import { mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import test from 'node:test';

import { cliBuildPlan, stagePluginHostResources } from './cli-product.mjs';
import { resolveProductDefinition } from './product-customization/resolver.mjs';

const ROOT = resolve(import.meta.dirname, '..');
const ACME = join(ROOT, 'products', 'fixtures', 'acme', 'product.jsonc');

test('CLI stages only the supported plugin Host entry', (t) => {
  const root = mkdtempSync(join(tmpdir(), 'bitfun-cli-plugin-host-'));
  t.after(() => rmSync(root, { recursive: true, force: true }));
  const source = join(root, 'source');
  const destination = join(root, 'destination');
  mkdirSync(source);
  writeFileSync(join(source, 'extension-host.js'), 'current');
  writeFileSync(join(source, 'stale-runtime.js'), 'stale');

  stagePluginHostResources(destination, source);

  assert.deepEqual(readdirSync(destination), ['extension-host.js']);
});

test('CLI uses the shared resolver and stages the internal binary under the member name', () => {
  const resolution = resolveProductDefinition({ rootDir: ROOT, productConfig: ACME, member: 'cli' });
  const plan = cliBuildPlan(resolution, 'build', ['--locked'], 'win32');

  assert.deepEqual(plan.cargoArgs.slice(0, 2), ['build', '--manifest-path']);
  assert.ok(plan.cargoArgs.includes('--locked'));
  assert.ok(plan.internalBinaryPath.endsWith('bitfun.exe'));
  assert.ok(plan.stagedBinaryPath.endsWith('acme.exe'));
  assert.ok(plan.stagedPluginHostPath.endsWith(join('resources', 'ext-host')));
  assert.equal(plan.environment.BITFUN_PRODUCT_DISPLAY_NAME, 'Acme CLI');
});

test('CLI dev forwards runtime arguments after the Cargo delimiter', () => {
  const resolution = resolveProductDefinition({ rootDir: ROOT, member: 'cli' });
  const plan = cliBuildPlan(resolution, 'dev', ['--', 'health'], 'linux');
  assert.deepEqual(plan.cargoArgs.slice(-2), ['--', 'health']);
});

test('CLI build stages a standard cross-target artifact from the Cargo target subdirectory', () => {
  const resolution = resolveProductDefinition({ rootDir: ROOT, productConfig: ACME, member: 'cli' });
  const plan = cliBuildPlan(
    resolution,
    'build',
    ['--target', 'aarch64-unknown-linux-gnu'],
    'linux',
  );

  assert.ok(plan.internalBinaryPath.endsWith(
    join('aarch64-unknown-linux-gnu', 'release', 'bitfun'),
  ));
});

test('CLI build uses CARGO_BUILD_TARGET when no explicit target is forwarded', { concurrency: false }, () => {
  const previousTarget = process.env.CARGO_BUILD_TARGET;
  process.env.CARGO_BUILD_TARGET = 'x86_64-unknown-linux-gnu';
  try {
    const resolution = resolveProductDefinition({ rootDir: ROOT, member: 'cli' });
    const plan = cliBuildPlan(resolution, 'build', [], 'linux');
    assert.ok(plan.internalBinaryPath.endsWith(
      join('x86_64-unknown-linux-gnu', 'release', 'bitfun'),
    ));
  } finally {
    if (previousTarget === undefined) delete process.env.CARGO_BUILD_TARGET;
    else process.env.CARGO_BUILD_TARGET = previousTarget;
  }
});

test('CLI build rejects custom target specs before resolving an artifact path', { concurrency: false }, () => {
  const resolution = resolveProductDefinition({ rootDir: ROOT, member: 'cli' });
  assert.throws(
    () => cliBuildPlan(resolution, 'build', ['--target=../custom-target.json'], 'linux'),
    (error) => error?.code === 'unsupported_cli_build_target',
  );
  assert.throws(
    () => cliBuildPlan(resolution, 'build', ['--target', 'custom-target.json'], 'linux'),
    (error) => error?.code === 'unsupported_cli_build_target',
  );
  const previousTarget = process.env.CARGO_BUILD_TARGET;
  process.env.CARGO_BUILD_TARGET = 'custom-target.json';
  try {
    assert.throws(
      () => cliBuildPlan(resolution, 'build', [], 'linux'),
      (error) => error?.code === 'unsupported_cli_build_target',
    );
  } finally {
    if (previousTarget === undefined) delete process.env.CARGO_BUILD_TARGET;
    else process.env.CARGO_BUILD_TARGET = previousTarget;
  }
});

test('CLI build derives the executable suffix from an explicit target OS', () => {
  const resolution = resolveProductDefinition({ rootDir: ROOT, productConfig: ACME, member: 'cli' });
  const windowsPlan = cliBuildPlan(
    resolution,
    'build',
    ['--target=x86_64-pc-windows-gnu'],
    'linux',
  );
  const linuxPlan = cliBuildPlan(
    resolution,
    'build',
    ['--target=x86_64-unknown-linux-gnu'],
    'win32',
  );

  assert.ok(windowsPlan.internalBinaryPath.endsWith(join('release', 'bitfun.exe')));
  assert.ok(windowsPlan.stagedBinaryPath.endsWith('acme.exe'));
  assert.ok(linuxPlan.internalBinaryPath.endsWith(join('release', 'bitfun')));
  assert.ok(linuxPlan.stagedBinaryPath.endsWith('acme'));
});

test('CLI build rejects Cargo target directory overrides that bypass product isolation', () => {
  const resolution = resolveProductDefinition({ rootDir: ROOT, productConfig: ACME, member: 'cli' });
  for (const forwardArgs of [
    ['--target-dir', 'target/other'],
    ['--target-dir=target/other'],
    ['--config', 'build.target-dir="target/other"'],
    ['--config=build.target-dir="target/other"'],
  ]) {
    assert.throws(
      () => cliBuildPlan(resolution, 'build', forwardArgs, 'linux'),
      (error) => error?.code === 'unsupported_cli_target_dir_override',
    );
  }
});