/*
	Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
	Copyright (C) 2023 Spacebar and Spacebar Contributors

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Affero General Public License as published
	by the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Affero General Public License for more details.

	You should have received a copy of the GNU Affero General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

import { config } from "dotenv";
import path from "path";
import { green, red, yellow } from "picocolors";
import { DataSource } from "typeorm";
// noinspection ES6PreferShortImport
import { ConfigEntity } from "../entities/Config";
import fs from "fs";

// UUID extension option is only supported with postgres
// We want to generate all id's with Snowflakes that's why we have our own BaseEntity class

export let dbConnection: DataSource | undefined;

let isHeadlessProcess = false;
// For typeorm cli
if (!process.env) {
    isHeadlessProcess = true;
    config({ quiet: true });
}
if (process.argv[1]?.endsWith("scripts/openapi.js")) isHeadlessProcess = true;

const dbConnectionString = process.env.DATABASE || path.join(process.cwd(), "database.db");

export const DatabaseType = dbConnectionString.includes("://") ? dbConnectionString.split(":")[0]?.replace("+srv", "") : "sqlite";
const isSqlite = DatabaseType.includes("sqlite");
const applyMigrations = process.env.APPLY_DB_MIGRATIONS !== "false";

// For openapi.js...
if (!isHeadlessProcess) {
    let hasWarnedSqlite = false;
    if (isSqlite && !hasWarnedSqlite) {
        hasWarnedSqlite = true;
        console.log(
            `[Database] ${red(`You do not have a database configured. This implies that it will try to use SQLite, which has been broken for a while and is unlikely to see a real fix any time soon.`)}`,
        );
        console.log(`[Database] ${red(`Please set up a PostgreSQL database instead: https://docs.spacebar.chat/setup/server/database/.`)}`);
        console.log(
            `[Database] ${red(`Alternatively, if you're able to install Nix (except MacOSX), and are trying to run a quick and dirty localhost instance: nix run .\\#testVm.config.system.build.vm, then open a client and connect to http://localhost:8080`)}`,
        );
        console.log(`[Database] ${red(`If you would like to try *anyways*, see the error below:`)}`);
        try {
            // TODO: fully remove sqlite3
            require("sqlite3");
        } catch (e) {
            console.log(`[Database] ${red(`Failed to load sqlite3 package. Please install it with 'npm install --no-save sqlite3', or switch to a real database like Postgres.`)}`);
            process.exit(1);
        }
    }
}

export const DataSourceOptions = isHeadlessProcess
    ? (undefined as unknown as DataSource)
    : new DataSource({
          // eslint-disable-next-line @typescript-eslint/ban-ts-comment
          //@ts-ignore type 'string' is not 'sqlite' | 'postgres' | etc etc
          type: DatabaseType,
          charset: "utf8mb4",
          url: isSqlite ? undefined : dbConnectionString,
          database: isSqlite ? dbConnectionString : undefined,
          entities: [path.join(__dirname, "..", "entities", "*.js")],
          synchronize: !!process.env.DB_SYNC,
          logging: !!process.env.DB_LOGGING,
          bigNumberStrings: false,
          supportBigNumbers: true,
          name: "default",
          migrations: applyMigrations ? [path.join(__dirname, "..", "migration", DatabaseType, "*.js")] : [],
      });

// Gets the existing database connection
export function getDatabase(): DataSource | null {
    // if (!dbConnection) throw new Error("Tried to get database before it was initialised");
    if (!dbConnection) return null;
    return dbConnection;
}

// Called once on server start
export async function initDatabase(): Promise<DataSource> {
    if (dbConnection) return dbConnection;

    if (!process.env.DB_SYNC) {
        const supported = ["postgres", "sqlite"];
        if (!supported.includes(DatabaseType)) {
            console.log(
                "[Database]" +
                    red(
                        ` We don't have migrations for DB type '${DatabaseType}'` +
                            ` To ignore, set DB_SYNC=true in your env. https://docs.spacebar.chat/setup/server/configuration/env/`,
                    ),
            );
            process.exit(1);
        }
    }

    console.log(`[Database] ${yellow(`Connecting to ${DatabaseType} db`)}`);

    dbConnection = await DataSourceOptions.initialize();

    if (DatabaseType === "sqlite") {
        console.log(`[Database] ${yellow("Warning: SQLite is not supported. Forcing sync, this may lead to data loss!")}`);
        await dbConnection.synchronize();
        console.log(`[Database] ${green("Connected")}`);
        return dbConnection;
    }

    // Crude way of detecting if the migrations table exists.
    const dbExists = async () => {
        try {
            await ConfigEntity.count();
            return true;
        } catch (e) {
            return false;
        }
    };
    if (applyMigrations) {
        if (!(await dbExists())) {
            console.log("[Database] This appears to be a fresh database. Running initial DDL.");
            const qr = dbConnection.createQueryRunner();
            const initialPath = path.join(__dirname, "..", "migration", DatabaseType + "-initial.js");
            if (fs.existsSync(initialPath)) {
                console.log("[Database] Found initial migration file, running it.");
                await new (require(`../migration/${DatabaseType}-initial`).initial0)().up(qr);
            } else console.log("[Database] No initial migration file found at '", initialPath, "', skipping.");
            await qr.release();
        }

        console.log("[Database] Applying missing migrations, if any.", process.env.APPLY_DB_MIGRATIONS);
        await dbConnection.runMigrations();
    } else {
        console.log("[Database] Skipping migrations as per config.");
        while (!(await dbExists())) {
            console.log("[Database] Database does not exist, and we are not running migrations... Waiting 1 seconds...");
            await new Promise((r) => setTimeout(r, 5000));
        }
    }

    console.log(`[Database] ${green("Connected")}`);

    return dbConnection;
}

export async function closeDatabase() {
    await dbConnection?.destroy();
}
