<script setup>

import { ref, useTemplateRef, onMounted, inject } from 'vue';
import { Dialog, Button, Icon, FormGroup, MaskedInput, SingleSelect, Checkbox, TextInput, ProgressBar } from '@cloudron/pankow';
import { prettyLongDate } from '@cloudron/pankow/utils';
import Section from './Section.vue';
import UserDirectoryModel from '../models/UserDirectoryModel.js';
import TasksModel from '../models/TasksModel.js';
import { TASK_TYPES } from '../constants.js';

const userDirectoryModel = UserDirectoryModel.create();
const tasksModel = TasksModel.create();

const subscriptionRequiredDialog = inject('subscriptionRequiredDialog');

const availableProviders = [
  { name: 'Active Directory', value: 'ad' },
  { name: 'Cloudron', value: 'cloudron' },
  { name: 'Jumpcloud', value: 'jumpcloud' },
  { name: 'Okta', value: 'okta' },
  { name: 'Univention Corporate Server (UCS)', value: 'univention' },
  { name: 'Other', value: 'other' },
  { name: 'Disabled', value: 'noop' }
];

const dialog = useTemplateRef('dialog');
const features = inject('features');
const taskLogsMenu = ref([]);
const tasks = ref([]);
const config = ref({ provider: 'noop' });

// for edit dialog
const editBusy = ref(false);
const editError = ref({});
const provider = ref('');
const url = ref('');
const acceptSelfSignedCerts = ref(false);
const baseDn = ref('');
const filter = ref('');
const usernameField = ref('');
const syncGroups = ref(false);
const groupBaseDn = ref('');
const groupFilter = ref('');
const groupnameField = ref('');
const bindDn = ref('');
const bindPassword = ref('');
const autoCreate = ref(false);

const form = useTemplateRef('form');
const isFormValid = ref(false);
function checkValidity() {
  isFormValid.value = form.value ? form.value.checkValidity() : false;
}

function onProviderChange() {
  url.value = '';
  acceptSelfSignedCerts.value = false;
  baseDn.value = '';
  filter.value = '';
  usernameField.value = '';
  syncGroups.value = false;
  groupBaseDn.value = '';
  groupFilter.value = '';
  groupnameField.value = '';
  bindDn.value = '';
  bindPassword.value = '';
  autoCreate.value = false;
}

async function onConfigure() {
  if (!features.value.externalLdap) return subscriptionRequiredDialog.value.open();

  editBusy.value = false;
  editError.value = {};
  provider.value = config.value.provider;
  url.value = config.value.url;
  acceptSelfSignedCerts.value = config.value.acceptSelfSignedCerts;
  baseDn.value = config.value.baseDn;
  filter.value = config.value.filter;
  usernameField.value = config.value.usernameField;
  syncGroups.value = config.value.syncGroups;
  groupBaseDn.value = config.value.groupBaseDn;
  groupFilter.value = config.value.groupFilter;
  groupnameField.value = config.value.groupnameField;
  bindDn.value = config.value.bindDn;
  bindPassword.value = config.value.bindPassword;
  autoCreate.value = config.value.autoCreate;

  dialog.value.open();

  setTimeout(checkValidity, 100); // update state of the confirm button
}

