<script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
import { ROLE, type Role } from '@n8n/api-types';
import { useI18n } from '@n8n/i18n';
import { useToast } from '@/app/composables/useToast';
import { useDocumentTitle } from '@/app/composables/useDocumentTitle';
import type { IFormInputs, ThemeOption } from '@/Interface';
import type { IUser } from '@n8n/rest-api-client/api/users';
import {
	CHANGE_PASSWORD_MODAL_KEY,
	CONFIRM_PASSWORD_MODAL_KEY,
	MFA_DOCS_URL,
	MFA_SETUP_MODAL_KEY,
	PROMPT_MFA_CODE_MODAL_KEY,
} from '@/app/constants';
import { useUIStore } from '@/app/stores/ui.store';
import { useUsersStore } from '@/features/settings/users/users.store';
import { useSettingsStore } from '@/app/stores/settings.store';
import { useCloudPlanStore } from '@/app/stores/cloudPlan.store';
import { createFormEventBus } from '@n8n/design-system/utils';
import type { MfaModalEvents } from '../auth.eventBus';
import { promptMfaCodeBus } from '../auth.eventBus';
import type { BaseTextKey } from '@n8n/i18n';
import { useSSOStore } from '@/features/settings/sso/sso.store';
import type { ConfirmPasswordModalEvents } from '../auth.eventBus';
import { confirmPasswordEventBus } from '../auth.eventBus';

import {
	N8nAvatar,
	N8nButton,
	N8nFormInputs,
	N8nHeading,
	N8nInputLabel,
	N8nLink,
	N8nNotice,
	N8nOption,
	N8nSelect,
	N8nText,
	N8nTooltip,
} from '@n8n/design-system';
type UserBasicDetailsForm = {
	firstName: string;
	lastName: string;
	email: string;
	/**
	 * Required when changing the user email and no MFA enabled
	 */
	currentPassword?: string;
};

type UserBasicDetailsWithMfa = UserBasicDetailsForm & {
	mfaCode?: string;
};

type RoleContent = {
	name: string;
	description: string;
};

const i18n = useI18n();
const { showToast, showError } = useToast();
const documentTitle = useDocumentTitle();

const hasAnyBasicInfoChanges = ref<boolean>(false);
const formInputs = ref<null | IFormInputs>(null);
const formBus = createFormEventBus();
const readyToSubmit = ref(false);
const currentSelectedTheme = ref(useUIStore().theme);
const themeOptions = ref<Array<{ name: ThemeOption; label: BaseTextKey }>>([
	{
		name: 'system',
		label: 'settings.personal.theme.systemDefault',
	},
	{
		name: 'light',
		label: 'settings.personal.theme.light',
	},
	{
		name: 'dark',
		label: 'settings.personal.theme.dark',
	},
]);

const uiStore = useUIStore();
const usersStore = useUsersStore();
const settingsStore = useSettingsStore();
const ssoStore = useSSOStore();
const cloudPlanStore = useCloudPlanStore();

const currentUser = computed((): IUser | null => {
	return usersStore.currentUser;
});

const isExternalAuthEnabled = computed((): boolean => {
	const isLdapEnabled =
		ssoStore.isEnterpriseLdapEnabled && currentUser.value?.signInType === 'ldap';
	const isSamlEnabled = ssoStore.isSamlLoginEnabled && ssoStore.isDefaultAuthenticationSaml;
	const isOidcEnabled =
		ssoStore.isEnterpriseOidcEnabled && currentUser.value?.signInType === 'oidc';
	return isLdapEnabled || isSamlEnabled || isOidcEnabled;
});

const isPersonalSecurityEnabled = computed((): boolean => {
	return usersStore.isInstanceOwner || !isExternalAuthEnabled.value;
});

const mfaDisabled = computed((): boolean => {
	return !usersStore.mfaEnabled;
});
const mfaEnforced = computed((): boolean => {
	return settingsStore.isMFAEnforced;
});
const isMfaFeatureEnabled = computed((): boolean => {
	return settingsStore.isMfaFeatureEnabled;
});

