import fs from 'fs/promises';
import path from 'path';
import { WorkflowExecutor } from './src/vl/workflow-executor.js';

let passed = 0;
let failed = 0;

function test(name, fn) {
  return Promise.resolve()
    .then(fn)
    .then(() => {
      console.log(`  ✓ ${name}`);
      passed++;
    })
    .catch((err) => {
      console.log(`  ✗ ${name}: ${err.message}`);
      failed++;
    });
}

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

function json(value) {
  return JSON.stringify(value);
}

function normalizeMessages(messages = []) {
  return messages.map((msg) => ({
    role: msg.role,
    text: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
  }));
}

async function collectFiles(dir, prefix = '') {
  const entries = await fs.readdir(path.join(dir, prefix), { withFileTypes: true }).catch(() => []);
  const files = [];
  for (const entry of entries) {
    const rel = path.join(prefix, entry.name);
    if (entry.isDirectory()) files.push(...await collectFiles(dir, rel));
    else files.push(rel);
  }
  return files.sort();
}

function makeCurrentMeta() {
  return {
    specVersion: 'ProjectMeta/1.0',
    projectName: 'OpsSuite',
    vlVersion: '3.5',
    config: {
      defaultDevice: 'Phone',
      defaultResolution: '375x812',
      themeFile: 'Theme/BrandTheme.vth',
      colorScheme: 'light',
    },
    database: { file: 'Database/AppDB.vdb' },
    theme: { file: 'Theme/BrandTheme.vth' },
    services: [
      { id: 'ReportService', domainId: 'ReportService', file: 'Services/ReportService.vs', filePath: 'Services/ReportService.vs' },
    ],
    components: [
      { id: 'StatusCard', file: 'ExtComponents/StatusCard.cp', filePath: 'ExtComponents/StatusCard.cp' },
    ],
    sections: [
      { id: 'Dashboard', file: 'Sections/Dashboard.sc', filePath: 'Sections/Dashboard.sc' },
    ],
    apps: [
      { id: 'MainApp', file: 'Apps/MainApp.vx', filePath: 'Apps/MainApp.vx' },
      { id: 'AdminApp', file: 'Apps/AdminPortalShell.vx', filePath: 'Apps/AdminPortalShell.vx' },
    ],
  };
}

async function seedProject(workDir) {
  const files = {
    '.vl-code/ProjectMeta.json': json(makeCurrentMeta()),
    'Database/AppDB.vdb': '// VL_VERSION:3.5\nDATABASE AppDB { }\n',
    'Theme/BrandTheme.vth': '// VL_VERSION:3.5\nTHEME BrandTheme {\n  COLOR primary = #005B4F\n}\n',
    'Services/ReportService.vs': '// VL_VERSION:3.5\nSERVICE ReportService {\n  METHOD list(): [OBJECT]\n}\n',
    'Sections/Dashboard.sc': '// VL_VERSION:3.5\nSECTION Dashboard {\n  INIT { }\n}\n',
    'ExtComponents/StatusCard.cp': '// VL_VERSION:3.5\nCOMPONENT StatusCard {\n  VIEW { Text("status") }\n}\n',
    'Apps/MainApp.vx': '// VL_VERSION:3.5\nAPP MainApp {\n  ROUTES { }\n}\n',
    'Apps/AdminPortalShell.vx': '// VL_VERSION:3.5\nAPP AdminApp {\n  ROUTES { path:\"/dashboard\" -> Dashboard }\n}\n',
  };

  for (const [rel, content] of Object.entries(files)) {
    const full = path.join(workDir, rel);
    await fs.mkdir(path.dirname(full), { recursive: true });
    await fs.writeFile(full, content, 'utf8');
  }
}