async function onSubmit() {
  if (!form.value.reportValidity()) return;

  editBusy.value = true;
  editError.value = {};

  const config = { provider: provider.value };

  if (provider.value === 'cloudron') {
    config.url = url.value;
    config.acceptSelfSignedCerts = acceptSelfSignedCerts.value;
    config.autoCreate = autoCreate.value;
    config.syncGroups = syncGroups.value;
    if (bindPassword.value) config.bindPassword = bindPassword.value;

    // those values are known and thus overwritten
    config.baseDn = 'ou=users,dc=cloudron';
    config.filter = '(objectClass=inetOrgPerson)';
    config.usernameField = 'username';
    config.groupBaseDn = 'ou=groups,dc=cloudron';
    config.groupFilter = '(objectClass=group)';
    config.groupnameField = 'cn';
    config.bindDn = 'cn=admin,ou=system,dc=cloudron';
  } else if (provider.value !== 'noop') {
    config.url = url.value;
    config.acceptSelfSignedCerts = acceptSelfSignedCerts.value;
    config.baseDn = baseDn.value;
    config.filter = filter.value;
    config.usernameField = usernameField.value;
    config.syncGroups = syncGroups.value;
    config.groupBaseDn = groupBaseDn.value;
    config.groupFilter = groupFilter.value;
    config.groupnameField = groupnameField.value;
    config.autoCreate = autoCreate.value;

    if (bindDn.value) {
      config.bindDn = bindDn.value;
      if (bindPassword.value) config.bindPassword = bindPassword.value;
    }
  }

  const [error] = await userDirectoryModel.setExternalLdapConfig(config);
  if (error) {
    editBusy.value = false;

    if (error.status === 424) {
      if (error.code === 'SELF_SIGNED_CERT_IN_CHAIN') editError.value.acceptSelfSignedCerts = true;
      else editError.value.url = true;
      editError.value.generic = error.body.message;
    } else if (error.status === 400 && error.body.message === 'invalid baseDn') {
      editError.value.baseDn = true;
    } else if (error.status === 400 && error.body.message === 'invalid filter') {
      editError.value.filter = true;
    } else if (error.status === 400 && error.body.message === 'invalid groupBaseDn') {
      editError.value.groupBaseDn = true;
    } else if (error.status === 400 && error.body.message === 'invalid groupFilter') {
      editError.value.groupFilter = true;
    } else if (error.status === 400 && error.body.message === 'invalid groupnameField') {
      editError.value.groupnameField = true;
    } else if (error.status === 400 && error.body.message === 'invalid bind credentials') {
      editError.value.credentials = true;
    }  else if (error.status === 400 && error.body.message === 'invalid usernameField') {
      editError.value.usernameField = true;
    } else {
      console.error('Failed to set external LDAP config:', error);
      editError.value.generic = error.body.message;
    }

    return;
  }

  await refresh();
  editBusy.value = false;
  dialog.value.close();
}

const syncBusy = ref(false);
const syncPercent = ref(0);
const syncMessage = ref('');
const syncError = ref('');

async function onSync() {
  syncBusy.value = true;
  syncPercent.value = 0;
  syncMessage.value = '';
  syncError.value = '';

  const [error] = await userDirectoryModel.startExternalLdapSync();
  if (error && error.body) syncMessage.value = error.body.message;
  else if (error) console.error(error);
  else await refreshTasks();
}

async function updateTaskStatus(id) {
  const [error, result] = await tasksModel.get(id);
  if (error) return setTimeout(updateTaskStatus.bind(null, id), 5000);

  if (!result.active) {
      syncBusy.value = false;
      syncMessage.value = '';
      syncPercent.value = 100; // indicates that 'result' is valid
      syncError.value = result.success ? '' : result.error.message;

      await refreshTasks(); // update the tasks list dropdown
      return;
  }

  syncBusy.value = true;
  syncPercent.value = result.percent;
  syncMessage.value = result.message;

  setTimeout(updateTaskStatus.bind(null, id), 2000);
}

async function refreshTasks() {
  const [error, result] = await tasksModel.getByType(TASK_TYPES.TASK_SYNC_EXTERNAL_LDAP);
  if (error) return console.error(error);

  // limit to last 10
  tasks.value = result.slice(0, 10);

  if (tasks.value.length && tasks.value[0].active) updateTaskStatus(tasks.value[0].id);

  taskLogsMenu.value = tasks.value.map(t => {
    return {
      icon: 'fa-solid ' + ((!t.active && t.success) ? 'status-active fa-check-circle' : (t.active ? 'fa-circle-notch fa-spin' : 'status-error fa-times-circle')),
      label: prettyLongDate(t.ts),
      action: () => { window.open(`/logs.html?taskId=${t.id}`); }
    };
  });
}

async function refresh() {
  const [error, result] = await userDirectoryModel.getExternalLdapConfig();
  if (error) return console.error(error);

  config.value = result;
}

onMounted(async () => {
  await refresh();
  await refreshTasks();
});

</script>