const hasAnyPersonalisationChanges = computed((): boolean => {
	return currentSelectedTheme.value !== uiStore.theme;
});

const hasAnyChanges = computed(() => {
	return hasAnyBasicInfoChanges.value || hasAnyPersonalisationChanges.value;
});

const roles = computed<Record<Role, RoleContent>>(() => ({
	[ROLE.Default]: {
		name: i18n.baseText('auth.roles.default'),
		description: i18n.baseText('settings.personal.role.tooltip.default'),
	},
	[ROLE.Member]: {
		name: i18n.baseText('auth.roles.member'),
		description: i18n.baseText('settings.personal.role.tooltip.member'),
	},
	[ROLE.ChatUser]: {
		name: i18n.baseText('auth.roles.chatUser'),
		description: i18n.baseText('settings.personal.role.tooltip.chatUser'),
	},
	[ROLE.Admin]: {
		name: i18n.baseText('auth.roles.admin'),
		description: i18n.baseText('settings.personal.role.tooltip.admin'),
	},
	[ROLE.Owner]: {
		name: i18n.baseText('auth.roles.owner'),
		description: i18n.baseText('settings.personal.role.tooltip.owner', {
			interpolate: {
				cloudAccess: cloudPlanStore.hasCloudPlan
					? i18n.baseText('settings.personal.role.tooltip.cloud')
					: '',
			},
		}),
	},
}));

const currentUserRole = computed<RoleContent>(() => roles.value[usersStore.globalRoleName]);

onMounted(() => {
	documentTitle.set(i18n.baseText('settings.personal.personalSettings'));
	formInputs.value = [
		{
			name: 'firstName',
			initialValue: currentUser.value?.firstName,
			properties: {
				label: i18n.baseText('auth.firstName'),
				maxlength: 32,
				required: true,
				autocomplete: 'given-name',
				capitalize: true,
				disabled: isExternalAuthEnabled.value,
			},
		},
		{
			name: 'lastName',
			initialValue: currentUser.value?.lastName,
			properties: {
				label: i18n.baseText('auth.lastName'),
				maxlength: 32,
				required: true,
				autocomplete: 'family-name',
				capitalize: true,
				disabled: isExternalAuthEnabled.value,
			},
		},
		{
			name: 'email',
			initialValue: currentUser.value?.email,
			properties: {
				label: i18n.baseText('auth.email'),
				type: 'email',
				required: true,
				validationRules: [{ name: 'VALID_EMAIL' }],
				autocomplete: 'email',
				capitalize: true,
				disabled: !isPersonalSecurityEnabled.value,
			},
		},
	];
});

function onInput() {
	hasAnyBasicInfoChanges.value = true;
}

function onReadyToSubmit(ready: boolean) {
	readyToSubmit.value = ready;
}

/** Saves users basic info and personalization settings */
async function saveUserSettings(params: UserBasicDetailsWithMfa) {
	try {
		// The MFA code might be invalid so we update the user's basic info first
		await updateUserBasicInfo(params);
		await updatePersonalisationSettings();

		showToast({
			title: i18n.baseText('settings.personal.personalSettingsUpdated'),
			message: '',
			type: 'success',
		});
	} catch (e) {
		showError(e, i18n.baseText('settings.personal.personalSettingsUpdatedError'));
	}
}

async function onSubmit(data: Record<string, string | number | boolean | null | undefined>) {
	const form = data as UserBasicDetailsForm;
	const emailChanged = usersStore.currentUser?.email !== form.email;

	if (usersStore.currentUser?.mfaEnabled && emailChanged) {
		uiStore.openModal(PROMPT_MFA_CODE_MODAL_KEY);

		promptMfaCodeBus.once('closed', async (payload: MfaModalEvents['closed']) => {
			if (!payload) {
				// User closed the modal without submitting the form
				return;
			}

			await saveUserSettings({
				...form,
				mfaCode: payload.mfaCode,
			});
		});
	} else if (emailChanged) {
		uiStore.openModal(CONFIRM_PASSWORD_MODAL_KEY);
		confirmPasswordEventBus.once('close', async (payload: ConfirmPasswordModalEvents['close']) => {
			if (!payload) {
				// User closed the modal without submitting the form
				return;
			}

			await saveUserSettings({
				...form,
				currentPassword: payload.currentPassword,
			});
			uiStore.closeModal(CONFIRM_PASSWORD_MODAL_KEY);
		});
	} else {
		await saveUserSettings(form);
	}
}

