import { describe, it, expect } from 'vitest';
import { resolveLaunchSpec, resolveAcpSpawnArgs } from '../acp-spawn.js';
import { buildResolvedRegistry } from '../provider-config-registry.js';
import type { CoderProvidersFile } from '../provider-config.js';
import { PROVIDERS } from '../provider-registry.js';

/** Resolved def for a provider id under the given config (default: no override). */
function builtin(name: string, providers: CoderProvidersFile['providers'] = {}) {
  const def = buildResolvedRegistry(PROVIDERS, { providers }).get(name);
  if (!def) throw new Error(`no resolved def for ${name}`);
  return def;
}

describe('resolveLaunchSpec', () => {
  // --- byte-identical built-in regression (the HARD CONSTRAINT) ---------------
  // These argv values are the pre-v2.3 resolveAcpSpawnArgs switch outputs and
  // MUST NOT change. spawn() is `spawn(spec.binary, spec.args, ...)`, so argv
  // parity here is dispatch parity.
  it('opencode (no override) → byte-identical argv ["acp"], binary = installPath', () => {
    const spec = resolveLaunchSpec(builtin('opencode'), '/usr/bin/opencode');
    expect(spec).not.toBeNull();
    expect(spec!.args).toEqual(['acp']); // pre-v2.3 value
    expect(spec!.binary).toBe('/usr/bin/opencode');
    expect(spec!.env).toBeUndefined();
    // cross-check against the switch source-of-truth
    expect(spec!.args).toEqual(resolveAcpSpawnArgs('opencode'));
  });

  it('goose/reasonix → ["acp"], qwen → ["--acp"]', () => {
    expect(resolveLaunchSpec(builtin('goose'), '/usr/bin/goose')!.args).toEqual(['acp']);
    expect(resolveLaunchSpec(builtin('reasonix'), '/usr/bin/reasonix')!.args).toEqual(['acp']);
    expect(resolveLaunchSpec(builtin('qwen'), '/usr/bin/qwen')!.args).toEqual(['--acp']);
  });

  it('built-in with null installPath falls back to the bare id (pre-v2.3 `installPath ?? agent`)', () => {
    const spec = resolveLaunchSpec(builtin('opencode'), null);
    expect(spec!.binary).toBe('opencode');
    expect(spec!.args).toEqual(['acp']);
  });

  it('non-ACP / unknown provider → null (claude has no ACP argv)', () => {
    expect(resolveLaunchSpec(builtin('claude'), '/usr/bin/claude')).toBeNull();
    expect(resolveLaunchSpec(builtin('boocode'), null)).toBeNull();
  });

  // --- config-driven launch (the new capability) ------------------------------
  it('custom ACP entry → configured command + env reach the spec', () => {
    const def = builtin('amp-acp', {
      'amp-acp': { extends: 'acp', label: 'Amp', command: ['amp-acp', '--acp'], env: { AMP_KEY: 'x' } },
    });
    const spec = resolveLaunchSpec(def, '/usr/local/bin/amp-acp');
    expect(spec).not.toBeNull();
    expect(spec!.binary).toBe('amp-acp'); // command[0], not the resolved install path
    expect(spec!.args).toEqual(['--acp']); // command.slice(1)
    expect(spec!.env).toEqual({ AMP_KEY: 'x' });
  });

  it('built-in WITH a config command override uses the override, not the switch default', () => {
    const def = builtin('opencode', { opencode: { command: ['opencode', 'acp', '--verbose'], env: { DEBUG: '1' } } });
    const spec = resolveLaunchSpec(def, '/usr/bin/opencode');
    expect(spec!.binary).toBe('opencode');
    expect(spec!.args).toEqual(['acp', '--verbose']);
    expect(spec!.env).toEqual({ DEBUG: '1' });
  });
});

describe('acp-dispatch spawn wiring (documented pass-through)', () => {
  // dispatchViaAcp spawns `spawn(spec.binary, spec.args, { env: { ...process.env, ...spec.env } })`.
  // The env merge layers config env over process.env; for a built-in with no
  // config env, spec.env is undefined → { ...process.env } (byte-identical).
  it('built-in with no config env yields an undefined spec.env (→ plain process.env at spawn)', () => {
    expect(resolveLaunchSpec(builtin('opencode'), '/usr/bin/opencode')!.env).toBeUndefined();
  });
});
