import path from 'path';
import { Container } from 'typedi';
import type { TlsOptions } from 'tls';
import type { DataSourceOptions, LoggerOptions } from '@n8n/typeorm';
import type { SqliteConnectionOptions } from '@n8n/typeorm/driver/sqlite/SqliteConnectionOptions';
import type { SqlitePooledConnectionOptions } from '@n8n/typeorm/driver/sqlite-pooled/SqlitePooledConnectionOptions';
import type { PostgresConnectionOptions } from '@n8n/typeorm/driver/postgres/PostgresConnectionOptions';
import type { MysqlConnectionOptions } from '@n8n/typeorm/driver/mysql/MysqlConnectionOptions';
import { InstanceSettings } from 'n8n-core';
import { ApplicationError } from 'n8n-workflow';

import config from '@/config';
import { entities } from './entities';
import { mysqlMigrations } from './migrations/mysqldb';
import { postgresMigrations } from './migrations/postgresdb';
import { sqliteMigrations } from './migrations/sqlite';

const getCommonOptions = () => {
	const entityPrefix = config.getEnv('database.tablePrefix');
	const maxQueryExecutionTime = config.getEnv('database.logging.maxQueryExecutionTime');

	let loggingOption: LoggerOptions = config.getEnv('database.logging.enabled');
	if (loggingOption) {
		const optionsString = config.getEnv('database.logging.options').replace(/\s+/g, '');

		if (optionsString === 'all') {
			loggingOption = optionsString;
		} else {
			loggingOption = optionsString.split(',') as LoggerOptions;
		}
	}
	return {
		entityPrefix,
		entities: Object.values(entities),
		migrationsTableName: `${entityPrefix}migrations`,
		migrationsRun: false,
		synchronize: false,
		maxQueryExecutionTime,
		logging: loggingOption,
	};
};

export const getOptionOverrides = (dbType: 'postgresdb' | 'mysqldb') => ({
	database: config.getEnv(`database.${dbType}.database`),
	host: config.getEnv(`database.${dbType}.host`),
	port: config.getEnv(`database.${dbType}.port`),
	username: config.getEnv(`database.${dbType}.user`),
	password: config.getEnv(`database.${dbType}.password`),
});

const getSqliteConnectionOptions = (): SqliteConnectionOptions | SqlitePooledConnectionOptions => {
	const poolSize = config.getEnv('database.sqlite.poolSize');
	const commonOptions = {
		...getCommonOptions(),
		database: path.resolve(
			Container.get(InstanceSettings).n8nFolder,
			config.getEnv('database.sqlite.database'),
		),
		migrations: sqliteMigrations,
	};
	if (poolSize > 0) {
		return { type: 'sqlite-pooled', poolSize, enableWAL: true, ...commonOptions };
	} else {
		return {
			type: 'sqlite',
			enableWAL: config.getEnv('database.sqlite.enableWAL'),
			...commonOptions,
		};
	}
};

const getPostgresConnectionOptions = (): PostgresConnectionOptions => {
	const sslCa = config.getEnv('database.postgresdb.ssl.ca');
	const sslCert = config.getEnv('database.postgresdb.ssl.cert');
	const sslKey = config.getEnv('database.postgresdb.ssl.key');
	const sslRejectUnauthorized = config.getEnv('database.postgresdb.ssl.rejectUnauthorized');

	let ssl: TlsOptions | boolean = config.getEnv('database.postgresdb.ssl.enabled');
	if (sslCa !== '' || sslCert !== '' || sslKey !== '' || !sslRejectUnauthorized) {
		ssl = {
			ca: sslCa || undefined,
			cert: sslCert || undefined,
			key: sslKey || undefined,
			rejectUnauthorized: sslRejectUnauthorized,
		};
	}

	return {
		type: 'postgres',
		...getCommonOptions(),
		...getOptionOverrides('postgresdb'),
		schema: config.getEnv('database.postgresdb.schema'),
		poolSize: config.getEnv('database.postgresdb.poolSize'),
		migrations: postgresMigrations,
		ssl,
	};
};

const getMysqlConnectionOptions = (dbType: 'mariadb' | 'mysqldb'): MysqlConnectionOptions => ({
	type: dbType === 'mysqldb' ? 'mysql' : 'mariadb',
	...getCommonOptions(),
	...getOptionOverrides('mysqldb'),
	migrations: mysqlMigrations,
	timezone: 'Z', // set UTC as default
});

export function getConnectionOptions(): DataSourceOptions {
	const dbType = config.getEnv('database.type');
	switch (dbType) {
		case 'sqlite':
			return getSqliteConnectionOptions();
		case 'postgresdb':
			return getPostgresConnectionOptions();
		case 'mariadb':
		case 'mysqldb':
			return getMysqlConnectionOptions(dbType);
		default:
			throw new ApplicationError('Database type currently not supported', { extra: { dbType } });
	}
}
