/*
This file is part of »zeitbild«.

Copyright 2025 'kcf' <fenris@folksprak.org>

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

»zeitbild« 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with »zeitbild«. If not, see <http://www.gnu.org/licenses/>.
 */


namespace _zeitbild.auth
{
	
	/**
	 */
	let _subject : (
		null
		|
		lib_plankton.auth.type_auth<any, any, any>
	) = null;
	
	
	
	/**
	 */
	let _subject_oidc : (null | lib_plankton.auth.oidc.type_subject) = null;

	
	
	/**
	 */
	let _oidc_redict_uri_template_map : (
		null
		|
		lib_plankton.map.type_map<string, string>
	) = null;
	
	
	/**
	 */
	export function oidc_get_redirect_uri_template(
		key : string
	) : string
	{
		if (_oidc_redict_uri_template_map === null)
		{
			throw (new Error("apparently not initialized yet"));
		}
		else
		{
			return _oidc_redict_uri_template_map.get(key);
		}
	}
	
	
	/**
	 */
	export function init(
	) : Promise<void>
	{
		switch (_zeitbild.conf.get().authentication.kind) {
			case "internal":
			{
				_subject = lib_plankton.auth.internal.implementation_auth(
					{
						"password_image_chest": {
							"setup": (input) => Promise.resolve<void>(undefined),
							"clear": () => Promise.reject<void>("not implemented"),
							"write": (key, item) => _zeitbild.repository.auth_internal.write(key, item),
							"delete": (key) => _zeitbild.repository.auth_internal.delete_(key),
							"read": (key) => _zeitbild.repository.auth_internal.read(key),
							"search": (term) => Promise.reject<any>("not implemented"),
						},
						"check_password": (image, input) => _zeitbild.service.auth_internal.check_raw(image, input),
					}
				);
				break;
			}
			case "oidc":
			{
				_subject_oidc = lib_plankton.auth.oidc.make(
					{
						"url_authorization": _zeitbild.conf.get().authentication.data.url_authorization,
						"url_token": _zeitbild.conf.get().authentication.data.url_token,
						"url_userinfo": _zeitbild.conf.get().authentication.data.url_userinfo,
						"client_id": _zeitbild.conf.get().authentication.data.client_id,
						"client_secret": _zeitbild.conf.get().authentication.data.client_secret,
						"url_redirect": (_zeitbild.conf.get().authentication.data.backend_url_base + "/session/oidc"),
						"scopes": [
							"openid",
							"profile",
							"email",
							"groups",
						],
						"label": _zeitbild.conf.get().authentication.data.label,
					}
				);
				_oidc_redict_uri_template_map = lib_plankton.map.simplemap.implementation_map(
					lib_plankton.map.simplemap.make(
					)
				);
				// TODO
				return Promise.resolve(undefined);
				break;
			}
			default:
			{
				// do nothing
				break;
			}
		}
		return Promise.resolve<void>(undefined);
	}
	
	
	/**
	 */
	export function prepare(
		input : any
	) : Promise<{kind : string; data : any;}>
	{
		switch (_zeitbild.conf.get().authentication.kind)
		{
			case "oidc":
			{
				if ((_subject_oidc === null) || (_oidc_redict_uri_template_map === null))
				{
					throw (new Error("not initialized yet"));
				}
				else
				{
					const stuff : {state : string; authorization_url : string;} = lib_plankton.auth.oidc.prepare_login(_subject_oidc);
					_oidc_redict_uri_template_map.set(
						stuff.state,
						input["oidc_redirect_uri_template"]
					);
					return Promise.resolve(
						{
							"kind": "oidc",
							"data": {
								"url": stuff.authorization_url,
								"label": _zeitbild.conf.get().authentication.data.label,
							}
						}
					);
				}
				break;
			}
			default:
			{
				if (_subject === null)
				{
					return Promise.reject(new Error("not initialized yet"));
				}
				else
				{
					return (
						_subject.login_prepare()
						.then(
							(data : any) => ({
								"kind": _zeitbild.conf.get().authentication.kind,
								"data": data,
							})
						)
					);
				}
				break;
			}
		}
	}
	
	
	/**
	 */
	export async function oidc_handle_authorization_callback(
		cookie : (null | string),
		data : Record<string, string>
	) : Promise<
		{
			token : string;
			userinfo : lib_plankton.auth.oidc.type_userinfo;
			redirect_uri_template : string;
		}
	>
	{
		if ((_subject_oidc === null) || (_oidc_redict_uri_template_map === null))
		{
			throw (new Error("not initialized yet"));
		}
		else
		{
			const state : string = data["state"];
			const result : {
				token : string;
				userinfo : lib_plankton.auth.oidc.type_userinfo;
			} = await lib_plankton.auth.oidc.handle_authorization_callback(
				_subject_oidc,
				cookie,
				data
			);
			return Promise.resolve<
				{
					token : string;
					userinfo : lib_plankton.auth.oidc.type_userinfo;
					redirect_uri_template : string;
				}
			>(
				{
					"token": result.token,
					"userinfo": result.userinfo,
					"redirect_uri_template": _oidc_redict_uri_template_map.get(state),
				}
			);
		}
	}
	
	
	/**
	 * @todo switch for enabling/disabling auto provisioning
	 */
	export async function oidc_adapt_user(
		userinfo : lib_plankton.auth.oidc.type_userinfo
	)
	: Promise<
		{
			id : _zeitbild.type_user_id;
			object : _zeitbild.type_user_object;
		}
	>
	{
		if (userinfo.name === null)
		{
			return Promise.reject(new Error("IDP did not return user name"));
		}
		else
		{
			// groups
			const group_ids : Array<_zeitbild.type_group_id> = await (async () => {
				const derive_name : ((group_name_raw : string) => string) = (
					(group_name_raw) => lib_plankton.string.coin(
						"auto-{{name_raw}}",
						{
							"name_raw": group_name_raw,
						}
					)
				);
				const derive_label : ((group_name_raw : string) => string) = (
					(group_name_raw) => lib_plankton.string.coin(
						"{{name_raw}}",
						{
							"name_raw": group_name_raw,
						}
					)
				);
				return Promise.all<_zeitbild.type_group_id>(
					(userinfo.groups ?? [])
					.map(
						async (group_name_raw) => {
							const group_name : string = derive_name(group_name_raw);
							const group_id_raw : (null | _zeitbild.type_group_id) = await (
								_zeitbild.repository.group.identify(group_name)
								.catch(() => Promise.resolve(null))
							);
							if (group_id_raw === null)
							{
								// create
								const group_id : _zeitbild.type_group_id = await _zeitbild.service.group.add(
									{
										"name": group_name,
										"label": derive_label(group_name_raw),
									}
								);
								lib_plankton.log.info(
									"zeitbild.oidc_adapt_user.auto_provisioned_group",
									{
										"id": group_id,
										"name": group_name,
									}
								);
								return group_id;
							}
							else
							{
								// update
								const group_id : _zeitbild.type_group_id = group_id_raw;
								await _zeitbild.service.group.change(
									group_id,
									{
										"name": group_name,
										"label": derive_label(group_name_raw),
									}
								);
								return group_id;
							}
						}
					)
				);
			}) ();
			
			// user
			const user : {
				id : _zeitbild.type_user_id;
				object : _zeitbild.type_user_object;
			} = await (async () => {
				const user_id_raw : (null | _zeitbild.type_user_id) = await (
					_zeitbild.service.user.identify(userinfo.name as string)
					.catch(() => Promise.resolve(null))
				);
				if (user_id_raw === null)
				{
					// provision
					const user_object : _zeitbild.type_user_object = {
						"name": (userinfo.name as string),
						"groups": group_ids,
						"email_address": userinfo.email,
						"dav_token": null,
					};
					const user_id : _zeitbild.type_user_id = await _zeitbild.service.user.add(
						user_object
					);
					lib_plankton.log.info(
						"user_provisioned",
						{
							"id": user_id,
							"name": user_object.name,
						}
					);
					return {"id": user_id, "object": user_object};
				}
				else
				{
					// update
					const user_id : _zeitbild.type_user_id = user_id_raw;
					const user_object : _zeitbild.type_user_object = await _zeitbild.service.user.get(user_id);
					user_object.name = (userinfo.name as string);
					user_object.groups = group_ids;
					user_object.email_address = userinfo.email;
					await _zeitbild.service.user.change(
						user_id,
						user_object
					);
					lib_plankton.log.info(
						"user_updated",
						{
							"id": user_id,
							"name": user_object.name,
						}
					);
					return {"id": user_id, "object": user_object};
				}
			}) ();
			
			return user;
		}
	}
	
}
