#!/usr/bin/env node
import { pathToFileURL } from 'node:url';
import { flag, option, parseArgs } from './ops-utils.mjs';

function usage() {
  return `Usage: node scripts/smoke-test-release.mjs --base-url <url> [options]

Read-only release smoke checks:
  - backend runtime version
  - frontend root page
  - frontend products route
  - products API
  - V2.2 requirement read path
  - AI config public endpoint

Options:
  --base-url <url>          Deployment base URL (default: http://127.0.0.1)
  --expected-version <sha>  Expected /api/v1/health/version payload version
  --timeout-ms <number>     Per-request timeout (default: 5000)
  --dry-run                 Print planned checks without making requests
  --help                    Show this help
`;
}

function normalizeBaseUrl(value) {
  const url = new URL(value || 'http://127.0.0.1');
  url.pathname = url.pathname.replace(/\/+$/, '');
  url.search = '';
  url.hash = '';
  return url.toString().replace(/\/$/, '');
}

export function buildSmokeChecks(baseUrl, expectedVersion = '') {
  const checks = [
    {
      name: 'backend runtime version',
      path: '/api/v1/health/version',
      kind: 'json',
      validate(payload) {
        if (payload?.service !== 'server' || typeof payload.version !== 'string') {
          throw new Error(`unexpected version payload: ${JSON.stringify(payload)}`);
        }
        if (expectedVersion && payload.version !== expectedVersion) {
          throw new Error(`expected version ${expectedVersion}, got ${payload.version}`);
        }
      },
    },
    { name: 'frontend root', path: '/', kind: 'text' },
    { name: 'frontend products route', path: '/products', kind: 'text' },
    {
      name: 'products API',
      path: '/api/v1/products',
      kind: 'json',
      validate(payload) {
        if (!Array.isArray(payload)) {
          throw new Error('products API did not return an array');
        }
      },
    },
    {
      name: 'V2.2 requirement scoped read',
      path: '/api/v1/v2.2/requirements?productId=__smoke__&limit=1',
      kind: 'json',
      validate(payload) {
        if (!payload || !Array.isArray(payload.items)) {
          throw new Error('V2.2 requirements response missing items array');
        }
      },
    },
    {
      name: 'AI config public endpoint',
      path: '/api/v1/config/ai',
      kind: 'json',
      validate(payload) {
        if (!payload || !Array.isArray(payload.providers)) {
          throw new Error('AI config response missing providers array');
        }
      },
    },
  ];

  return checks.map((check) => ({
    ...check,
    url: new URL(check.path, `${baseUrl}/`).toString(),
  }));
}

async function fetchWithTimeout(url, timeoutMs) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    return await fetch(url, { signal: controller.signal });
  } finally {
    clearTimeout(timer);
  }
}

async function runCheck(check, timeoutMs) {
  const response = await fetchWithTimeout(check.url, timeoutMs);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  if (check.kind === 'json') {
    const payload = await response.json();
    check.validate?.(payload);
  } else {
    await response.text();
  }
}

export async function main(argv = process.argv.slice(2)) {
  const args = parseArgs(argv);
  if (flag(args, 'help')) {
    process.stdout.write(usage());
    return;
  }

  const baseUrl = normalizeBaseUrl(option(args, 'base-url', 'http://127.0.0.1'));
  const expectedVersion = option(args, 'expected-version', '');
  const timeoutMs = Number.parseInt(option(args, 'timeout-ms', '5000'), 10);
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
    throw new Error('--timeout-ms must be a positive number');
  }

  const checks = buildSmokeChecks(baseUrl, expectedVersion);
  if (flag(args, 'dry-run')) {
    process.stdout.write(`[dry-run] Release smoke target: ${baseUrl}\n`);
    for (const check of checks) {
      process.stdout.write(`GET ${check.url} # ${check.name}\n`);
    }
    return;
  }

  process.stdout.write(`Release smoke target: ${baseUrl}\n`);
  const failures = [];
  for (const check of checks) {
    try {
      await runCheck(check, timeoutMs);
      process.stdout.write(`[pass] ${check.name} ${check.url}\n`);
    } catch (error) {
      failures.push(`[fail] ${check.name} ${check.url}: ${error.message}`);
      process.stderr.write(`${failures.at(-1)}\n`);
    }
  }

  if (failures.length > 0) {
    throw new Error(`Release smoke failed: ${failures.length} check(s) failed`);
  }

  process.stdout.write('Release smoke test passed.\n');
}

if (import.meta.url === pathToFileURL(process.argv[1]).href) {
  main().catch((error) => {
    process.stderr.write(`${error.message}\n`);
    process.exit(1);
  });
}

