<script setup>

import { useI18n } from 'vue-i18n';
const i18n = useI18n();
const t = i18n.t;

import { ref, onMounted, computed, useTemplateRef, inject } from 'vue';
import { Button, TextInput, SingleSelect, TableView, InputDialog, ProgressBar } from '@cloudron/pankow';
import { ROLES } from '../constants.js';
import Section from '../components/Section.vue';
import ActionBar from '../components/ActionBar.vue';
import UserDialog from '../components/UserDialog.vue';
import ImpersonateDialog from '../components/ImpersonateDialog.vue';
import InvitationDialog from '../components/InvitationDialog.vue';
import PasswordResetDialog from '../components/PasswordResetDialog.vue';
import UsersModel from '../models/UsersModel.js';
import GroupsModel from '../models/GroupsModel.js';
import ProfileModel from '../models/ProfileModel.js';
import DashboardModel from '../models/DashboardModel.js';

const usersModel = UsersModel.create();
const groupsModel = GroupsModel.create();
const profileModel = ProfileModel.create();
const dashboardModel = DashboardModel.create();

const usersColumns = {
  avatar: {
    width: '40px'
  },
  user: {
    label: t('users.users.user'),
    nowrap: true,
    sort(a,b, A, B) {
      if (!A.username) return -1;
      if (!B.username) return 1;
      return A.username.localeCompare(B.username);
    },
    width: '70%',
  },
  groups: {
    label: t('users.users.groups'),
    hideMobile: true,
    nowrap: true,
    width: '30%',
  },
  role: {
    width: '30px',
    hideMobile: true,
  },
  actions: {
    width: window.innerWidth <= 576 ? 'auto' : '150px',
  }
};

function createUserActionMenu(user) {
  return [{
    icon: 'fa fa-pencil-alt',
    label: t('main.action.edit'),
    disabled: !canEdit(user),
    quickAction: true,
    action: onEditOrAddUser.bind(null, user),
  }, {
    separator: true,
  }, {
    icon: 'fa-solid fa-paper-plane',
    label: t('users.users.invitationTooltip'),
    visible: !user.inviteAccepted && !isMe(user) && !user.source,
    disabled: !canEdit(user),
    quickAction: true,
    action: onInvitation.bind(null, user),
  }, {
    icon: 'fa-solid fa-qrcode',
    label: t('users.passwordResetDialog.reset2FAAction'),
    visible: user.totpEnabled && !(user.source && external2FA),
    disabled: !canEdit(user),
    action: on2FAReset.bind(null, user),
  }, {
    icon: 'fa-solid fa-key',
    label: t('users.users.resetPasswordTooltip'),
    visible: user.inviteAccepted && !user.source,
    disabled: !canEdit(user),
    action: onPasswordReset.bind(null, user),
  }, {
    icon: 'fa-solid fa-user-secret',
    label: t('users.users.setGhostTooltip'),
    visible: canImpersonate(user),
    action: onImpersonate.bind(null, user),
  }, {
    separator: true,
  }, {
    icon: 'fa-solid fa-trash-alt',
    label: t('main.action.remove'),
    disabled: !canEdit(user) || isMe(user),
    action: onRemoveUser.bind(null, user),
  }];
}

const external2FA = ref(false);
const profile = ref({});
const busy = ref(true);
const filterOptions = ref([
  { id: 'all', name: 'All' },
  { separator: true, name: 'State' },
  { id: 'active', name: 'Active' },
  { id: 'inactive', name: 'Inactive' },
  { id: 'invited', name: 'Invited' },
  { separator: true, name: 'Roles' },
  { id: ROLES.USER, name: t('users.role.user') },
  { id: ROLES.USER_MANAGER, name: t('users.role.usermanager') },
  { id: ROLES.MAIL_MANAGER, name: t('users.role.mailmanager') },
  { id: ROLES.ADMIN, name: t('users.role.admin') },
  { id: ROLES.OWNER, name: t('users.role.owner') },
]);
const users = ref([]);
const usersById = ref({});
const search = ref('');
const filter = ref(filterOptions.value[0].id);
const groups = ref([]);
const groupsById = ref({});
const roles = ref([]);

