#!/usr/bin/env node
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

// One-shot: read all individual secrets from a Key Vault, assemble them
// into a YAML config tree, and write the result as a single
// "typeagent-config" secret in the same vault. Does not touch local files.
//
// Usage:
//   node migrateToYamlSecret.mjs --vault <name> [--secret typeagent-config] [--dry-run]

import { DefaultAzureCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";
import yaml from "js-yaml";
import chalk from "chalk";
import { getClient as getPIMClient } from "./lib/pimClient.mjs";

let vaultName;
let secretName = "typeagent-config";
let dryRun = false;

for (let i = 2; i < process.argv.length; i++) {
    const arg = process.argv[i];
    if (arg === "--vault") {
        vaultName = process.argv[++i];
    } else if (arg === "--secret") {
        secretName = process.argv[++i];
    } else if (arg === "--dry-run") {
        dryRun = true;
    } else {
        console.error(`Unknown argument: ${arg}`);
        process.exit(1);
    }
}

if (!vaultName) {
    console.error("Missing required --vault <name>");
    process.exit(1);
}

function toEnvKey(secretKey) {
    return secretKey.split("-").join("_");
}

function isForbidden(e) {
    return (
        e?.statusCode === 403 ||
        (typeof e?.message === "string" &&
            (e.message.includes("ForbiddenByRbac") ||
                e.message.includes("Forbidden") ||
                e.message.includes("statusCode 403")))
    );
}

async function elevate(roleName) {
    const pimClient = await getPIMClient();
    await pimClient.elevate({
        requestType: "SelfActivate",
        roleName,
        expirationType: "AfterDuration",
        expirationDuration: "PT5M",
        continueOnFailure: true,
    });
    console.log(chalk.green(`Elevation to '${roleName}' successful.`));
    console.warn(chalk.yellowBright("Waiting 5 seconds..."));
    await new Promise((r) => setTimeout(r, 5000));
}

async function listSecretsWithElevation() {
    try {
        return await collectNames();
    } catch (e) {
        if (!isForbidden(e)) throw e;
        console.warn(
            chalk.yellowBright(
                "List blocked by RBAC — elevating to Key Vault Administrator ...",
            ),
        );
        try {
            await elevate("Key Vault Administrator");
        } catch {
            console.warn(
                chalk.yellow(
                    "Admin elevation failed — trying Key Vault Secrets Officer ...",
                ),
            );
            await elevate("Key Vault Secrets Officer");
        }
        return collectNames();
    }
}

async function collectNames() {
    const result = [];
    for await (const props of client.listPropertiesOfSecrets()) {
        if (!props.enabled) continue;
        const name = props.name;
        if (name === secretName) {
            console.log(chalk.gray(`  skip existing config secret '${name}'`));
            continue;
        }
        result.push(name);
    }
    return result;
}

const credential = new DefaultAzureCredential();
const client = new SecretClient(
    `https://${vaultName}.vault.azure.net`,
    credential,
);

console.log(`Listing secrets in ${chalk.cyanBright(vaultName)} ...`);
const names = await listSecretsWithElevation();
console.log(`Found ${names.length} enabled secret(s).`);

const concurrency = 20;
const entries = [];
const failures = [];
for (let i = 0; i < names.length; i += concurrency) {
    const batch = names.slice(i, i + concurrency);
    const results = await Promise.all(
        batch.map(async (n) => {
            try {
                const s = await client.getSecret(n);
                return [n, s.value];
            } catch (e) {
                failures.push({ name: n, error: e.message });
                return null;
            }
        }),
    );
    for (const r of results) {
        if (r) entries.push(r);
    }
}

if (failures.length) {
    console.warn(chalk.yellow(`Failed to read ${failures.length} secret(s):`));
    for (const f of failures) {
        console.warn(chalk.yellow(`  - ${f.name}: ${f.error}`));
    }
}

console.log(`Read ${entries.length} secret(s). Converting to YAML ...`);

const flat = {};
for (const [secretKey, value] of entries) {
    flat[toEnvKey(secretKey)] = value;
}

const { envToYamlTree } = await import("@typeagent/config");
const tree = envToYamlTree(flat);
const header = `# TypeAgent configuration — auto-generated by migrateToYamlSecret on ${new Date().toISOString().slice(0, 10)}\n`;
const yamlContent =
    header +
    yaml.dump(tree, {
        lineWidth: -1,
        noRefs: true,
        sortKeys: false,
    });

console.log(
    `YAML payload is ${yamlContent.length} bytes (${entries.length} keys).`,
);

if (dryRun) {
    console.log(
        chalk.yellow(
            `\n[dry-run] Would write secret '${secretName}' to vault '${vaultName}'.`,
        ),
    );
    console.log("\n--- YAML preview (first 80 lines) ---");
    console.log(yamlContent.split("\n").slice(0, 80).join("\n"));
    console.log("--- end preview ---");
    process.exit(0);
}

console.log(
    `Writing '${chalk.cyanBright(secretName)}' to ${chalk.cyanBright(vaultName)} ...`,
);
await client.setSecret(secretName, yamlContent);
console.log(
    chalk.green(`Secret '${secretName}' updated in vault '${vaultName}'.`),
);
