/**
 * Test script for adjustment workflows
 * Tests: workflow loading, auto-selection, param mapping, and workflow JSON validity
 */
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const WORKFLOWS_DIR = path.join(__dirname, '.vl-code', 'workflows');
const TEST_PROJECT = path.join(process.env.HOME, 'Documents/VLProjects/_tests/AdjustTest');

let passed = 0;
let failed = 0;

function test(name, fn) {
  try {
    fn();
    console.log(`  ✅ ${name}`);
    passed++;
  } catch (err) {
    console.log(`  ❌ ${name}: ${err.message}`);
    failed++;
  }
}

function assert(condition, msg) {
  if (!condition) throw new Error(msg || 'Assertion failed');
}

// ========== 1. Workflow JSON Validity ==========
console.log('\n📋 1. Workflow JSON Validity');

const adjustWorkflows = [
  'feature-adjust.json',
  'data-adjust.json',
  'batch-adjust.json',
  'add-page.json',
  'add-service.json',
  'theme-customize.json',
  'incremental-update.json',
];

for (const wfFile of adjustWorkflows) {
  test(`${wfFile} is valid JSON with required fields`, () => {
    const fp = path.join(WORKFLOWS_DIR, wfFile);
    assert(fs.existsSync(fp), `File not found: ${fp}`);
    const wf = JSON.parse(fs.readFileSync(fp, 'utf-8'));
    assert(wf.version, 'Missing version');
    assert(wf.name, 'Missing name');
    assert(wf.registry, 'Missing registry');
    assert(wf.registry.params, 'Missing registry.params');
    assert(wf.registry.vars, 'Missing registry.vars');
    assert(Array.isArray(wf.steps), 'steps must be an array');
    assert(wf.steps.length > 0, 'steps must not be empty');
  });
}

// ========== 2. New Workflow Structure ==========
console.log('\n📋 2. New Workflow Structure');

test('feature-adjust.json has Pause node for developer review', () => {
  const wf = JSON.parse(fs.readFileSync(path.join(WORKFLOWS_DIR, 'feature-adjust.json'), 'utf-8'));
  const pauseNodes = wf.steps.filter(s => s.id.startsWith('Pause_'));
  assert(pauseNodes.length > 0, 'Must have at least one Pause node');
  assert(pauseNodes[0].resumeResultTarget, 'Pause node must have resumeResultTarget');
});

test('feature-adjust.json has Fork for parallel execution', () => {
  const wf = JSON.parse(fs.readFileSync(path.join(WORKFLOWS_DIR, 'feature-adjust.json'), 'utf-8'));
  const forkNodes = wf.steps.filter(s => s.id.startsWith('Fork_'));
  assert(forkNodes.length > 0, 'Must have at least one Fork node');
  assert(forkNodes[0].children.length > 0, 'Fork must have children');
});

test('data-adjust.json has cascade flow: DB → Services → Sections', () => {
  const wf = JSON.parse(fs.readFileSync(path.join(WORKFLOWS_DIR, 'data-adjust.json'), 'utf-8'));
  const stepIds = wf.steps.map(s => s.id);
  assert(stepIds.some(id => id.includes('Database')), 'Must have DB edit step');
  assert(stepIds.some(id => id.includes('CascadeServices')), 'Must have service cascade');
  assert(stepIds.some(id => id.includes('CascadeSections')), 'Must have section cascade');
});

test('batch-adjust.json has decompose + loop pattern', () => {
  const wf = JSON.parse(fs.readFileSync(path.join(WORKFLOWS_DIR, 'batch-adjust.json'), 'utf-8'));
  const stepIds = wf.steps.map(s => s.id);
  assert(stepIds.some(id => id.includes('Decompose')), 'Must have decompose step');
  assert(stepIds.some(id => id.includes('Loop')), 'Must have loop step');
});

test('add-page.json writes the updated app back to the planned target app file', () => {
  const wf = JSON.parse(fs.readFileSync(path.join(WORKFLOWS_DIR, 'add-page.json'), 'utf-8'));
  const appUpdate = wf.steps.find(s => s.id === 'LLM_050_UpdateApp');
  assert(appUpdate, 'Missing LLM_050_UpdateApp step');
  assert(appUpdate.out && Object.keys(appUpdate.out).includes("/{$targetAppFilePath || ('Apps/' + $targetApp + '.vx')}"), 'App update must write to dynamic target app file path');
});