const features = inject('features');
const subscriptionRequiredDialog = inject('subscriptionRequiredDialog');

const inputDialog = useTemplateRef('inputDialog');
const userDialog = useTemplateRef('userDialog');
const impersonateDialog = useTemplateRef('impersonateDialog');
const passwordResetDialog = useTemplateRef('passwordResetDialog');

const filteredUsers = computed(() => {
  return users.value.filter(u => {
    const username = u.username || ''; // username is null if not yet set
    return username.indexOf(search.value) !== -1 || u.email.indexOf(search.value) !== -1 || u.displayName.indexOf(search.value) !== -1;
  }).filter(u => {
    if (filter.value === 'active') {
      return u.active && (u.inviteAccepted || u.source);
    } else if (filter.value === 'inactive') {
      return !u.active;
    } else if (filter.value === 'invited') {
      return !u.inviteAccepted && !u.source;
    } else if (filter.value === ROLES.USER || filter.value === ROLES.USER_MANAGER || filter.value === ROLES.MAIL_MANAGER || filter.value === ROLES.ADMIN || filter.value === ROLES.OWNER) {
      return u.role === filter.value;
    } else {
      return true;
    }
  });
});

async function refreshUsers() {
  const [error, result] = await usersModel.list();
  if (error) return console.error(error);

  usersById.value = {};
  users.value = result;
  result.forEach(user => {
    usersById.value[user.id] = user;
  });
}

async function refreshGroups() {
  const [error, result] = await groupsModel.list();
  if (error) return console.error(error);

  groupsById.value = {};
  groups.value = result;
  result.forEach(group => {
    groupsById.value[group.id] = group;
  });
}

function canEdit(user) {
  const roleInt1 = roles.value.findIndex(function (role) { return role.id === profile.value.role; });
  const roleInt2 = roles.value.findIndex(function (role) { return role.id === user.role; });

  return (roleInt1 - roleInt2) >= 0;
}

function canImpersonate(user) {
  // only admins can impersonate
  if (!profile.value.isAtLeastAdmin) return false;

  // only users with username can be impersonated
  if (!user.username) return false;

  // normal admins cannot impersonate owners
  if (!profile.value.isAtLeastOwner && [ ROLES.OWNER ].indexOf(user.role) !== -1) return false;

  return true;
}

function isMe(user) {
  return user.username === profile.value.username;
}

function onImpersonate(user) {
  impersonateDialog.value.open(user);
}

function onPasswordReset(user) {
  passwordResetDialog.value.open(user);
}

async function on2FAReset(user) {
  const yes = await inputDialog.value.confirm({
    title: t('users.2FAResetDialog.title'),
    message: t('users.2FAResetDialog.description', { username: (user.username || user.email) }),
    confirmStyle: 'danger',
    confirmLabel: t('main.action.reset'),
    rejectLabel: t('main.dialog.cancel'),
    rejectStyle: 'secondary'
  });

  if (!yes) return;

  const [error] = await usersModel.disableTotp(user.id);
  if (error) return console.error(error);

  user.totpEnabled = false;
}

function onEditOrAddUser(user = null) {
  if (user || features.value.userMaxCount > users.value.length) userDialog.value.open(user);
  else subscriptionRequiredDialog.value.open();
}

const invitationDialog = useTemplateRef('invitationDialog');
function onInvitation(user) {
  invitationDialog.value.open(user);
}

async function onRemoveUser(user) {
  const yes = await inputDialog.value.confirm({
    title: t('users.deleteUserDialog.title'),
    message: t('users.deleteUserDialog.description', { username: (user.username || user.email) }),
    confirmStyle: 'danger',
    confirmLabel: t('users.deleteUserDialog.deleteAction'),
    rejectLabel: t('main.dialog.cancel'),
    rejectStyle: 'secondary'
  });

  if (!yes) return;

  const [error] = await usersModel.remove(user.id);
  if (error) console.error(error);

  await refreshUsers();
}

