#!/usr/bin/env node
import { performance } from 'node:perf_hooks';

const DEFAULT_THRESHOLDS = {
  healthVersion: 500,
  requirementPool: 1000,
  versionDetail: 1500,
  workspace: 1200,
  xiaobaoWarning: 1000,
};

const DEFAULTS = {
  baseUrl: 'http://localhost:3001/api/v1',
  productId: 'perf-product-001',
  versionId: 'perf-version-001-001-001',
  userId: 'perf-user-dev-01',
  iterations: 5,
  warmup: 1,
  dryRun: false,
};

function parseArgs(argv) {
  const args = { ...DEFAULTS };
  for (let index = 0; index < argv.length; index += 1) {
    const arg = argv[index];
    if (arg === '--dry-run') {
      args.dryRun = true;
      continue;
    }
    if (arg === '--base-url') {
      args.baseUrl = argv[++index] ?? args.baseUrl;
      continue;
    }
    if (arg.startsWith('--base-url=')) {
      args.baseUrl = arg.slice('--base-url='.length);
      continue;
    }
    if (arg === '--product-id') {
      args.productId = argv[++index] ?? args.productId;
      continue;
    }
    if (arg === '--version-id') {
      args.versionId = argv[++index] ?? args.versionId;
      continue;
    }
    if (arg === '--user-id') {
      args.userId = argv[++index] ?? args.userId;
      continue;
    }
    if (arg === '--iterations') {
      args.iterations = parsePositiveInt(argv[++index], args.iterations);
      continue;
    }
    if (arg === '--warmup') {
      args.warmup = parsePositiveInt(argv[++index], args.warmup, 0);
      continue;
    }
    if (arg === '--help' || arg === '-h') {
      printHelp();
      process.exit(0);
    }
    throw new Error(`Unknown argument: ${arg}`);
  }
  args.baseUrl = args.baseUrl.replace(/\/+$/, '');
  return args;
}

function printHelp() {
  console.log(`Usage: node scripts/perf-check.mjs [--base-url URL] [--dry-run]

Runs the V2.6 hot API performance harness.

Options:
  --base-url      API root. Defaults to ${DEFAULTS.baseUrl}
  --product-id    Requirement pool product id. Defaults to ${DEFAULTS.productId}
  --version-id    Version detail id. Defaults to ${DEFAULTS.versionId}
  --user-id       Workspace user id. Defaults to ${DEFAULTS.userId}
  --iterations    Timed iterations per probe. Defaults to ${DEFAULTS.iterations}
  --warmup        Warmup iterations per probe. Defaults to ${DEFAULTS.warmup}
  --dry-run       Print probes without making HTTP requests.
`);
}

function parsePositiveInt(raw, fallback, min = 1) {
  const parsed = Number(raw);
  if (!Number.isFinite(parsed)) return fallback;
  return Math.max(min, Math.floor(parsed));
}

function buildProbes(args) {
  const requirementQuery = new URLSearchParams({
    productId: args.productId,
    q: 'REQ',
    limit: '50',
  });
  const workspaceQuery = new URLSearchParams({ userId: args.userId });
  const xiaobaoQuery = new URLSearchParams({ manager: 'true' });

  return [
    {
      key: 'healthVersion',
      label: 'health/version',
      method: 'GET',
      path: '/health/version',
      thresholdMs: DEFAULT_THRESHOLDS.healthVersion,
    },
    {
      key: 'requirementPool',
      label: 'v2.2 requirement query',
      method: 'GET',
      path: `/v2.2/requirements?${requirementQuery.toString()}`,
      thresholdMs: DEFAULT_THRESHOLDS.requirementPool,
    },
    {
      key: 'versionDetail',
      label: 'v2.2 version detail',
      method: 'GET',
      path: `/v2.2/versions/${encodeURIComponent(args.versionId)}/detail-data`,
      thresholdMs: DEFAULT_THRESHOLDS.versionDetail,
    },
    {
      key: 'workspace',
      label: 'v2.2 workspace',
      method: 'GET',
      path: `/v2.2/workspace?${workspaceQuery.toString()}`,
      thresholdMs: DEFAULT_THRESHOLDS.workspace,
    },
    {
      key: 'xiaobaoWarning',
      label: 'v2.2 xiaobao warning',
      method: 'GET',
      path: `/v2.2/xiaobao-warning?${xiaobaoQuery.toString()}`,
      thresholdMs: DEFAULT_THRESHOLDS.xiaobaoWarning,
    },
  ].map((probe) => ({
    ...probe,
    url: `${args.baseUrl}${probe.path}`,
  }));
}