test('add-service.json updates the actual database file path from ProjectMeta', () => {
  const wf = JSON.parse(fs.readFileSync(path.join(WORKFLOWS_DIR, 'add-service.json'), 'utf-8'));
  const dbStep = wf.steps.find(s => s.id === 'LLM_020_UpdateDB');
  assert(dbStep, 'Missing LLM_020_UpdateDB step');
  assert(dbStep.out && Object.keys(dbStep.out).includes("/{currentMeta.database.file || ('Database/' + currentMeta.projectName + '.vdb')}"), 'DB update must write to the ProjectMeta database file or project-name fallback');
});

test('theme-customize.json writes cascade updates back to affected files', () => {
  const wf = JSON.parse(fs.readFileSync(path.join(WORKFLOWS_DIR, 'theme-customize.json'), 'utf-8'));
  const updateStep = wf.steps.find(s => s.id === 'LLM_030_UpdateFile');
  assert(updateStep, 'Missing LLM_030_UpdateFile step');
  assert(updateStep.out && Object.keys(updateStep.out).includes('/{_item.file}'), 'Cascade update must write the updated file');
});

test('incremental-update.json no longer depends on missing DocCenter prompt paths', () => {
  const wf = JSON.parse(fs.readFileSync(path.join(WORKFLOWS_DIR, 'incremental-update.json'), 'utf-8'));
  assert(Object.keys(wf.registry.docs).sort().join(',') === '1', 'Incremental update should only depend on the syntax doc until dedicated prompts exist');
});

// ========== 3. Auto-Selection Logic ==========
console.log('\n📋 3. Auto-Selection Logic (import vl-adjust)');

// We can't directly import the function because it's not exported,
// so we test by pattern matching the same regexes
const featurePatterns = [
  /add\s*(a\s+)?(?:new\s+)?(?:button|form|field|chart|dialog|modal|table|tab|card|filter|sort|export|import|upload|download)/i,
  /(?:添加|增加|加)\s*(?:一个?\s*)?(?:按钮|表单|字段|图表|弹窗|对话框|表格|标签页|卡片|筛选|排序|导出|导入|上传|下载)/,
  /add\s*(a\s+)?feature/i,
  /(?:添加|增加)\s*功能/,
  /给.*(?:加|添加|增加)\s*(?:一个?)?\s*(?:按钮|功能|字段|表单|图表)/,
];

const dataPatterns = [
  /add\s*(a\s+)?(?:new\s+)?(?:field|column|table|index)/i,
  /(?:添加|增加|新增)\s*(?:字段|列|表|索引)/,
  /(?:modify|change|update)\s*(?:the\s+)?(?:database|schema|data\s*model|field\s*type)/i,
  /(?:修改|变更)\s*(?:数据库|数据模型|字段类型|表结构)/,
];

const batchPatterns = [
  /(?:batch|multiple|several|一批|批量|多个)\s*(?:change|update|fix|修改|调整|更新)/i,
  /(?:change|update|fix|修改|调整|更新)\s*(?:batch|multiple|several|一批|批量|多个)/i,
  /同时.*(?:修改|调整|更新|添加)/,
];

function matchesAny(text, patterns) {
  return patterns.some(p => p.test(text));
}

test('Detects "add a button" as add-feature', () => {
  assert(matchesAny('add a button to the home page', featurePatterns), 'Should match add-feature');
});

test('Detects "添加按钮" as add-feature', () => {
  assert(matchesAny('给首页添加一个按钮', featurePatterns), 'Should match add-feature (Chinese)');
});

test('Detects "add a chart" as add-feature', () => {
  assert(matchesAny('add a chart to show statistics', featurePatterns), 'Should match add-feature');
});

test('Detects "add feature" as add-feature', () => {
  assert(matchesAny('add a feature for export', featurePatterns), 'Should match add-feature');
});

test('Detects "add a field" as modify-data', () => {
  assert(matchesAny('add a field called price to Items table', dataPatterns), 'Should match modify-data');
});

test('Detects "添加字段" as modify-data', () => {
  assert(matchesAny('添加字段 description 到 Items 表', dataPatterns), 'Should match modify-data');
});

test('Detects "modify database schema" as modify-data', () => {
  assert(matchesAny('modify the database schema to add a new column', dataPatterns), 'Should match modify-data');
});

test('Detects "batch changes" as batch', () => {
  assert(matchesAny('batch update multiple files', batchPatterns), 'Should match batch');
});

test('Detects "批量修改" as batch', () => {
  assert(matchesAny('批量修改多个页面', batchPatterns), 'Should match batch');
});

