#!/usr/bin/env node
import assert from 'assert';
import express from 'express';
import fs from 'fs';
import os from 'os';
import path from 'path';
import { setupWorkflowRoutes } from './src/server/routes/workflow.js';
import { ToolRegistry } from './src/core/tool-registry.js';
import { createReadFileTool } from './src/tools/read-file.js';

function createTempProject() {
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'vlcode-rerun-route-'));
  fs.mkdirSync(path.join(dir, '.vl-code', 'workflows'), { recursive: true });
  fs.mkdirSync(path.join(dir, 'Process'), { recursive: true });
  fs.writeFileSync(path.join(dir, '.vl-code', 'workspace.json'), '{}');
  fs.writeFileSync(path.join(dir, 'VL.md'), '# RouteStateTest\n// VL_VERSION:3.5\n', 'utf8');
  return dir;
}

async function readSSE(response, onEvent) {
  assert.equal(response.status, 200);
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    while (buffer.includes('\n\n')) {
      const idx = buffer.indexOf('\n\n');
      const raw = buffer.slice(0, idx);
      buffer = buffer.slice(idx + 2);
      if (!raw.trim()) continue;
      const lines = raw.split('\n');
      const event = (lines.find((line) => line.startsWith('event: ')) || '').slice(7).trim();
      const dataLine = lines.find((line) => line.startsWith('data: '));
      const data = dataLine ? JSON.parse(dataLine.slice(6)) : {};
      await onEvent(event, data);
    }
  }
}

async function main() {
  const workDir = createTempProject();
  const workflowName = 'pause-tool-smoke';
  const workflowPath = path.join(workDir, '.vl-code', 'workflows', `${workflowName}.json`);
  fs.writeFileSync(workflowPath, JSON.stringify({
    version: '3.16',
    name: 'PauseToolSmoke',
    steps: [
      { id: 'Set_010_Seed', target: '$msg', value: '="start"', next: 'Tool_020_ReadFile' },
      { id: 'Tool_020_ReadFile', input: { file_path: '="VL.md"' }, out: { '$file': '=_result' }, next: 'Pause_030_Approve' },
      { id: 'Pause_030_Approve', resumeResultTarget: '$approved', next: 'Branch_040_Check' },
      { id: 'Branch_040_Check', cases: [{ condition: '=$approved === false', next: 'Stop_050_Cancel' }], default: 'Set_060_Result' },
      { id: 'Set_060_Result', target: '$final', value: '=$approved ? "approved" : "rejected"', next: 'Write_070_Output' },
      { id: 'Write_070_Output', target: '="Process/pause-tool-smoke.txt"', value: '=$final + "|" + $file.slice(0, 10)', next: 'Stop_080_End' },
      { id: 'Stop_050_Cancel' },
      { id: 'Stop_080_End' },
    ],
  }, null, 2), 'utf8');

  const toolRegistry = new ToolRegistry();
  toolRegistry.register('ReadFile', createReadFileTool({ workDir }));

  const fakeServer = {
    config: {
      workDir,
      model: 'claude-opus-4-6',
      toolRegistry,
    },
    symbolIndex: null,
    _activeExecutor: null,
    _activeExecutors: new Map(),
    _currentRunState: null,
    _uiState: null,
    broadcast() {},
    autoExtractMeta() {},
    projectContext: {
      async scan() {},
    },
  };

  const app = express();
  app.use(express.json({ limit: '10mb' }));
  setupWorkflowRoutes(app, fakeServer);
  const httpServer = app.listen(0);
  await new Promise((resolve, reject) => {
    httpServer.once('listening', resolve);
    httpServer.once('error', reject);
  });
  const { port } = httpServer.address();
  const base = `http://127.0.0.1:${port}`;

  try {
    let runID = null;
    let activeState = null;
    const clientRunToken = 'route-state-1';
    const executeEvents = [];
    const executeResponse = await fetch(`${base}/api/workflow/execute`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ workflowName, clientRunToken }),
    });
    await readSSE(executeResponse, async (event, data) => {
      executeEvents.push(event);
      if (data.runID && !runID) runID = data.runID;
      if (event === 'pause') {
        const activeStateResponse = await fetch(`${base}/api/workflow/current-state`);
        activeState = await activeStateResponse.json();
        const resumeResponse = await fetch(`${base}/api/workflow/resume`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ runID, nodeId: data.nodeId, payload: true }),
        });
        const resumeBody = await resumeResponse.json();
        assert.equal(resumeBody.ok, true);
      }
    });

    assert(runID, 'runID missing from execute stream');
    assert(executeEvents.includes('tool_start'), 'missing tool_start event');
    assert(executeEvents.includes('tool_done'), 'missing tool_done event');
    assert(executeEvents.includes('checkpoint'), 'missing checkpoint event');
    assert(executeEvents.includes('pause'), 'missing pause event');
    assert(executeEvents.includes('resumed'), 'missing resumed event');
    assert(executeEvents.includes('done'), 'missing done event');
    assert.equal(activeState.active, true);
    assert.equal(activeState.workflowName, workflowName);
    assert.equal(activeState.runID, runID);
    assert.equal(activeState.clientRunToken, clientRunToken);
    assert.equal(activeState.checkpoint?.currentStepID, 'Tool_020_ReadFile');

    const checkpointResponse = await fetch(`${base}/api/workflow/${runID}/checkpoint`);
    const checkpoint = await checkpointResponse.json();
    assert.equal(checkpoint.status, 'completed');

    const currentStateResponse = await fetch(`${base}/api/workflow/current-state`);
    const currentState = await currentStateResponse.json();
    assert.equal(currentState.active, false);
    assert.equal(currentState.workflowName, null);
    assert.equal(currentState.nodeStatuses.Stop_080_End, 'done');

    const outputPath = path.join(workDir, 'Process', 'pause-tool-smoke.txt');
    assert(fs.readFileSync(outputPath, 'utf8').startsWith('approved|'));

    const rerunEvents = [];
    const rerunResponse = await fetch(`${base}/api/workflow/rerun`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        workflowName,
        checkpoint,
        stepID: 'Write_070_Output',
        overrides: { '$final': 'rerun' },
      }),
    });
    await readSSE(rerunResponse, async (event, _data) => {
      rerunEvents.push(event);
    });
    assert(rerunEvents.includes('done'), 'missing rerun done event');

    const rerunCheckpointResponse = await fetch(`${base}/api/workflow/${runID}/checkpoint`);
    const rerunCheckpoint = await rerunCheckpointResponse.json();
    assert.equal(rerunCheckpoint.status, 'completed');

    const rerunStateResponse = await fetch(`${base}/api/workflow/current-state`);
    const rerunState = await rerunStateResponse.json();
    assert.equal(rerunState.active, false);
    assert.equal(rerunState.workflowName, null);
    assert.equal(rerunState.nodeStatuses.Write_070_Output, 'done');
    assert.equal(rerunState.nodeStatuses.Stop_080_End, 'done');
    assert(fs.readFileSync(outputPath, 'utf8').startsWith('rerun|'));
  } finally {
    await new Promise((resolve) => httpServer.close(resolve));
    fs.rmSync(workDir, { recursive: true, force: true });
  }

  console.log('\n-- Workflow Rerun Route State --');
  console.log('PASS test-workflow-rerun-route-state.js');
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});