async function updateUserBasicInfo(userBasicInfo: UserBasicDetailsWithMfa) {
	if (!hasAnyBasicInfoChanges.value || !usersStore.currentUserId) {
		return;
	}

	await usersStore.updateUser({
		firstName: userBasicInfo.firstName,
		lastName: userBasicInfo.lastName,
		email: userBasicInfo.email,
		mfaCode: userBasicInfo.mfaCode,
		currentPassword: userBasicInfo.currentPassword,
	});
	hasAnyBasicInfoChanges.value = false;
}

async function updatePersonalisationSettings() {
	if (!hasAnyPersonalisationChanges.value) {
		return;
	}

	uiStore.setTheme(currentSelectedTheme.value);
}

function onSaveClick() {
	formBus.emit('submit');
}

function openPasswordModal() {
	uiStore.openModal(CHANGE_PASSWORD_MODAL_KEY);
}

async function onMfaEnableClick() {
	if (!settingsStore.isCloudDeployment || !usersStore.isInstanceOwner) {
		uiStore.openModal(MFA_SETUP_MODAL_KEY);
		return;
	}

	try {
		await usersStore.canEnableMFA();
		uiStore.openModal(MFA_SETUP_MODAL_KEY);
	} catch (e) {
		showToast({
			title: i18n.baseText('settings.personal.mfa.toast.canEnableMfa.title'),
			message: e.message,
			type: 'error',
		});
		await usersStore.sendConfirmationEmail();
	}
}

async function disableMfa(payload: MfaModalEvents['closed']) {
	if (!payload) {
		// User closed the modal without submitting the form
		return;
	}

	try {
		await usersStore.disableMfa(payload);

		showToast({
			title: i18n.baseText('settings.personal.mfa.toast.disabledMfa.title'),
			message: i18n.baseText('settings.personal.mfa.toast.disabledMfa.message'),
			type: 'success',
			duration: 0,
		});
	} catch (e) {
		showError(e, i18n.baseText('settings.personal.mfa.toast.disabledMfa.error.message'));
	}
}

async function onMfaDisableClick() {
	uiStore.openModal(PROMPT_MFA_CODE_MODAL_KEY);

	promptMfaCodeBus.once('closed', disableMfa);
}

onBeforeUnmount(() => {
	promptMfaCodeBus.off('closed', disableMfa);
});
</script>

