import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { buildStartRunOptions, needsLegacyConvert } from './platform-run.js';

// ── needsLegacyConvert ────────────────────────────────────────────────────────

describe('needsLegacyConvert', () => {
  it('returns false for upload-only projects (no repoUrl, no srcPath)', () => {
    const cfg = { uploadId: 'upload-abc' };
    const project = {};
    assert.equal(needsLegacyConvert(cfg, project), false);
  });

  it('returns true when additionalSourceLanguages is non-empty (no uploadId)', () => {
    // Without an uploadId the early-return for upload-only projects does not fire,
    // so the additionalSourceLanguages check is reached.
    const cfg = { additionalSourceLanguages: ['jcl'] };
    assert.equal(needsLegacyConvert(cfg, {}), true);
  });

  it('returns false when additionalSourceLanguages is empty (upload-only project)', () => {
    const cfg = { uploadId: 'up-1', additionalSourceLanguages: [] };
    assert.equal(needsLegacyConvert(cfg, {}), false);
  });

  it('returns true when useStages is present', () => {
    assert.equal(needsLegacyConvert({ useStages: true }, {}), true);
    assert.equal(needsLegacyConvert({ useStages: false }, {}), true);
  });

  it('returns true when useRepair is present', () => {
    assert.equal(needsLegacyConvert({ useRepair: false }, {}), true);
  });

  it('returns true for local source path without repoUrl', () => {
    // needsLegacyConvert reads project.source_path (snake_case from DB column)
    const cfg = {};
    const project = { source_path: '/local/code' };
    assert.equal(needsLegacyConvert(cfg, project), true);
  });

  it('returns false when there is a repoUrl (handled by /runs)', () => {
    const cfg = {};
    const project = { repoUrl: 'https://github.com/org/repo' };
    assert.equal(needsLegacyConvert(cfg, project), false);
  });

  it('returns true when dependencyMapping is non-empty', () => {
    const cfg = {
      dependencyMapping: [{ id: '1', source: 'db2', target: 'postgres', version: '', notes: '', status: 'auto' }],
    };
    assert.equal(needsLegacyConvert(cfg, {}), true);
  });
});

// ── buildStartRunOptions ──────────────────────────────────────────────────────

