/**
 * Loop Contention Scenario
 *
 * Launches 10 workflows that each perform many loop iterations.
 * Each iteration calls updateExecutionVariables, creating sustained
 * write pressure on execution documents and variable storage.
 *
 * Usage:
 *   bunx tsx stress-tests/scenarios/loop-contention.ts
 *
 * Requires: CONVEX_URL, ORGANIZATION_ID, WORKFLOW_DEFINITION_ID
 *           (use a workflow definition that includes a loop step)
 */

import { ConvexHttpClient } from 'convex/browser';

import { api } from '../../convex/_generated/api';
import type { Id } from '../../convex/_generated/dataModel';
import { scenarios } from '../fixtures/stress-workflows';
import type { ExecutionStatus } from '../metrics';
import { MetricsCollector } from '../metrics';

const config = scenarios.loop_contention;

async function run() {
  const convexUrl = process.env.CONVEX_URL || process.env.VITE_CONVEX_URL || '';
  const organizationId = process.env.ORGANIZATION_ID || '';
  const workflowSlug = process.env.WORKFLOW_DEFINITION_ID || '';

  if (!convexUrl || !organizationId || !workflowSlug) {
    console.error(
      'Required env vars: CONVEX_URL, ORGANIZATION_ID, WORKFLOW_DEFINITION_ID',
    );
    process.exit(1);
  }

  const client = new ConvexHttpClient(convexUrl);
  const metrics = new MetricsCollector();
  const rawLoopIterations = config.inputOverrides?.loopIterations;
  const loopIterations =
    typeof rawLoopIterations === 'number' ? rawLoopIterations : 50;

  console.log(`\n[${config.name}] ${config.description}`);
  console.log(
    `${config.total} workflows x ${loopIterations} loop iterations\n`,
  );

  const executionMap = new Map<string, Id<'wfExecutions'>>();

  const launches = Array.from({ length: config.total }, async (_, i) => {
    const id = `wf_${i}`;
    metrics.track(id);

    try {
      const executionId = await client.action(
        api.wf_executions.actions.startWorkflowFromFile,
        {
          organizationId,
          workflowSlug,
          input: {
            stressTest: true,
            loopIterations,
            scenarioIndex: i,
            timestamp: Date.now(),
          },
          triggeredBy: 'stress-test:loop-contention',
          triggerData: {
            triggerType: 'manual',
            reason: 'loop-contention',
            loopIterations,
          },
        },
      );
      if (!executionId) throw new Error('Workflow start returned null');
      metrics.update(id, 'running');
      executionMap.set(id, executionId);
    } catch (error) {
      metrics.update(
        id,
        'failed',
        error instanceof Error ? error.message : String(error),
      );
    }
  });

  await Promise.all(launches);
  console.log(
    `Started: ${executionMap.size}/${config.total} | Failed: ${config.total - executionMap.size}`,
  );

  const pending = new Set(executionMap.keys());
  const startTime = Date.now();

  while (pending.size > 0) {
    if (Date.now() - startTime > config.stuckThresholdMs) {
      for (const id of pending) metrics.markStuck(id);
      console.log(`\nTimeout: ${pending.size} still pending`);
      break;
    }

    const batch = Array.from(pending).slice(0, 20);
    await Promise.all(
      batch.map(async (id) => {
        const executionId = executionMap.get(id);
        if (!executionId) return;

        try {
          const execution = await client.query(
            api.wf_executions.queries.getRawExecution,
            { executionId },
          );
          if (!execution) return;
          const status = execution.status as ExecutionStatus;
          if (status === 'completed' || status === 'failed') {
            const metadata = execution.metadata
              ? JSON.parse(execution.metadata)
              : {};
            metrics.update(id, status, metadata.error);
            pending.delete(id);
          }
        } catch {
          // transient
        }
      }),
    );

    if (pending.size > 0) {
      const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);
      process.stdout.write(
        `\r  Pending: ${pending.size}/${executionMap.size}  (${elapsed}s)`,
      );
      await new Promise((r) => setTimeout(r, config.pollIntervalMs));
    }
  }

  console.log('');
  const report = metrics.printReport();

  const fs = await import('fs');
  const reportPath = `stress-tests/report-loop-${Date.now()}.json`;
  fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
  console.log(`Report: ${reportPath}`);
}

run().catch((err) => {
  console.error('Scenario failed:', err);
  process.exit(1);
});
