import { spawn } from "node:child_process";
import { mkdir, readFile, rename, writeFile } from "fs/promises";
import path from "path";
import { resetApiKey } from "./auth.js";
import { instantiateDownloadClients } from "./clients/TorrentClient.js";
import { createAppDirHierarchy, getDefaultRuntimeConfig, getFileConfig, getFileConfigPath, stripDefaults, transformFileConfig, } from "./configuration.js";
import { db } from "./db.js";
import { getDbConfig, setDbConfig } from "./dbConfig.js";
import { exitOnCrossSeedErrors, initializeLogger, Label, logger, } from "./logger.js";
import { initializePushNotifier } from "./pushNotifier.js";
import { getRuntimeConfig, setRuntimeConfig, } from "./runtimeConfig.js";
import { notExists } from "./utils.js";
import { getLogWatcher } from "./utils/logWatcher.js";
import { omitUndefined } from "./utils/object.js";
export async function exitGracefully() {
    await db.destroy();
    process.exit();
}
process.on("SIGINT", exitGracefully);
process.on("SIGTERM", exitGracefully);
async function ensureConfiguredDirectories() {
    const { outputDir, linkDirs = [] } = getRuntimeConfig();
    const directories = [];
    if (outputDir) {
        directories.push({ path: outputDir, label: "outputDir" });
    }
    for (const [index, linkDir] of linkDirs.entries()) {
        directories.push({ path: linkDir, label: `linkDir${index}` });
    }
    for (const { path, label } of directories) {
        if (!(await notExists(path))) {
            continue;
        }
        try {
            logger.info(`Creating ${label}: ${path}`);
            await mkdir(path, { recursive: true });
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error ?? "");
            logger.error({
                label: Label.SERVER,
                message: `Failed to create ${label} at ${path}: ${message}`,
            });
        }
    }
}
const MIGRATED_CONFIG_COMMENT = "// This config.js file was imported into the cross-seed v7 database. Use the Web UI to view or change settings.\n\n";
/**
 * Initializes only the database, runs the callback, then cleans up
 */
export function withMinimalRuntime(entrypoint, { migrate = true } = {}) {
    return async (...args) => {
        try {
            if (migrate)
                await db.migrate.latest();
            const output = await entrypoint(...args);
            if (output)
                console.log(output);
        }
        catch (e) {
            exitOnCrossSeedErrors(e);
        }
        finally {
            await exitGracefully();
        }
    };
}
async function applyExistingApiKey(config) {
    try {
        const existingApiKey = await db("settings").select("apikey").first();
        if (existingApiKey?.apikey && !config.apiKey) {
            config.apiKey = existingApiKey.apikey;
        }
    }
    catch {
        // best-effort only
    }
}
async function getFileConfigBackupPath(configPath) {
    const basePath = `${configPath}.bak`;
    if (await notExists(basePath)) {
        return basePath;
    }
    for (let index = 1;; index++) {
        const candidate = `${basePath}.${index}`;
        if (await notExists(candidate)) {
            return candidate;
        }
    }
}
async function backupFileConfig() {
    const configPath = getFileConfigPath();
    const backupPath = await getFileConfigBackupPath(configPath);
    await rename(configPath, backupPath);
    const backupContents = await readFile(backupPath, "utf8");
    await writeFile(backupPath, `${MIGRATED_CONFIG_COMMENT}${backupContents}`);
    return backupPath;
}
export async function determineRuntimeConfig(rawOptions) {
    const cliOptions = omitUndefined(rawOptions);
    // first, try to load from database (existing user happy path)
    let dbOverrides;
    try {
        dbOverrides = await getDbConfig();
    }
    catch (dbError) {
        logger.debug("Unable to load configuration from database", dbError);
    }
    if (dbOverrides !== undefined) {
        return {
            ...getDefaultRuntimeConfig(),
            ...dbOverrides,
            ...cliOptions,
        };
    }
    // then, try to migrate from file config (v6 to v7 upgrade path)
    try {
        const fileConfig = await getFileConfig();
        if (fileConfig) {
            const transformedFileConfig = transformFileConfig(fileConfig);
            const runtimeFromFile = {
                ...getDefaultRuntimeConfig(),
                ...transformedFileConfig,
            };
            await applyExistingApiKey(runtimeFromFile);
            await setDbConfig(runtimeFromFile);
            const resolvedOverrides = stripDefaults(runtimeFromFile);
            const backupPath = await backupFileConfig();
            logger.info(`Migrated file config to database; v7 now uses database/Web UI settings. Preserved old config file as ${path.basename(backupPath)}.`);
            return {
                ...getDefaultRuntimeConfig(),
                ...resolvedOverrides,
                ...cliOptions,
            };
        }
    }
    catch (migrationError) {
        logger.error(new Error("Failed to import configuration file, falling back to defaults", { cause: migrationError }));
    }
    // finally, fall back to defaults (new user happy path or migration failure)
    const defaultRuntime = getDefaultRuntimeConfig();
    await setDbConfig(defaultRuntime);
    await resetApiKey();
    logger.info("Created initial database config from defaults");
    const resolvedOverrides = stripDefaults(defaultRuntime);
    return {
        ...getDefaultRuntimeConfig(),
        ...resolvedOverrides,
        ...cliOptions,
    };
}
/**
 * Initializes the full runtime, runs the callback, then cleans up
 * @param entrypoint
 */
export function withFullRuntime(entrypoint) {
    return withMinimalRuntime(async (options) => {
        createAppDirHierarchy();
        initializeLogger(options);
        const runtimeConfig = await determineRuntimeConfig(options);
        setRuntimeConfig(runtimeConfig);
        initializePushNotifier();
        getLogWatcher();
        await ensureConfiguredDirectories();
        instantiateDownloadClients();
        await entrypoint(runtimeConfig);
    });
}
export async function restartCrossSeed() {
    logger.info("Restarting cross-seed");
    process.on("exit", () => {
        spawn(process.argv[0], process.argv.slice(1), {
            cwd: process.cwd(),
            stdio: "inherit",
            detached: false,
        });
    });
    await exitGracefully();
}
//# sourceMappingURL=startup.js.map