function createAdjustLLM() {
  let themeCascadeCount = 0;
  return {
    async call(params) {
      const allText = normalizeMessages(params.messages).map((msg) => msg.text).join('\n');

      if (allText.includes('Plan the new page. Determine')) {
        const updatedMeta = makeCurrentMeta();
        updatedMeta.sections = [
          ...updatedMeta.sections,
          {
            id: 'AnalyticsPage',
            file: 'Sections/AnalyticsPage.sc',
            filePath: 'Sections/AnalyticsPage.sc',
            consumesServices: ['ReportService'],
            usesComponents: ['KpiCard'],
          },
        ];
        updatedMeta.components = [
          ...updatedMeta.components,
          { id: 'KpiCard', file: 'ExtComponents/KpiCard.cp', filePath: 'ExtComponents/KpiCard.cp' },
        ];
        return {
          content: json({
            section: {
              id: 'AnalyticsPage',
              filePath: 'Sections/AnalyticsPage.sc',
              description: 'Analytics dashboard page',
              consumesServices: ['ReportService'],
              usesComponents: ['KpiCard'],
              bindings: [],
              events: [],
            },
            components: [
              { id: 'KpiCard', name: 'KpiCard', filePath: 'ExtComponents/KpiCard.cp', description: 'Summary KPI card' },
            ],
            service: {
              name: 'ReportService',
              domainId: 'ReportService',
              filePath: 'Services/ReportService.vs',
              methods: [{ id: 'getAnalytics' }],
            },
            targetApp: 'AdminApp',
            targetAppFilePath: 'Apps/AdminPortalShell.vx',
            route: '/analytics',
            updatedMeta,
          }),
          model: 'fake-adjust',
          usage: {},
        };
      }

      if (allText.includes('Plan the new service domain. Determine')) {
        const updatedMeta = makeCurrentMeta();
        updatedMeta.services = [
          ...updatedMeta.services,
          { id: 'AuditService', domainId: 'AuditService', file: 'Services/AuditService.vs', filePath: 'Services/AuditService.vs' },
        ];
        return {
          content: json({
            service: {
              name: 'AuditService',
              domainId: 'AuditService',
              filePath: 'Services/AuditService.vs',
              methods: [{ id: 'listAuditLogs' }],
              routes: [],
            },
            dbChanges: {
              newTables: [{ id: 'AuditLog' }],
              modifiedTables: [],
            },
            updatedMeta,
          }),
          model: 'fake-adjust',
          usage: {},
        };
      }

      if (allText.includes('Analyze the current theme and plan the changes')) {
        return {
          content: json({
            tokenChanges: [{ token: 'primary', oldValue: '#005B4F', newValue: '#0A7A68' }],
            affectedFiles: [
              { file: 'Sections/Dashboard.sc', reason: 'Uses primary theme token' },
              { file: 'ExtComponents/StatusCard.cp', reason: 'Uses primary theme token' },
            ],
            summary: 'Refresh primary accent color',
          }),
          model: 'fake-adjust',
          usage: {},
        };
      }

      if (allText.includes('Generate the .sc section file for this new section')) {
        return { content: '// VL_VERSION:3.5\nSECTION AnalyticsPage {\n  INIT { }\n}\n', model: 'fake-adjust', usage: {} };
      }

      if (allText.includes('Generate the .cp component file for:')) {
        return { content: '// VL_VERSION:3.5\nCOMPONENT KpiCard {\n  VIEW { Text("kpi") }\n}\n', model: 'fake-adjust', usage: {} };
      }

      if (allText.includes('Add or update the service domain with new methods for the page')) {
        return { content: '// VL_VERSION:3.5\nSERVICE ReportService {\n  METHOD list(): [OBJECT]\n  METHOD getAnalytics(): OBJECT\n}\n', model: 'fake-adjust', usage: {} };
      }

      if (allText.includes('Update the app .vx file to include the new page route')) {
        return { content: '// VL_VERSION:3.5\nAPP AdminApp {\n  ROUTES { path:"/dashboard" -> Dashboard, path:"/analytics" -> AnalyticsPage }\n}\n', model: 'fake-adjust', usage: {} };
      }

      if (allText.includes('Update the .vdb database schema file')) {
        return { content: '// VL_VERSION:3.5\nDATABASE AppDB {\n  TABLE AuditLog { id INT }\n}\n', model: 'fake-adjust', usage: {} };
      }

      if (allText.includes('Generate the .vs service domain file for:')) {
        return { content: '// VL_VERSION:3.5\nSERVICE AuditService {\n  METHOD listAuditLogs(): [OBJECT]\n}\n', model: 'fake-adjust', usage: {} };
      }

      if (allText.includes('Generate the COMPLETE updated .vth theme file')) {
        return { content: '// VL_VERSION:3.5\nTHEME BrandTheme {\n  COLOR primary = #0A7A68\n}\n', model: 'fake-adjust', usage: {} };
      }

      if (allText.includes('Update this file to use the new theme tokens')) {
        themeCascadeCount += 1;
        if (themeCascadeCount === 1) {
          return { content: '// VL_VERSION:3.5\nSECTION Dashboard {\n  INIT { }\n  VIEW { Text("dashboard") }\n}\n', model: 'fake-adjust', usage: {} };
        }
        return { content: '// VL_VERSION:3.5\nCOMPONENT StatusCard {\n  VIEW { Text("status-updated") }\n}\n', model: 'fake-adjust', usage: {} };
      }

      throw new Error(`Unhandled fake LLM prompt: ${allText.slice(0, 220)}`);
    },
  };
}