async function measureProbe(probe, args) {
  for (let index = 0; index < args.warmup; index += 1) {
    await requestOnce(probe);
  }

  const samples = [];
  const statuses = new Map();
  const errors = [];
  for (let index = 0; index < args.iterations; index += 1) {
    const result = await requestOnce(probe);
    samples.push(result.durationMs);
    statuses.set(result.status, (statuses.get(result.status) ?? 0) + 1);
    if (result.error) errors.push(result.error);
    if (result.status < 200 || result.status >= 300) {
      errors.push(`HTTP ${result.status}`);
    }
  }

  const p50 = percentile(samples, 50);
  const p95 = percentile(samples, 95);
  if (p95 > probe.thresholdMs) {
    errors.push(`p95 ${p95.toFixed(1)}ms > budget ${probe.thresholdMs}ms`);
  }

  return {
    ...probe,
    count: samples.length,
    p50,
    p95,
    statuses: [...statuses.entries()].sort(([a], [b]) => a - b),
    ok: errors.length === 0,
    errors,
  };
}

async function requestOnce(probe) {
  const startedAt = performance.now();
  try {
    const response = await fetch(probe.url, { method: probe.method });
    await response.arrayBuffer();
    return {
      status: response.status,
      durationMs: performance.now() - startedAt,
    };
  } catch (error) {
    return {
      status: 0,
      durationMs: performance.now() - startedAt,
      error: error instanceof Error ? error.message : String(error),
    };
  }
}

function percentile(values, pct) {
  if (values.length === 0) return 0;
  const sorted = [...values].sort((a, b) => a - b);
  const index = Math.ceil((pct / 100) * sorted.length) - 1;
  return sorted[Math.max(0, Math.min(sorted.length - 1, index))];
}

function formatStatuses(statuses) {
  return statuses.map(([status, count]) => `${status}:${count}`).join(', ');
}

function printProbePlan(probes) {
  console.log('Performance harness dry-run');
  for (const probe of probes) {
    console.log(`${probe.key}\t${probe.method}\t${probe.url}\tbudget_p95_ms=${probe.thresholdMs}`);
  }
}

function printResults(results) {
  console.log('probe\tstatus\tp50_ms\tp95_ms\tbudget_p95_ms\tok');
  for (const result of results) {
    console.log([
      result.key,
      formatStatuses(result.statuses),
      result.p50.toFixed(1),
      result.p95.toFixed(1),
      result.thresholdMs,
      result.ok ? 'yes' : 'no',
    ].join('\t'));
    if (result.errors.length > 0) {
      for (const error of result.errors) console.error(`${result.key}: ${error}`);
    }
  }
}

async function main() {
  const args = parseArgs(process.argv.slice(2));
  const probes = buildProbes(args);
  if (args.dryRun) {
    printProbePlan(probes);
    return;
  }

  const results = [];
  for (const probe of probes) {
    results.push(await measureProbe(probe, args));
  }
  printResults(results);
  if (results.some((result) => !result.ok)) {
    process.exitCode = 1;
  }
}

main().catch((error) => {
  console.error(error instanceof Error ? error.message : error);
  process.exitCode = 1;
});

