#!/usr/bin/env node
/**
 * Seed ByteLyst tracker items from a JSON payload file.
 *
 * Creates feature/bug/task items via the platform-service tracker API
 * (POST /api/items — the same endpoint tracker-web proxies to). Items are
 * scoped per `productId`. Use this to file the ENGINEERING_REVIEW_SCORECARD.md
 * P0-P3 work once the platform stack is running.
 *
 * Auth: mints a short-lived HS256 access token signed with $JWT_SECRET
 * (same secret platform-service verifies with). No external deps — uses
 * node:crypto only.
 *
 * Env:
 *   PLATFORM_API_URL  Base URL of platform-service (default http://localhost:4003)
 *   JWT_SECRET        Shared JWT secret (required unless --dry-run)
 *   SEED_SUB          Token subject / reportedBy (default "eng-review-bot")
 *   SEED_EMAIL        Token email claim (default "eng-review-bot@bytelyst.local")
 *   SEED_ROLE         Token role claim (default "admin")
 *   ITEMS_FILE        Payload file (default ./engineering-review-items.json)
 *
 * Flags:
 *   --dry-run   Print what would be created; no token, no network calls.
 *   --force     Skip the dedupe-by-title check.
 *
 * Examples:
 *   node seed-tracker-items.mjs --dry-run
 *   JWT_SECRET=... PLATFORM_API_URL=http://localhost:4003 node seed-tracker-items.mjs
 */

import { readFileSync } from 'node:fs';
import { createHmac } from 'node:crypto';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';

const __dirname = dirname(fileURLToPath(import.meta.url));

const args = new Set(process.argv.slice(2));
const DRY_RUN = args.has('--dry-run');
const FORCE = args.has('--force');

const API = (process.env.PLATFORM_API_URL || 'http://localhost:4003').replace(/\/$/, '');
const SECRET = process.env.JWT_SECRET || '';
const SUB = process.env.SEED_SUB || 'eng-review-bot';
const EMAIL = process.env.SEED_EMAIL || 'eng-review-bot@bytelyst.local';
const ROLE = process.env.SEED_ROLE || 'admin';
const ITEMS_FILE = resolve(__dirname, process.env.ITEMS_FILE || 'engineering-review-items.json');

function b64url(buf) {
  return Buffer.from(buf).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

/** Mint an HS256 access token compatible with @bytelyst/auth extractAuth(). */
function mintToken() {
  if (!SECRET) throw new Error('JWT_SECRET must be set (or use --dry-run)');
  const now = Math.floor(Date.now() / 1000);
  const header = { alg: 'HS256', typ: 'JWT' };
  const payload = { sub: SUB, email: EMAIL, role: ROLE, type: 'access', iat: now, exp: now + 3600 };
  const head = b64url(JSON.stringify(header));
  const body = b64url(JSON.stringify(payload));
  const sig = b64url(createHmac('sha256', SECRET).update(`${head}.${body}`).digest());
  return `${head}.${body}.${sig}`;
}

async function listTitles(token, productId) {
  // limit max is 100 (server caps it); 500 would 400 and silently break dedupe.
  const url = `${API}/api/items?productId=${encodeURIComponent(productId)}&limit=100`;
  const res = await fetch(url, {
    headers: { authorization: `Bearer ${token}`, 'x-product-id': productId },
  });
  if (!res.ok) throw new Error(`list failed (${res.status})`);
  const data = await res.json();
  return new Set((data.items || []).map((i) => i.title));
}

/** Derive an uppercase-letters-only license prefix (<=8) from a productId. */
function licensePrefix(productId) {
  return productId.replace(/[^a-z]/gi, '').toUpperCase().slice(0, 8) || 'PRD';
}

/**
 * Ensure a product exists before creating items under it. POST /items skips
 * product validation when productId is in the body, but GET /items validates —
 * so without registration the dedupe list 400s ("Unknown product") and
 * re-runs create duplicates. Idempotent: treats 409/200/201 as success.
 */
async function ensureProduct(token, productId) {
  const res = await fetch(`${API}/api/products`, {
    method: 'POST',
    headers: {
      authorization: `Bearer ${token}`,
      'content-type': 'application/json',
      'x-product-id': productId,
    },
    body: JSON.stringify({
      productId,
      displayName: productId.charAt(0).toUpperCase() + productId.slice(1),
      licensePrefix: licensePrefix(productId),
    }),
  });
  if (!res.ok && res.status !== 409) {
    console.warn(`  warn: register ${productId} -> HTTP ${res.status}`);
  }
}

async function createItem(token, item) {
  const res = await fetch(`${API}/api/items`, {
    method: 'POST',
    headers: {
      authorization: `Bearer ${token}`,
      'content-type': 'application/json',
      'x-product-id': item.productId,
    },
    body: JSON.stringify(item),
  });
  const text = await res.text();
  if (!res.ok) throw new Error(`${res.status} ${text.slice(0, 300)}`);
  return JSON.parse(text);
}

async function main() {
  const raw = JSON.parse(readFileSync(ITEMS_FILE, 'utf8'));
  const items = raw.items || raw;
  console.log(`Loaded ${items.length} item(s) from ${ITEMS_FILE}`);
  console.log(`Target: ${API}/api/items  (dry-run=${DRY_RUN}, force=${FORCE})\n`);

  if (DRY_RUN) {
    for (const it of items) {
      console.log(`  [DRY] ${it.productId.padEnd(16)} ${it.type}/${it.priority}  ${it.title}`);
    }
    console.log(`\nDry run only — nothing sent. ${items.length} item(s) would be created.`);
    return;
  }

  const token = mintToken();

  // Register every referenced product first so the dedupe list (GET /items)
  // doesn't 400 on unknown products — which would silently cause duplicates.
  const productIds = [...new Set(items.map((it) => it.productId))];
  console.log(`Ensuring ${productIds.length} product(s) are registered...`);
  for (const pid of productIds) await ensureProduct(token, pid);

  const titleCache = new Map();
  let created = 0;
  let skipped = 0;
  let failed = 0;

  for (const it of items) {
    try {
      if (!FORCE) {
        if (!titleCache.has(it.productId)) {
          titleCache.set(
            it.productId,
            await listTitles(token, it.productId).catch((e) => {
              console.warn(`  warn: dedupe list for ${it.productId} failed (${e.message}); may create duplicates`);
              return new Set();
            })
          );
        }
        if (titleCache.get(it.productId).has(it.title)) {
          console.log(`  SKIP   ${it.productId.padEnd(16)} ${it.title} (already exists)`);
          skipped++;
          continue;
        }
      }
      const res = await createItem(token, it);
      console.log(`  OK     ${it.productId.padEnd(16)} ${res.id}  ${it.title}`);
      created++;
    } catch (err) {
      console.error(`  FAIL   ${it.productId.padEnd(16)} ${it.title}\n         ${err.message}`);
      failed++;
    }
  }

  console.log(`\nDone. created=${created} skipped=${skipped} failed=${failed}`);
  if (failed > 0) process.exit(1);
}

main().catch((err) => {
  console.error(`Fatal: ${err.message}`);
  process.exit(1);
});
