#!/usr/bin/env node
/**
 * cli:configure-login — Configure the generated client app's login/auth.
 *
 * Writes (deep-merged, idempotent):
 *   - {apiDir}/appsettings.json        — providers (public), email (sender + provider block), admin email
 *   - {apiDir}/appsettings.Local.json  — secrets (ClientSecret, admin password, SMTP pwd / SendGrid key / ACS conn) [gitignored]
 *   - {webDir}/src/main.tsx            — SmartStackProvider config.auth  [only when frontendAuth:true]
 *   - {webDir}/.env.example            — # VITE_MSAL_* placeholders       [only when writeEnvExample + entra]
 *
 * Does NOT scaffold a login page — that UI ships in @atlashub/smartstack; this
 * CLI only writes the configuration the package reads.
 */
import { parseArgs } from 'node:util';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { validate } from './validate.js';
import { generate, injectProviderAuth, buildAuthLiteral, buildEnvAppend } from './generate.js';
import { deepMerge } from '../../../lib/json-merge.js';
import { generateEnvelope, failGenerate, printEnvelope } from '../../../lib/output.js';

const COMMAND = 'configure-login';

async function main(): Promise<void> {
  const { values } = parseArgs({
    options: {
      spec: { type: 'string' },
      dry_run: { type: 'boolean', default: false },
    },
    strict: true,
  });

  if (!values.spec) {
    printEnvelope(failGenerate(COMMAND, ['--spec is required']));
    process.exit(1);
  }

  let raw: unknown;
  try {
    raw = JSON.parse(values.spec);
  } catch {
    printEnvelope(failGenerate(COMMAND, ['Invalid JSON in --spec']));
    process.exit(1);
  }

  const v = await validate(raw);
  if (!v.valid || !v.spec || !v.layout) {
    printEnvelope(failGenerate(COMMAND, v.errors));
    process.exit(1);
  }

  const spec = v.spec;
  const layout = v.layout;
  const projectRoot = resolve(spec.projectPath);
  const files = generate(spec, layout);

  const providersConfigured: string[] = [];
  if (spec.connectionTypes.local) providersConfigured.push('local');
  if (spec.connectionTypes.microsoft) providersConfigured.push('microsoft');
  if (spec.connectionTypes.google) providersConfigured.push('google');
  if (spec.connectionTypes.entra) providersConfigured.push('entra');

  const warnings = [...v.warnings];
  const nextSteps: string[] = [];

  // The backend has no switch to disable self-registration — POST /api/auth/register
  // stays open (limited only by the license seat quota). Say so plainly so nobody
  // believes allowRegistration:false closes signup server-side.
  if (!spec.allowRegistration) {
    warnings.push(
      'allowRegistration:false has NO backend effect — POST /api/auth/register stays open (limited only by the license seat quota). ' +
        'To hide the "create account" link, scaffold a custom login page (scaffold-login-page); to block signup server-side, restrict it at the network layer.',
    );
  }
  // The seeded default admin makes the API fail-fast on first boot until a real
  // password is set; warn when no initial admin was configured.
  if (!spec.initialAdmin) {
    warnings.push(
      'No initialAdmin configured: the API fail-fasts on first boot until Security:InitialAdmin:Password is provided (the default seeded admin keeps a placeholder hash). Re-run with initialAdmin, or set the secret in appsettings.Local.json / user-secrets.',
    );
  }

  if (values.dry_run) {
    printEnvelope(
      generateEnvelope(COMMAND, {
        data: {
          dryRun: true,
          files: files.map((f) => f.path),
          providersConfigured,
          registrationEnabled: spec.allowRegistration,
          secretsMode: spec.secretsMode,
          frontendAuth: spec.frontendAuth,
          apiDir: layout.apiDir,
          webDir: layout.webDir,
        },
        warnings,
      }),
    );
    process.exit(0);
  }

  const filesCreated: string[] = [];
  const filesModified: string[] = [];
  let secretsTarget: string | null = null;

  // 1) Backend appsettings — deep-merge JSON (preserves every other key).
  for (const file of files) {
    const abs = resolve(join(projectRoot, file.path));
    if (file.path.endsWith('appsettings.Local.json')) secretsTarget = file.path;

    if (existsSync(abs)) {
      let existingJson: unknown = {};
      try {
        existingJson = JSON.parse(readFileSync(abs, 'utf-8'));
      } catch {
        existingJson = {}; // malformed — fall back to the patch
      }
      const merged = deepMerge(existingJson, JSON.parse(file.content));
      const out = JSON.stringify(merged, null, 2) + '\n';
      if (!spec.force && readFileSync(abs, 'utf-8') === out) continue; // no drift
      writeFileSync(abs, out, 'utf-8');
      filesModified.push(abs);
    } else {
      mkdirSync(dirname(abs), { recursive: true });
      writeFileSync(abs, file.content, 'utf-8');
      filesCreated.push(abs);
    }
  }

  // 2) Frontend SmartStackProvider config.auth — gated (default OFF).
  let providerInjected = false;
  let providerSkipped = false;
  if (spec.frontendAuth) {
    const mainAbs = resolve(join(projectRoot, layout.webDir, 'src', 'main.tsx'));
    if (!existsSync(mainAbs)) {
      providerSkipped = true;
      warnings.push(`frontendAuth: ${layout.webDir}/src/main.tsx not found — provider injection skipped.`);
      nextSteps.push(`Add into SmartStackProvider config={{ … }}: ${buildAuthLiteral(spec)}`);
    } else {
      const src = readFileSync(mainAbs, 'utf-8');
      const { content, status } = injectProviderAuth(src, spec);
      if (status === 'injected' || status === 'updated') {
        if (content !== src) {
          writeFileSync(mainAbs, content, 'utf-8');
          filesModified.push(mainAbs);
        }
        providerInjected = true;
      } else {
        providerSkipped = true;
        if (status === 'skipped-customised') {
          warnings.push('frontendAuth: main.tsx is marked @customised — provider injection skipped.');
        } else {
          warnings.push('frontendAuth: could not find `config={{` in main.tsx — provider injection skipped.');
          nextSteps.push(`Add into SmartStackProvider config={{ … }}: ${buildAuthLiteral(spec)}`);
        }
      }
    }
  } else {
    nextSteps.push(
      'Frontend auth injection is OFF (frontendAuth:false). Enable it once @atlashub/smartstack exposes SmartStackProvider config.auth, then re-run with frontendAuth:true.',
    );
  }

  // 3) .env.example MSAL placeholders — optional, Entra only.
  if (spec.writeEnvExample && spec.connectionTypes.entra) {
    const envAbs = resolve(join(projectRoot, layout.webDir, '.env.example'));
    const existed = existsSync(envAbs);
    const existing = existed ? readFileSync(envAbs, 'utf-8') : '';
    const { content, added } = buildEnvAppend(existing, spec);
    if (added.length > 0) {
      mkdirSync(dirname(envAbs), { recursive: true });
      writeFileSync(envAbs, content, 'utf-8');
      (existed ? filesModified : filesCreated).push(envAbs);
    }
  }

  // nextSteps / warnings
  if (secretsTarget) {
    nextSteps.push(`Secrets written to ${secretsTarget} (gitignored) — verify it is NOT committed.`);
  }
  if (spec.secretsMode === 'placeholders') {
    nextSteps.push('Placeholders mode: populate real secrets in appsettings.Local.json or dotnet user-secrets (nothing secret was written).');
  }
  nextSteps.push('Restart `ss dev` (or the API) to apply the backend configuration.');
  if (filesCreated.length === 0 && filesModified.length === 0) {
    nextSteps.unshift('Login configuration already up-to-date — no changes needed.');
  }

  printEnvelope(
    generateEnvelope(COMMAND, {
      data: {
        providersConfigured,
        registrationEnabled: spec.allowRegistration,
        secretsMode: spec.secretsMode,
        apiDir: layout.apiDir,
        webDir: layout.webDir,
        providerInjected,
        providerSkipped,
        fileCount: filesCreated.length + filesModified.length,
      },
      filesCreated,
      filesModified,
      warnings,
      nextSteps,
    }),
  );
}

void main();