describe('buildStartRunOptions', () => {
  const BASE = { sourceLanguage: 'cobol', targetLanguage: 'java', uploadId: 'up-test' };

  function build(cfg: Record<string, unknown>) {
    return buildStartRunOptions(cfg, BASE.sourceLanguage, BASE.targetLanguage, BASE.uploadId);
  }

  it('always includes uploadId, sourceLanguage, targetLanguage', () => {
    const opts = build({});
    assert.equal(opts.uploadId, 'up-test');
    assert.equal(opts.sourceLanguage, 'cobol');
    assert.equal(opts.targetLanguage, 'java');
  });

  it('defaults qualityLevel to 0 when not in config', () => {
    const opts = build({});
    assert.equal(opts.qualityLevel, 0);
  });

  it('clamps qualityLevel to 0–3', () => {
    assert.equal(build({ qualityLevel: -1 }).qualityLevel, 0);
    assert.equal(build({ qualityLevel: 10 }).qualityLevel, 3);
    assert.equal(build({ qualityLevel: 2 }).qualityLevel, 2);
  });

  it('defaults maxIterations to 3', () => {
    const opts = build({});
    assert.equal(opts.maxIterations, 3);
  });

  it('passes through customRules when set', () => {
    const opts = build({ customRules: 'Replace DB2 with PostgreSQL' });
    assert.equal(opts.customRules, 'Replace DB2 with PostgreSQL');
  });

  it('omits customRules when blank', () => {
    const opts = build({ customRules: '   ' });
    assert.equal(opts.customRules, undefined);
  });

  it('passes includePatterns and excludePatterns', () => {
    const opts = build({ includePatterns: '**/*.cbl', excludePatterns: '**/test/**' });
    assert.equal(opts.includePatterns, '**/*.cbl');
    assert.equal(opts.excludePatterns, '**/test/**');
  });

  it('passes useStages when explicitly set', () => {
    assert.equal(build({ useStages: true }).useStages, true);
    assert.equal(build({ useStages: false }).useStages, false);
  });

  it('omits useStages when not in config', () => {
    assert.equal(build({}).useStages, undefined);
  });

  it('passes useRepair when explicitly set', () => {
    assert.equal(build({ useRepair: true }).useRepair, true);
    assert.equal(build({ useRepair: false }).useRepair, false);
  });

  it('passes intent when set to a valid value', () => {
    assert.equal(build({ intent: 'parity' }).intent, 'parity');
    assert.equal(build({ intent: 'compat-strict' }).intent, 'compat-strict');
    assert.equal(build({ intent: 'modernize' }).intent, 'modernize');
  });

  it('omits intent for unknown values', () => {
    assert.equal(build({ intent: 'unknown-intent' }).intent, undefined);
  });

  // ── profile (FE-WIZ-2) ───────────────────────────────────────────────────

  it('omits profile when not set', () => {
    assert.equal(build({}).profile, undefined);
  });

  it('omits profile when set to "auto"', () => {
    assert.equal(build({ projectProfile: 'auto' }).profile, undefined);
  });

  it('passes profile for each valid non-auto value', () => {
    for (const p of ['batch', 'online', 'library', 'utility', 'mixed'] as const) {
      assert.equal(build({ projectProfile: p }).profile, p, `expected profile=${p}`);
    }
  });

  it('omits profile for an invalid string', () => {
    assert.equal(build({ projectProfile: 'enterprise-batch' }).profile, undefined);
  });

  it('omits profile when projectProfile is not a string', () => {
    assert.equal(build({ projectProfile: 42 }).profile, undefined);
    assert.equal(build({ projectProfile: null }).profile, undefined);
  });

  // ── budget caps ───────────────────────────────────────────────────────────

  it('passes maxCostUsd when positive and finite', () => {
    assert.equal(build({ maxCostUsd: 5 }).maxCostUsd, 5);
  });

  it('omits maxCostUsd when zero or negative', () => {
    assert.equal(build({ maxCostUsd: 0 }).maxCostUsd, undefined);
    assert.equal(build({ maxCostUsd: -1 }).maxCostUsd, undefined);
  });

  it('passes maxTotalTokens when positive and finite', () => {
    assert.equal(build({ maxTotalTokens: 200_000 }).maxTotalTokens, 200_000);
  });

  it('derives maxCostUsd from engineEstimate.p95CostUsd when maxCostUsd is not set', () => {
    const opts = build({ engineEstimate: { p95CostUsd: 0.42 } });
    assert.equal(opts.maxCostUsd, 0.42);
  });

  it('prefers explicit maxCostUsd over engineEstimate.p95CostUsd', () => {
    const opts = build({ maxCostUsd: 1.00, engineEstimate: { p95CostUsd: 0.42 } });
    assert.equal(opts.maxCostUsd, 1.00);
  });

  it('omits maxCostUsd when engineEstimate.p95CostUsd is 0', () => {
    const opts = build({ engineEstimate: { p95CostUsd: 0 } });
    assert.equal(opts.maxCostUsd, undefined);
  });

  it('omits maxCostUsd when engineEstimate has no p95CostUsd', () => {
    const opts = build({ engineEstimate: { expectedCostUsd: 0.30 } });
    assert.equal(opts.maxCostUsd, undefined);
  });

  it('omits maxCostUsd when engineEstimate is not an object', () => {
    assert.equal(build({ engineEstimate: null }).maxCostUsd, undefined);
    assert.equal(build({ engineEstimate: 'bad' }).maxCostUsd, undefined);
  });

  // ── additionalSourceLanguages ─────────────────────────────────────────────

  it('passes non-empty additionalSourceLanguages', () => {
    const opts = build({ additionalSourceLanguages: ['jcl', 'cics'] });
    assert.deepEqual(opts.additionalSourceLanguages, ['jcl', 'cics']);
  });

  it('omits additionalSourceLanguages when empty', () => {
    assert.equal(build({ additionalSourceLanguages: [] }).additionalSourceLanguages, undefined);
  });

  it('filters falsy entries from additionalSourceLanguages', () => {
    const opts = build({ additionalSourceLanguages: ['jcl', '', null] });
    assert.deepEqual(opts.additionalSourceLanguages, ['jcl']);
  });

  // ── disabledPlugins ───────────────────────────────────────────────────────

  it('omits disabledPlugins when all plugins are enabled', () => {
    assert.equal(build({}).disabledPlugins, undefined);
  });
});
