/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
Copyright (C) 2024 Christian Fraß

This program is free software: you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details.

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


/**
 */
async function main(
	args_raw : Array<string>
) : Promise<void>
{
	// init
	lib_plankton.log.conf_push(
		[
			lib_plankton.log.channel_make(
				{
					"kind": "stdout",
					"data": {
						"threshold": "notice",
						// "format": "human_readable",
					}
				}
			),
		]
	);
	const language_codes : Array<string> = [
		"deu",
		"eng",
	];
	await (
		Promise.all(
			language_codes
			.map(
				language_code => (
					lib_plankton.file.read(
						lib_plankton.string.coin(
							"data/localization/{{language_code}}.loc.json",
							{
								"language_code": language_code,
							}
						)
					)
					.then<any>(
						content => (new Promise<any>(
							(resolve, reject) => {
								try {
									resolve(JSON.parse(content));
								}
								catch (error) {
									reject(error);
								}
							}
						))
					)
				)
			)
		)
		.then(
			packages => lib_plankton.translate.initialize(
				{
					"verbosity": 1,
					"packages": packages,
					"order": language_codes,
					"autopromote": false,
				}
			)
		)
	);
	
	// args
	const arg_handler : lib_plankton.args.class_handler = new lib_plankton.args.class_handler({
		"action": lib_plankton.args.class_argument.positional({
			"index": 0,
			"type": lib_plankton.args.enum_type.string,
			"mode": lib_plankton.args.enum_mode.replace,
			"default": "serve",
			"name": "action",
			"info": lib_plankton.string.coin(
				"{{description}}:\n{{options}}\n\t\t",
				{
					"description": lib_plankton.translate.get("help.args.action.options.serve"),
					"options": (
						[
							{
								"name": "serve",
								"description": lib_plankton.translate.get("help.args.action.options.serve"),
							},
							{
								"name": "api-doc",
								"description": lib_plankton.translate.get("help.args.action.options.api_doc")
							},
							{
								"name": "email-test",
								"description": lib_plankton.translate.get("help.args.action.options.email_test")
							},
							{
								"name": "expose-conf",
								"description": lib_plankton.translate.get("help.args.action.options.expose_conf")
							},
							{
								"name": "password-image",
								"description": lib_plankton.translate.get("help.args.action.options.password_image")
							},
							{
								"name": "export-authelia",
								"description": lib_plankton.translate.get("help.args.action.options.export_authelia")
							},
							{
								"name": "help",
								"description": lib_plankton.translate.get("help.args.action.options.help")
							},
						]
						.map(
							entry => lib_plankton.string.coin(
								"\t\t- {{name}}\n\t\t\t{{description}}\n",
								{
									"name": entry.name,
									"description": entry.description,
								}
							)
						)
						.join("")
					),
				}
			),
		}),
		"arg1": lib_plankton.args.class_argument.positional({
			"index": 1,
			"type": lib_plankton.args.enum_type.string,
			"mode": lib_plankton.args.enum_mode.replace,
			"default": null,
			// "info": null,
			"name": "arg1",
			"hidden": true,
		}),
		"arg2": lib_plankton.args.class_argument.positional({
			"index": 2,
			"type": lib_plankton.args.enum_type.string,
			"mode": lib_plankton.args.enum_mode.replace,
			"default": null,
			// "info": null,
			"name": "arg2",
			"hidden": true,
		}),
		"conf_path": lib_plankton.args.class_argument.volatile({
			"indicators_long": ["conf_path"],
			"indicators_short": ["c"],
			"type": lib_plankton.args.enum_type.string,
			"mode": lib_plankton.args.enum_mode.replace,
			"default": "conf.json",
			"info": lib_plankton.translate.get("help.args.conf_path.description"),
			"name": "conf-path",
		}),
		"help": lib_plankton.args.class_argument.volatile({
			"indicators_long": ["help"],
			"indicators_short": ["h"],
			"type": lib_plankton.args.enum_type.boolean,
			"mode": lib_plankton.args.enum_mode.replace,
			"default": false,
			"info": lib_plankton.translate.get("help.args.help.description"),
			"name": "help",
		}),
	});
	const args : Record<string, any> = arg_handler.read(lib_plankton.args.enum_environment.cli, args_raw.join(" "));	
	
	// init
	await _espe.conf.load(args["conf_path"]);
	{
		const language : (null | string) = _espe.conf.get().general.language;
		if (language === null) {
			// do nothing
		}
		else {
			lib_plankton.translate.promote(language);
		}
	}
	lib_plankton.log.conf_push(
		_espe.conf.get().log.map(
			log_output => lib_plankton.log.channel_make(
				{
					"kind": log_output.kind,
					"data": log_output.data
				}
			)
		)
	);
	
	// exec
	if (args["help"] || (args["action"] === "help")) {
		process.stdout.write(
			arg_handler.generate_help(
				{
					"programname": "espe",
					"description": "Espe | Backend",
					"executable": "espe",
				}
			)
			+
			"\n"
		);
	}
	else {
		switch (args["action"]) {
			default: {
				process.stderr.write("invalid action: " + args["action"] + "\n");
				break;
			}
			case "password-image": {
				const input : (null | string) = args["arg1"];
				if (input === null) {
					throw (new Error("SYNTAX: password-image <password>"));
				}
				else {
					const result : string = await _espe.helpers.bcrypt_compute(input);
					process.stdout.write(result + "\n")
				}
				break;
			}
			case "expose-conf": {
				process.stdout.write(
					JSON.stringify(
						_espe.conf.get(),
						undefined,
						"\t"
					)
					+
					"\n"
				);
				break;
			}
			case "api-doc": {
				lib_plankton.log.conf_push([]);
				const rest_subject : lib_plankton.rest.type_rest = _espe.api.make();
				lib_plankton.log.conf_pop();
				process.stdout.write(
					JSON.stringify(
						lib_plankton.rest.to_oas(rest_subject),
						undefined,
						"\t"
					)
				);
				break;
			}
			case "email-test": {
				await _espe.helpers.email_send(
					(
						(args["arg1"] !== null)
						? [args["arg1"]]
						: (
							(
								_espe.conf.get().admins
								.map(admin => admin.email_address)
								.filter(x => (x !== null))
							) as Array<string>
						)
					),
					lib_plankton.string.coin(
						"{{head}} | Test",
						{
							"head": _espe.conf.get().settings.organisation.name,
						}
					),
					"This is a test e-mail"
				);				
				break;
			}
			case "serve": {
				// prepare database
				await _espe.database.check();
		
				await lib_plankton.session.setup(
					{
						"data_chest": (
							_espe.conf.get().session_management.in_memory
							? lib_plankton.storage.memory.implementation_chest<lib_plankton.session.type_session>({})
							: lib_plankton.call.convey(
								lib_plankton.storage.sql_table_common.chest(
									{
										"database_implementation": _espe.helpers.database_implementation(),
										"table_name": "sessions",
										"key_names": ["key"],
									}
								),
								[
									(core) => ({
										"setup": (input) => core.setup(undefined),
										"clear": () => core.clear(),
										"write": (key, value) => core.write([key], {"data": JSON.stringify(value)}),
										"delete": (key) => core.delete([key]),
										"read": (key) => core.read([key]).then(row => JSON.parse(row["data"])),
										// "search": (term) => core.search(term).then(() => []),
										"search": (term) => Promise.reject(new Error("not implemented")),
									}),
								]
							)
						),
						"default_lifetime": _espe.conf.get().session_management.lifetime,
					}
				);
				
				// outputs
				_espe.service.member.listen_change(
					() => {
						lib_plankton.log.info(
							"member_change",
							{
							}
						);
						_espe.conf.get().outputs.forEach(
							output_description => {
								const procedure : (() => Promise<void>) = (
									(() => {
										switch (output_description.kind) {
											default: {
												lib_plankton.log.warning(
													"output_kind_unhandled",
													{
														"description": output_description,
													}
												);
												return (() => Promise.resolve<void>(undefined));
												break;
											}
											case "authelia_file": {
												return (() => _espe.service.member.output_authelia_file(output_description.data));
												break;
											}
											case "http": {
												return (() => _espe.service.member.output_http(output_description.data));
												break;
											}
											case "arc": {
												return (() => _espe.service.member.output_arc(output_description.data));
												break;
											}
										}
									}) ()
								);
								(
									procedure()
									.then(
										() => {
										}
									)
									.catch(
										(error) => {
											lib_plankton.log.warning(
												"output_procedure_failed",
												{
													"error": String(error),
												}
											);
										}
									)
								);
							}
						);
					}
				);
				
				const rest_subject : lib_plankton.rest.type_rest = _espe.api.make();
				const server : lib_plankton.server.type_subject = lib_plankton.server.make(
					async (input, metadata) => {
						const http_request : lib_plankton.http.type_request = lib_plankton.http.decode_request(input.toString());
						const http_response : lib_plankton.http.type_response = await lib_plankton.rest.call(
							rest_subject,
							http_request,
							{
								"checklevel_restriction": lib_plankton.api.enum_checklevel.hard,
								// "checklevel_input": lib_plankton.api.enum_checklevel.soft,
								// "checklevel_output": lib_plankton.api.enum_checklevel.soft,
							}
						);
						const output : string = lib_plankton.http.encode_response(http_response);
						return output;
					},
					{
						"host": _espe.conf.get().server.host,
						"port": _espe.conf.get().server.port,
						// DANGER! DANGER!
						"threshold": 0.125,
					}
				);
				
				lib_plankton.server.start(server);
				break;
			}
			case "export-authelia": {
				process.stdout.write(await _espe.service.member.export_authelia_user_file() + "\n");
				break;
			}
		}	
	}
}


(
	main(process.argv.slice(2))
	.then(
		() => {
		}
	)
	.catch(
		(error) => {
			process.stderr.write(String(error) + "\n");
		}
	)
);