onMounted(async () => {
  let [error, result] = await profileModel.get();
  if (error) return console.error(error);

  profile.value = result;

  await refreshUsers();
  await refreshGroups();

  // Order matters for permissions used in canEdit
  roles.value = [
    { id: 'user', name: t('users.role.user'), disabled: false },
    { id: 'usermanager', name: t('users.role.usermanager'), disabled: false },
    { id: 'mailmanager', name: t('users.role.mailmanager'), disabled: false },
    { id: 'admin', name: t('users.role.admin'), disabled: !profile.value.isAtLeastAdmin },
    { id: 'owner', name: t('users.role.owner'), disabled: !profile.value.isAtLeastOwner }
  ];

    [error, result] = await dashboardModel.config();
    if (error) return console.error(error);
    external2FA.value = result.external2FA;

  busy.value = false;
});

</script>

<template>
  <div class="content">
    <InputDialog ref="inputDialog" />
    <UserDialog ref="userDialog" @success="refreshUsers()"/>
    <ImpersonateDialog ref="impersonateDialog" />
    <PasswordResetDialog ref="passwordResetDialog" />
    <InvitationDialog ref="invitationDialog" @refresh-required="refreshUsers()" />

    <Section :title="$t('main.navbar.users')">
      <template #header-title-extra>
        <span style="font-weight: normal; font-size: 14px">({{ busy ? '-' : filteredUsers.length }})</span>
      </template>
      <template #filter-bar>
        <TextInput v-model="search" placeholder="Search ..." style="flex-grow: 1"/>
        <SingleSelect :options="filterOptions" option-key="id" option-label="name" v-model="filter" />
      </template>
      <template #header-buttons>
        <Button @click="onEditOrAddUser()">{{ $t('main.action.add') }}</Button>
      </template>

      <ProgressBar v-if="busy" mode="indeterminate" :show-label="false" :slim="true" :show-track="false"/>

      <TableView :columns="usersColumns" :model="filteredUsers" :busy="busy" :fixed-layout="true" :placeholder="$t(search ? 'users.users.noMatchesPlaceholder' : 'users.users.emptyPlaceholder')">
        <template #avatar="{ item:user }">
          <img v-if="user.hasAvatar" :src="user.avatarUrl" @error="$event.target.src = '/img/avatar-default-symbolic.svg'" style="width: 30px; height: 30px; border-radius: 5px"/>
          <img v-else src="/img/avatar-default-symbolic.svg" style="width: 30px; height: 30px;"/>
        </template>
        <template #user="{ item:user }">
          {{ user.displayName }}
          <span class="text-muted" style="margin: 0 6px" v-if="user.username">{{ user.username }}</span>
          <span class="text-muted" style="margin: 0 6px" v-else>{{ user.email }}</span>

          <i class="fa fa-ban" v-if="!user.active" v-tooltip="$t('users.users.inactiveTooltip')"></i>
          <!-- <i v-show="user.source" class="far fa-address-book" v-tooltip="$t('users.users.externalLdapTooltip')"></i> -->
        </template>
        <template #groups="{ item:user }">
          {{ user.groupIds.map(gid => groupsById[gid] ? groupsById[gid].name : gid).join(' ') }}
        </template>
        <template #role="{ item:user }">
          <i class="fas fa-crown arrow" v-if="user.role === 'owner'" v-tooltip="$t('users.users.superadminTooltip')"></i>
          <i class="fa fa-user-tie arrow" v-else-if="user.role === 'admin'" v-tooltip="$t('users.users.adminTooltip')"></i>
          <i class="fas fa-users-cog arrow" v-else-if="user.role === 'usermanager'" v-tooltip="$t('users.users.usermanagerTooltip')"></i>
          <i class="fas fa-mail-bulk arrow" v-else-if="user.role === 'mailmanager'" v-tooltip="$t('users.users.mailmanagerTooltip')"></i>
        </template>
        <template #actions="{ item:user }">
          <ActionBar :actions="createUserActionMenu(user)" />
        </template>
      </TableView>
    </Section>
  </div>
</template>
