import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts";
import { encrypt } from "@server/lib/crypto";
import { generateCA } from "@server/lib/sshCA";
import Database from "better-sqlite3";
import fs from "fs";
import path from "path";
import yaml from "js-yaml";

const version = "1.16.0";

function getServerSecret(): string {
    const envSecret = process.env.SERVER_SECRET;

    const configPath = fs.existsSync(configFilePath1)
        ? configFilePath1
        : fs.existsSync(configFilePath2)
          ? configFilePath2
          : null;

    // If no config file but an env secret is set, use the env secret directly
    if (!configPath) {
        if (envSecret && envSecret.length > 0) {
            return envSecret;
        }

        throw new Error(
            "Cannot generate org CA keys: no config file found and SERVER_SECRET env var is not set. " +
                "Expected config.yml or config.yaml in the config directory, or set SERVER_SECRET."
        );
    }

    const configContent = fs.readFileSync(configPath, "utf8");
    const config = yaml.load(configContent) as {
        server?: { secret?: string };
    };

    let secret = config?.server?.secret;
    if (!secret || secret.length === 0) {
        // Fall back to SERVER_SECRET env var if config does not contain server.secret
        if (envSecret && envSecret.length > 0) {
            secret = envSecret;
        }
    }

    if (!secret || secret.length === 0) {
        throw new Error(
            "Cannot generate org CA keys: no server.secret in config and SERVER_SECRET env var is not set. " +
                "Set server.secret in config.yml/config.yaml or set SERVER_SECRET."
        );
    }

    return secret;
}

export default async function migration() {
    console.log(`Running setup script ${version}...`);

    // Ensure server secret exists before running migration (required for org CA key generation)
    getServerSecret();

    const location = path.join(APP_PATH, "db", "db.sqlite");
    const db = new Database(location);

    try {
        db.pragma("foreign_keys = OFF");

        db.transaction(() => {
            // Create roundTripMessageTracker table for tracking message round-trips
            db.prepare(
                `
                CREATE TABLE 'roundTripMessageTracker' (
                    'messageId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
                    'clientId' text,
                    'messageType' text,
                    'sentAt' integer NOT NULL,
                    'receivedAt' integer,
                    'error' text,
                    'complete' integer DEFAULT 0 NOT NULL
                );
                `
            ).run();

            // Org SSH CA and billing columns
            db.prepare(`ALTER TABLE 'orgs' ADD 'sshCaPrivateKey' text;`).run();
            db.prepare(`ALTER TABLE 'orgs' ADD 'sshCaPublicKey' text;`).run();
            db.prepare(`ALTER TABLE 'orgs' ADD 'isBillingOrg' integer;`).run();
            db.prepare(`ALTER TABLE 'orgs' ADD 'billingOrgId' text;`).run();

            // Role SSH sudo and unix group columns
            db.prepare(
                `ALTER TABLE 'roles' ADD 'sshSudoMode' text DEFAULT 'none';`
            ).run();
            db.prepare(
                `ALTER TABLE 'roles' ADD 'sshSudoCommands' text DEFAULT '[]';`
            ).run();
            db.prepare(
                `ALTER TABLE 'roles' ADD 'sshCreateHomeDir' integer DEFAULT 1;`
            ).run();
            db.prepare(
                `ALTER TABLE 'roles' ADD 'sshUnixGroups' text DEFAULT '[]';`
            ).run();

            // Site resource auth daemon columns
            db.prepare(
                `ALTER TABLE 'siteResources' ADD 'authDaemonPort' integer DEFAULT 22123;`
            ).run();
            db.prepare(
                `ALTER TABLE 'siteResources' ADD 'authDaemonMode' text DEFAULT 'site';`
            ).run();

            // UserOrg PAM username for SSH
            db.prepare(`ALTER TABLE 'userOrgs' ADD 'pamUsername' text;`).run();

            // Set all admin role sudo to "full"; other roles keep default "none"
            db.prepare(
                `UPDATE 'roles' SET 'sshSudoMode' = 'full' WHERE isAdmin = 1;`
            ).run();
        })();

        db.pragma("foreign_keys = ON");

        const orgRows = db.prepare("SELECT orgId FROM orgs").all() as {
            orgId: string;
        }[];

        // Generate and store encrypted SSH CA keys for all orgs
        const secret = getServerSecret();

        const updateOrgCaKeys = db.prepare(
            "UPDATE orgs SET sshCaPrivateKey = ?, sshCaPublicKey = ? WHERE orgId = ?"
        );

        const failedOrgIds: string[] = [];

        for (const row of orgRows) {
            try {
                const ca = generateCA(`pangolin-ssh-ca-${row.orgId}`);
                const encryptedPrivateKey = encrypt(ca.privateKeyPem, secret);
                updateOrgCaKeys.run(
                    encryptedPrivateKey,
                    ca.publicKeyOpenSSH,
                    row.orgId
                );
            } catch (err) {
                failedOrgIds.push(row.orgId);
                console.error(
                    `Error: No CA was generated for organization "${row.orgId}".`,
                    err instanceof Error ? err.message : err
                );
            }
        }

        if (orgRows.length > 0) {
            const succeeded = orgRows.length - failedOrgIds.length;
            console.log(
                `Generated and stored SSH CA keys for ${succeeded} org(s).`
            );
        }

        if (failedOrgIds.length > 0) {
            console.error(
                `No CA was generated for ${failedOrgIds.length} organization(s): ${failedOrgIds.join(", ")}`
            );
        }

        console.log(`Migrated database`);
    } catch (e) {
        console.log("Failed to migrate db:", e);
        throw e;
    }

    console.log(`${version} migration complete`);
}