<template>
	<div :class="$style.container" data-test-id="personal-settings-container">
		<div :class="$style.header">
			<N8nHeading size="2xlarge">{{
				i18n.baseText('settings.personal.personalSettings')
			}}</N8nHeading>
			<div v-if="currentUser" :class="$style.user">
				<span :class="$style.username" data-test-id="current-user-name">
					<N8nText color="text-base" bold>{{ currentUser.fullName }}</N8nText>
					<N8nTooltip placement="bottom">
						<template #content>{{ currentUserRole.description }}</template>
						<N8nText :class="$style.tooltip" color="text-light" data-test-id="current-user-role">{{
							currentUserRole.name
						}}</N8nText>
					</N8nTooltip>
				</span>
				<N8nAvatar
					:first-name="currentUser.firstName"
					:last-name="currentUser.lastName"
					size="large"
				/>
			</div>
		</div>
		<div>
			<div class="mb-s">
				<N8nHeading size="large">{{
					i18n.baseText('settings.personal.basicInformation')
				}}</N8nHeading>
			</div>
			<div data-test-id="personal-data-form">
				<N8nFormInputs
					v-if="formInputs"
					:inputs="formInputs"
					:event-bus="formBus"
					@update="onInput"
					@ready="onReadyToSubmit"
					@submit="onSubmit"
				/>
			</div>
		</div>
		<div v-if="isPersonalSecurityEnabled">
			<div class="mb-s">
				<N8nHeading size="large">{{ i18n.baseText('settings.personal.security') }}</N8nHeading>
			</div>
			<div class="mb-s">
				<N8nInputLabel :label="i18n.baseText('auth.password')">
					<N8nLink data-test-id="change-password-link" @click="openPasswordModal">{{
						i18n.baseText('auth.changePassword')
					}}</N8nLink>
				</N8nInputLabel>
			</div>
			<div v-if="isMfaFeatureEnabled" data-test-id="mfa-section">
				<div class="mb-xs">
					<N8nInputLabel :label="i18n.baseText('settings.personal.mfa.section.title')" />
					<N8nText :bold="false" :class="$style.infoText">
						{{
							mfaDisabled
								? i18n.baseText('settings.personal.mfa.button.disabled.infobox')
								: i18n.baseText('settings.personal.mfa.button.enabled.infobox')
						}}
						<N8nLink :to="MFA_DOCS_URL" size="small" :bold="true">
							{{ i18n.baseText('generic.learnMore') }}
						</N8nLink>
					</N8nText>
				</div>
				<N8nNotice
					v-if="mfaDisabled && mfaEnforced"
					:content="i18n.baseText('settings.personal.mfa.enforced')"
				/>

				<N8nButton
					v-if="mfaDisabled"
					:class="$style.button"
					type="tertiary"
					:label="i18n.baseText('settings.personal.mfa.button.enabled')"
					data-test-id="enable-mfa-button"
					@click="onMfaEnableClick"
				/>
				<N8nButton
					v-else
					:class="$style.disableMfaButton"
					type="tertiary"
					:label="i18n.baseText('settings.personal.mfa.button.disabled')"
					data-test-id="disable-mfa-button"
					@click="onMfaDisableClick"
				/>
			</div>
		</div>
		<div>
			<div class="mb-s">
				<N8nHeading size="large">{{
					i18n.baseText('settings.personal.personalisation')
				}}</N8nHeading>
			</div>
			<div>
				<N8nInputLabel :label="i18n.baseText('settings.personal.theme')">
					<N8nSelect
						v-model="currentSelectedTheme"
						:class="$style.themeSelect"
						data-test-id="theme-select"
						size="small"
						filterable
					>
						<N8nOption
							v-for="item in themeOptions"
							:key="item.name"
							:label="i18n.baseText(item.label)"
							:value="item.name"
						>
						</N8nOption>
					</N8nSelect>
				</N8nInputLabel>
			</div>
		</div>
		<div>
			<N8nButton
				float="right"
				:label="i18n.baseText('settings.personal.save')"
				size="large"
				:disabled="!hasAnyChanges || !readyToSubmit"
				data-test-id="save-settings-button"
				@click="onSaveClick"
			/>
		</div>
	</div>
</template>

<style lang="scss" module>
.container {
	padding-bottom: 100px;

	> * {
		margin-bottom: var(--spacing--2xl);
	}
}

.header {
	display: flex;
	align-items: center;
	white-space: nowrap;
	*:first-child {
		flex-grow: 1;
	}
}

.user {
	display: flex;
	align-items: center;

	@media (max-width: $breakpoint-2xs) {
		display: none;
	}
}

.username {
	display: grid;
	grid-template-columns: 1fr;
	margin-right: var(--spacing--sm);

	@media (max-width: $breakpoint-sm) {
		max-width: 100px;
		overflow: hidden;
		text-overflow: ellipsis;
	}
}

.tooltip {
	justify-self: start;
}

.disableMfaButton {
	> span {
		font-weight: var(--font-weight--bold);
	}
}

.button {
	font-size: var(--spacing--xs);
	> span {
		font-weight: var(--font-weight--bold);
	}
}

.infoText {
	font-size: var(--font-size--2xs);
	color: var(--color--text--tint-1);
}

.themeSelect {
	max-width: 50%;
}
</style>