<template>
  <div>
    <Dialog ref="dialog"
        :title="$t('users.externalLdapDialog.title')"
        :confirm-label="$t('main.dialog.save')"
        :confirm-busy="editBusy"
        :confirm-active="!editBusy && isFormValid"
        :reject-label="$t('main.dialog.cancel')"
         reject-style="secondary"
        @confirm="onSubmit()"
      >
      <p class="has-error text-center" v-show="editError.generic">{{ editError.generic }}</p>

      <FormGroup>
        <label for="ldapProvider">{{ $t('users.externalLdap.provider') }} <sup><a href="https://docs.cloudron.io/user-directory/#external-directory" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
        <SingleSelect id="ldapProvider" @select="onProviderChange" v-model="provider" :options="availableProviders" option-key="value" option-label="name"/>
        <div class="warning-label" v-show="provider === 'noop' && config.provider !== 'noop'">{{ $t('users.externalLdap.disableWarning') }}</div>
      </FormGroup>

      <form ref="form" @submit.prevent="onSubmit()" autocomplete="off" @input="checkValidity()">
        <fieldset :disabled="editBusy" v-if="provider !== 'noop'">
          <input style="display: none" type="submit" />

          <FormGroup :class="{ 'has-error': editError.url }">
            <label class="control-label" for="configUrlInput">{{ $t('users.externalLdap.server') }}</label>
            <TextInput id="configUrlInput" v-model="url" placeholder="ldaps://example.com:636" required/>
          </FormGroup>

          <Checkbox v-model="acceptSelfSignedCerts" :label="$t('users.externalLdap.acceptSelfSignedCert')" />
          <p class="has-error" v-show="editError.acceptSelfSignedCerts">{{ $t('users.externalLdap.errorSelfSignedCert') }}</p>

          <FormGroup :class="{ 'has-error': editError.baseDn }" v-if="provider !== 'cloudron'">
            <label for="baseDnInput">{{ $t('users.externalLdap.baseDn') }}</label>
            <TextInput v-model="baseDn" id="baseDnInput" placeholder="ou=users,dc=example,dc=com" required />
          </FormGroup>

          <FormGroup :class="{ 'has-error': editError.filter }" v-if="provider !== 'cloudron'">
            <label for="filterInput">{{ $t('users.externalLdap.filter') }}</label>
            <TextInput v-model="filter" id="filterInput" placeholder="(objectClass=inetOrgPerson)" required />
          </FormGroup>

          <FormGroup :class="{ 'has-error': editError.usernameField }" v-if="provider !== 'cloudron'">
            <label for="usernameFieldInput">{{ $t('users.externalLdap.usernameField') }}</label>
            <TextInput v-model="usernameField" id="usernameFieldInput" placeholder="uid or sAMAcountName" />
          </FormGroup>

          <Checkbox v-model="syncGroups" :label="$t('users.externalLdap.syncGroups')" />

          <FormGroup :class="{ 'has-error': editError.groupBaseDn }" v-if="syncGroups && provider !== 'cloudron'">
            <label for="groupBaseDnInput">{{ $t('users.externalLdap.groupBaseDn') }}</label>
            <TextInput v-model="groupBaseDn" id="groupBaseDnInput" placeholder="ou=groups,dc=example,dc=com" required />
          </FormGroup>

          <FormGroup :class="{ 'has-error': editError.groupFilter }" v-if="syncGroups && provider !== 'cloudron'">
            <label for="groupFilterInput">{{ $t('users.externalLdap.groupFilter') }}</label>
            <TextInput v-model="groupFilter" id="groupFilterInput" placeholder="(objectClass=groupOfNames)" required />
          </FormGroup>

          <FormGroup :class="{ 'has-error': editError.groupnameField }" v-if="syncGroups && provider !== 'cloudron'">
            <label for="groupnameFieldInput">{{ $t('users.externalLdap.groupnameField') }}</label>
            <TextInput v-model="groupnameField" id="groupnameFieldInput" placeholder="cn" required />
          </FormGroup>

          <FormGroup :class="{ 'has-error': editError.credentials }" v-if="provider !== 'cloudron'">
            <label for="bindDnInput">{{ $t('users.externalLdap.bindUsername') }}</label>
            <TextInput v-model="bindDn" id="bindDnInput" placeholder="uid=admin,ou=Users,dc=example,dc=com" />
          </FormGroup>

          <FormGroup :class="{ 'has-error': editError.credentials }">
            <label for="bindPasswordInput">{{ $t('users.externalLdap.bindPassword') }}</label>
            <MaskedInput v-model="bindPassword" id="bindPasswordInput" />
          </FormGroup>

          <Checkbox v-model="autoCreate" :label="$t('users.externalLdap.autocreateUsersOnLogin')" />
        </fieldset>
      </form>
    </Dialog>

    <Section :title="$t('users.externalLdap.title')" :title-badge="!features.externalLdap ? 'Upgrade' : ''">
      <template #header-buttons>
        <Button tool secondary :menu="taskLogsMenu" :disabled="!taskLogsMenu.length">{{ $t('main.action.logs') }}</Button>
      </template>

      <div>{{ $t('users.externalLdap.description') }}</div>
      <br/>

      <div v-if="features.externalLdap">
        <div v-show="config.provider === 'noop'">
          {{ $t('users.externalLdap.noopInfo') }}
        </div>

        <div class="info-row" v-show="config.provider !== 'noop'">
          <div class="info-label">{{ $t('users.externalLdap.provider') }}</div>
          <div class="info-value">{{ config.provider }}</div>
        </div>

        <div class="info-row" v-show="config.provider !== 'noop'">
          <div class="info-label">{{ $t('users.externalLdap.server') }}</div>
          <div class="info-value">{{ config.url }}</div>
        </div>

        <div class="info-row" v-show="config.provider !== 'noop'">
          <div class="info-label">{{ $t('users.externalLdap.acceptSelfSignedCert') }}</div>
          <div class="info-value"><Icon :icon="config.acceptSelfSignedCerts ? 'fa-solid fa-check' : 'fa-solid fa-xmark'" /></div>
        </div>

        <div class="info-row" v-show="config.provider !== 'noop' && config.provider !== 'cloudron'">
          <div class="info-label">{{ $t('users.externalLdap.baseDn') }}</div>
          <div class="info-value">{{ config.baseDn }}</div>
        </div>

        <div class="info-row" v-show="config.provider !== 'noop' && config.provider !== 'cloudron'">
          <div class="info-label">{{ $t('users.externalLdap.filter') }}</div>
          <div class="info-value">{{ config.filter }}</div>
        </div>

        <div class="info-row" v-show="config.provider !== 'noop' && config.provider !== 'cloudron'">
          <div class="info-label">{{ $t('users.externalLdap.usernameField') }}</div>
          <div class="info-value">{{ config.usernameField || 'uid' }}</div>
        </div>

        <div class="info-row" v-show="config.provider !== 'noop'">
          <div class="info-label">{{ $t('users.externalLdap.syncGroups') }}</div>
          <div class="info-value"><Icon :icon="config.syncGroups ? 'fa-solid fa-check' : 'fa-solid fa-xmark'" /></div>
        </div>

        <div class="info-row" v-show="config.provider !== 'noop' && config.provider !== 'cloudron' && config.syncGroups">
          <div class="info-label">{{ $t('users.externalLdap.groupBaseDn') }}</div>
          <div class="info-value">{{ config.groupBaseDn }}</div>
        </div>

        <div class="info-row" v-show="config.provider !== 'noop' && config.provider !== 'cloudron' && config.syncGroups">
          <div class="info-label">{{ $t('users.externalLdap.groupFilter') }}</div>
          <div class="info-value">{{ config.groupFilter }}</div>
        </div>

        <div class="info-row" v-show="config.provider !== 'noop' && config.provider !== 'cloudron' && config.syncGroups">
          <div class="info-label">{{ $t('users.externalLdap.groupnameField') }}</div>
          <div class="info-value">{{ config.groupnameField }}</div>
        </div>

        <div class="info-row" v-show="config.provider !== 'noop' && config.provider !== 'cloudron'">
          <div class="info-label">{{ $t('users.externalLdap.auth') }}</div>
          <div class="info-value"><Icon :icon="config.bindDn ? 'fa-solid fa-check' : 'fa-solid fa-xmark'" /></div>
        </div>

        <div class="info-row" v-show="config.provider !== 'noop'">
          <div class="info-label">{{ $t('users.externalLdap.autocreateUsersOnLogin') }}</div>
          <div class="info-value"><Icon :icon="config.autoCreate ? 'fa-solid fa-check' : 'fa-solid fa-xmark'" /></div>
        </div>
      </div>

      <br/>

      <ProgressBar :value="syncPercent" v-show="syncBusy" :busy="true" />
      <p v-show="syncBusy">{{ syncMessage || 'Queued' }}</p>
      <p v-show="!syncBusy && syncError" class="has-error">{{ syncError }}</p>

      <div class="button-bar">
        <Button @click="onSync()" :loading="syncBusy" :disabled="syncBusy || config.provider === 'noop'">{{ $t('users.externalLdap.syncAction') }}</Button>
        <Button @click="onConfigure()">{{ $t('users.externalLdap.configureAction') }}</Button>
      </div>
    </Section>
  </div>
</template>