async function runWorkflow(fileName, params) {
  const workflow = JSON.parse(await fs.readFile(path.join(process.cwd(), '.vl-code', 'workflows', fileName), 'utf8'));
  const workDir = path.join('/tmp/vlcode-lite-adjust-workflows', `${fileName.replace(/\.json$/, '')}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
  await fs.rm(workDir, { recursive: true, force: true });
  await fs.mkdir(workDir, { recursive: true });
  await seedProject(workDir);

  const executor = new WorkflowExecutor({ workDir, model: 'fake-adjust' });
  executor._resolveDocCenterDocs = async () => {};
  executor._buildLLMAdapter = function buildLLMAdapter() {
    return createAdjustLLM();
  };

  const errors = [];
  await executor.execute(workflow, params, {
    onNodeError: (evt) => errors.push(evt.error || evt),
    onError: (msg) => errors.push(msg),
  });

  return { workDir, files: await collectFiles(workDir), errors };
}

console.log('\n── Adjust Workflow Execution Regression ──');

await test('add-page writes the planned target app file instead of dropping the route update', async () => {
  const currentMeta = makeCurrentMeta();
  const result = await runWorkflow('add-page.json', {
    pageDescription: 'Add an analytics page to the admin app',
    currentMeta,
    approved: true,
  });

  assert(result.errors.length === 0, `Unexpected errors: ${result.errors.join(', ')}`);
  assert(result.files.includes('Sections/AnalyticsPage.sc'), 'Expected new section file');
  assert(result.files.includes('ExtComponents/KpiCard.cp'), 'Expected new component file');
  assert(result.files.includes('Services/ReportService.vs'), 'Expected service file update');
  assert(result.files.includes('Apps/AdminPortalShell.vx'), 'Expected target app file update');
});

await test('add-service respects currentMeta.database.file instead of hardcoding Database/main.vdb', async () => {
  const currentMeta = makeCurrentMeta();
  const result = await runWorkflow('add-service.json', {
    serviceDescription: 'Add audit log backend service',
    currentMeta,
  });

  assert(result.errors.length === 0, `Unexpected errors: ${result.errors.join(', ')}`);
  assert(result.files.includes('Database/AppDB.vdb'), 'Expected dynamic database path');
  assert(!result.files.includes('Database/main.vdb'), 'Should not write hardcoded main.vdb');
  assert(result.files.includes('Services/AuditService.vs'), 'Expected generated service file');
});

await test('theme-customize writes both the theme file and cascade-updated VL files', async () => {
  const currentMeta = makeCurrentMeta();
  const result = await runWorkflow('theme-customize.json', {
    themeRequest: 'Refresh the brand color',
    currentMeta,
  });

  assert(result.errors.length === 0, `Unexpected errors: ${result.errors.join(', ')}`);
  assert(result.files.includes('Theme/BrandTheme.vth'), 'Expected dynamic theme file path');
  assert(result.files.includes('Sections/Dashboard.sc'), 'Expected updated section file');
  assert(result.files.includes('ExtComponents/StatusCard.cp'), 'Expected updated component file');
});

console.log(`\n── Results ──\n\n  ${passed} passed, ${failed} failed\n`);
process.exit(failed > 0 ? 1 : 0);
