import { parseArgs } from "node:util";
import { createInterface } from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";
import { networkInterfaces } from "node:os";
import { db, runMigrations } from "../db/client.js";
import { users } from "../db/schema.js";
import { hashPassword } from "../auth/passwords.js";
import { env } from "../env.js";
import { loadInstallConfig, updateInstallConfig, configPath } from "../config.js";
import { applyCaddy, writeCaddyfile } from "../proxy/caddy.js";
const MIN_PASSWORD_LENGTH = 8;
/**
 * Assistant de premier lancement : `private-server setup [options]`
 *
 * Pose les questions d'installation — domaine (avec instructions DNS et
 * génération du Caddyfile pour le HTTPS automatique), compte admin, nom de
 * la famille — puis affiche l'URL et les identifiants finaux.
 *
 * Le cœur ne connaît pas les modules : la création de la famille initiale
 * passe par `familyProvider` injecté au point d'assemblage (le CLI), comme
 * pour les routes.
 */
export async function runSetupWizard(argv, deps) {
    const parsed = parseArgs({
        args: argv,
        options: {
            domain: { type: "string" },
            email: { type: "string" },
            password: { type: "string" },
            family: { type: "string" },
            /** Non-interactif : toutes les valeurs via options. */
            yes: { type: "boolean" },
            /** `--no-caddy` : ne tente pas de recharger Caddy (Caddyfile écrit quand même). */
            caddy: { type: "boolean" },
        },
        allowPositionals: false,
        strict: true,
        allowNegative: true,
    });
    runMigrations();
    const config = loadInstallConfig();
    if (config.setupDoneAt) {
        throw new Error(`Serveur déjà configuré le ${config.setupDoneAt}. ` +
            `Pour repartir de zéro : supprimez ${configPath()} puis relancez ` +
            "`private-server setup` (les données existantes restent).");
    }
    const rl = createInterface({ input, output });
    try {
        /* ---------------- 1. Domaine ---------------- */
        let domain = parsed.values.domain ?? null;
        if (domain === null) {
            domain = await rl.question("Domaine public (ex. photos.madomain.fr)\n" +
                "  [Entrée] = pas de domaine, accès direct par IP (sans HTTPS automatique) : ");
        }
        domain = normalizeDomain(domain ?? "");
        if (domain) {
            console.log(`\nDomaine retenu : ${domain}\n`);
        }
        else {
            console.log("\nPas de domaine — accès direct par IP, HTTPS à gérer à la main.\n");
        }
        if (domain) {
            const publicIp = await detectPublicIp();
            if (publicIp) {
                console.log(`Enregistrement DNS à créer :\n    A   ${domain}  →  ${publicIp}\n`);
            }
            else {
                console.log(`Enregistrement DNS à créer :\n    A   ${domain}  →  <IP publique de cette machine>\n`);
            }
        }
        /* ---------------- 2. Compte admin ---------------- */
        let email = parsed.values.email;
        if (!email) {
            email = (await rl.question("Email du compte administrateur : ")).trim();
        }
        if (!isValidEmail(email))
            throw new Error(`Email invalide : "${email}"`);
        let password = parsed.values.password;
        if (!password) {
            // eslint-disable-next-line no-constant-condition
            while (true) {
                password = await rl.question(`Mot de passe administrateur (min ${MIN_PASSWORD_LENGTH} caractères) : `);
                if (password.length >= MIN_PASSWORD_LENGTH)
                    break;
                console.error(`Trop court — minimum ${MIN_PASSWORD_LENGTH} caractères.`);
            }
            const confirm = await rl.question("Confirmation du mot de passe : ");
            if (confirm !== password)
                throw new Error("Les mots de passe ne correspondent pas.");
        }
        if (password.length < MIN_PASSWORD_LENGTH) {
            throw new Error(`Mot de passe trop court — minimum ${MIN_PASSWORD_LENGTH} caractères.`);
        }
        let familyName = parsed.values.family;
        if (familyName === undefined) {
            familyName = (await rl.question("Nom de la famille (Entrée = \"Ma famille\") : ")).trim() || "Ma famille";
        }
        /* ---------------- 3. Création (compte + famille, une transaction) ---------------- */
        const ids = db.transaction((tx) => {
            const [user] = tx
                .insert(users)
                .values({
                email,
                passwordHash: hashPassword(password),
                displayName: "Admin",
            })
                .returning()
                .all();
            if (!user)
                throw new Error("Échec de la création du compte admin");
            const familyId = deps.familyProvider.createInitialFamily(tx, user.id, familyName);
            return { userId: user.id, familyId };
        });
        updateInstallConfig({
            domain,
            adminEmail: email,
            setupDoneAt: new Date().toISOString(),
        });
        /* ---------------- 4. Caddy ---------------- */
        let caddyNote = "";
        if (domain) {
            const path = await writeCaddyfile(domain, env.PORT, env.DATA_DIR);
            if (parsed.values.caddy === false) {
                // --no-caddy : on ne touche pas au Caddy en cours d'exécution.
                caddyNote = `  Caddyfile écrit : ${path} (rechargez Caddy vous-même)\n`;
            }
            else {
                const res = await applyCaddy(path);
                caddyNote = res.ok
                    ? `  Caddy rechargé : ${res.action}\n`
                    : `  Caddyfile écrit : ${path}\n  Activez-le à la main (${res.action}).\n`;
            }
        }
        /* ---------------- 5. Récapitulatif ---------------- */
        const url = domain
            ? `https://${domain}`
            : `http://${localIp() ?? "<IP_de_cette_machine>"}:${env.PORT}`;
        console.log(`
────────────────────────────────────────────────────────────────
  Configuration terminée ✅

  URL            ${url}
  Identifiant    ${email}
  Mot de passe   ${password.length > 0 ? "".padStart(password.length, "•") : ""}
  Famille        ${familyName}
${caddyNote}
  Notez ces identifiants : le serveur ne pourra pas vous les
  renvoyer (le mot de passe est stocké haché).

  Le serveur peut démarrer :  private-server start
  (ou \`node dist/index.js\` — voir l'unité systemd sur Linux)
────────────────────────────────────────────────────────────────`);
        void ids;
    }
    finally {
        rl.close();
    }
}
/* ------------------------------------------------------------------ */
/* Helpers                                                             */
/* ------------------------------------------------------------------ */
/** Normalise une saisie de domaine : minuscules, sans schéma ni chemin. */
export function normalizeDomain(raw) {
    let d = raw.trim().toLowerCase();
    d = d.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/:.*$/, "");
    if (d === "")
        return null;
    if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/.test(d)) {
        throw new Error(`Domaine invalide : "${raw}" (ex. photos.madomain.fr)`);
    }
    return d;
}
function isValidEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
/** IP publique de la machine (API externe) — null si injoignable. */
async function detectPublicIp() {
    try {
        const res = await fetch("https://api.ipify.org", { signal: AbortSignal.timeout(3_000) });
        if (!res.ok)
            return null;
        return (await res.text()).trim() || null;
    }
    catch {
        return null;
    }
}
/** Première IPv4 locale non-loopback, pour le mode « accès direct ». */
function localIp() {
    for (const addrs of Object.values(networkInterfaces())) {
        for (const a of addrs ?? []) {
            if (a.family === "IPv4" && !a.internal)
                return a.address;
        }
    }
    return null;
}
//# sourceMappingURL=wizard.js.map