// ========== 4. Test Project Validation ==========
console.log('\n📋 4. Test Project (AdjustTest) Validation');

test('Test project directory exists', () => {
  assert(fs.existsSync(TEST_PROJECT), 'AdjustTest directory should exist');
});

test('ProjectMeta.json is valid', () => {
  const metaPath = path.join(TEST_PROJECT, '.vl-code', 'ProjectMeta.json');
  const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
  assert(meta.projectName === 'AdjustTest', 'projectName should be AdjustTest');
  assert(meta.apps.length > 0, 'Should have at least one app');
  assert(meta.sections.length > 0, 'Should have at least one section');
  assert(meta.services.length > 0, 'Should have at least one service');
  assert(meta.database, 'Should have database');
});

test('All VL files referenced in meta exist', () => {
  const meta = JSON.parse(fs.readFileSync(path.join(TEST_PROJECT, '.vl-code', 'ProjectMeta.json'), 'utf-8'));
  for (const app of meta.apps) {
    assert(fs.existsSync(path.join(TEST_PROJECT, app.file)), `App file missing: ${app.file}`);
  }
  for (const section of meta.sections) {
    assert(fs.existsSync(path.join(TEST_PROJECT, section.file)), `Section file missing: ${section.file}`);
  }
  for (const service of meta.services) {
    assert(fs.existsSync(path.join(TEST_PROJECT, service.file)), `Service file missing: ${service.file}`);
  }
  assert(fs.existsSync(path.join(TEST_PROJECT, meta.database.file)), `Database file missing: ${meta.database.file}`);
  assert(fs.existsSync(path.join(TEST_PROJECT, meta.theme.file)), `Theme file missing: ${meta.theme.file}`);
});

test('VL files have correct version declaration', () => {
  const vlFiles = [
    'Apps/MainApp.vx',
    'Sections/HomePage.sc',
    'Sections/ItemList.sc',
    'Services/ItemService.vs',
    'Database/AppDB.vdb',
    'Theme/DefaultTheme.vth',
  ];
  for (const f of vlFiles) {
    const content = fs.readFileSync(path.join(TEST_PROJECT, f), 'utf-8');
    assert(content.startsWith('// VL_VERSION:3.5'), `${f} must start with VL_VERSION:3.5`);
  }
});

// ========== 5. Workflow-Param Mapping ==========
console.log('\n📋 5. Workflow-Param Mapping');

test('feature-adjust expects featureRequest param', () => {
  const wf = JSON.parse(fs.readFileSync(path.join(WORKFLOWS_DIR, 'feature-adjust.json'), 'utf-8'));
  assert(wf.registry.params.includes('featureRequest(STRING)'), 'Must have featureRequest param');
  assert(wf.registry.params.includes('currentMeta(OBJECT)'), 'Must have currentMeta param');
});

test('data-adjust expects dataRequest param', () => {
  const wf = JSON.parse(fs.readFileSync(path.join(WORKFLOWS_DIR, 'data-adjust.json'), 'utf-8'));
  assert(wf.registry.params.includes('dataRequest(STRING)'), 'Must have dataRequest param');
});

test('batch-adjust expects batchRequest param', () => {
  const wf = JSON.parse(fs.readFileSync(path.join(WORKFLOWS_DIR, 'batch-adjust.json'), 'utf-8'));
  assert(wf.registry.params.includes('batchRequest(STRING)'), 'Must have batchRequest param');
});

// ========== 6. Seed Workflows Sync ==========
console.log('\n📋 6. Seed Workflows Sync');

const seedDir = path.join(__dirname, 'public', 'seed-workflows');
for (const wfFile of ['feature-adjust.json', 'data-adjust.json', 'batch-adjust.json']) {
  test(`${wfFile} exists in seed-workflows`, () => {
    assert(fs.existsSync(path.join(seedDir, wfFile)), `Missing in seed-workflows: ${wfFile}`);
  });

  test(`${wfFile} matches between .vl-code/workflows and seed-workflows`, () => {
    const a = fs.readFileSync(path.join(WORKFLOWS_DIR, wfFile), 'utf-8');
    const b = fs.readFileSync(path.join(seedDir, wfFile), 'utf-8');
    assert(a === b, `Content mismatch for ${wfFile}`);
  });
}

// ========== Summary ==========
console.log(`\n${'='.repeat(50)}`);
console.log(`Results: ${passed} passed, ${failed} failed, ${passed + failed} total`);
console.log(`${'='.repeat(50)}\n`);

process.exit(failed > 0 ? 1 : 0);
