#!/usr/bin/env tsx
/**
 * render-for-instance.ts — Approach C Phase 0 verification harness.
 *
 * Loads a CCP-tracked Instance row, builds its template context, and renders
 * all templates to a scratch directory under /tmp/render-<slug>/. Operator
 * then diffs the rendered output against the tenant's actual on-disk files
 * to verify the template-vs-prod-compose equivalence contract.
 *
 * Usage (run inside ccp-api container):
 *   docker compose exec ccp-api npx tsx scripts/render-for-instance.ts --slug changemakerlite
 *   docker compose exec ccp-api npx tsx scripts/render-for-instance.ts --id <uuid>
 *
 * Output: prints scratch dir path; exits 0 on success, 1 on failure.
 *
 * This script does NOT touch any tenant. It only reads from the CCP database
 * and writes to /tmp on the CCP api container.
 */

import { prisma } from '../src/lib/prisma';
import { decryptJson } from '../src/utils/encryption';
import {
  buildTemplateContext,
  renderAllTemplates,
} from '../src/services/template-engine';
import path from 'node:path';
import fs from 'node:fs/promises';

interface Args {
  slug?: string;
  id?: string;
  outDir?: string;
}

function parseArgs(argv: string[]): Args {
  const args: Args = {};
  for (let i = 0; i < argv.length; i++) {
    const a = argv[i];
    if (a === '--slug' && argv[i + 1]) { args.slug = argv[++i]; continue; }
    if (a === '--id' && argv[i + 1])   { args.id   = argv[++i]; continue; }
    if (a === '--out' && argv[i + 1])  { args.outDir = argv[++i]; continue; }
    if (a === '-h' || a === '--help') {
      console.log('usage: render-for-instance.ts (--slug X | --id Y) [--out /tmp/render-X]');
      process.exit(0);
    }
  }
  return args;
}

async function main() {
  const args = parseArgs(process.argv.slice(2));
  if (!args.slug && !args.id) {
    console.error('error: --slug or --id is required');
    process.exit(1);
  }

  const instance = await prisma.instance.findUnique({
    where: args.id ? { id: args.id } : { slug: args.slug! },
  });
  if (!instance) {
    console.error(`error: instance not found (slug=${args.slug ?? '?'}, id=${args.id ?? '?'})`);
    process.exit(1);
  }

  // For isRegistered tenants there are no encrypted secrets. Use empty stubs
  // so buildTemplateContext doesn't crash; env.hbs values that read from
  // {{secrets.*}} will render as blank, which is fine for diff purposes
  // because the tenant's own .env still has the real values via install.sh.
  let secrets: Record<string, string> = {};
  if (instance.encryptedSecrets) {
    try {
      secrets = decryptJson<Record<string, string>>(instance.encryptedSecrets);
    } catch (err) {
      console.warn(`warn: decryptJson failed (${(err as Error).message}); using empty secrets`);
    }
  } else {
    console.log(`(isRegistered=true tenant; using empty secrets for compose/nginx render — env.hbs values will be blank)`);
  }

  const outDir = args.outDir ?? path.join('/tmp', `render-${instance.slug}`);
  await fs.rm(outDir, { recursive: true, force: true });
  await fs.mkdir(outDir, { recursive: true });

  const context = buildTemplateContext(instance, secrets);
  await renderAllTemplates(context, outDir);

  // Summarize what we rendered
  const entries: string[] = [];
  async function walk(dir: string, rel = '') {
    const items = await fs.readdir(dir, { withFileTypes: true });
    for (const item of items) {
      const full = path.join(dir, item.name);
      const r = path.join(rel, item.name);
      if (item.isDirectory()) await walk(full, r);
      else entries.push(r);
    }
  }
  await walk(outDir);

  console.log(`\n=== rendered ${entries.length} files to: ${outDir} ===`);
  for (const e of entries.sort()) {
    const stat = await fs.stat(path.join(outDir, e));
    console.log(`  ${e}  (${stat.size} bytes)`);
  }
  console.log(`\nTo diff against the live tenant:`);
  console.log(`  ssh <tenant> 'cat <basePath>/docker-compose.yml' | diff -u - ${outDir}/docker-compose.yml`);
  console.log(``);

  await prisma.$disconnect();
}

main().catch((err) => {
  console.error('render-for-instance.ts failed:', err);
  process.exit(1);
});
