<script setup>

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

import { ref, onMounted, useTemplateRef, reactive, inject, computed } from 'vue';
import { Button, ProgressBar, InputDialog } from '@cloudron/pankow';
import { prettyLongDate } from '@cloudron/pankow/utils';
import ActionBar from '../components/ActionBar.vue';
import Section from  '../components/Section.vue';
import StateLED from  '../components/StateLED.vue';
import BackupSiteScheduleDialog from  '../components/BackupSiteScheduleDialog.vue';
import BackupSiteAddDialog from  '../components/BackupSiteAddDialog.vue';
import BackupSiteContentDialog from  '../components/BackupSiteContentDialog.vue';
import BackupSiteConfigDialog from  '../components/BackupSiteConfigDialog.vue';
import SystemBackupList from  '../components/SystemBackupList.vue';
import { TASK_TYPES } from '../constants.js';
import BackupSitesModel from '../models/BackupSitesModel.js';
import TasksModel from '../models/TasksModel.js';
import AppsModel from '../models/AppsModel.js';

import { prettySchedule } from  '../utils.js';

const profile = inject('profile');

const tasksModel = TasksModel.create();
const backupSitesModels = BackupSitesModel.create();
const appsModel = AppsModel.create();
const allApps = ref([]);

const inputDialog = useTemplateRef('inputDialog');
const systemBackupList = useTemplateRef('systemBackupList');

const sites = ref([]);
const busy = ref(true);
const hasUpdateBackupSite = computed(() => sites.value.some(site => site.enableForUpdates));

const backupSiteAddDialog = useTemplateRef('backupSiteAddDialog');
function onAdd() {
  backupSiteAddDialog.value.open();
}

const backupSiteContentDialog = useTemplateRef('backupSiteContentDialog');
function onEditContent(site) {
  backupSiteContentDialog.value.open(site);
}

const backupSiteScheduleDialog = useTemplateRef('backupSiteScheduleDialog');
function onEditSchedule(site) {
  backupSiteScheduleDialog.value.open(site);
}

const backupSiteConfigDialog = useTemplateRef('backupSiteConfigDialog');
function onEditConfig(site) {
  backupSiteConfigDialog.value.open(site);
}

function prettyBackupRetention(retention) {
  function stableStringify(obj) { return JSON.stringify(obj, Object.keys(obj).sort()); }
  const tmp = BackupSitesModel.backupRetentions.find(function (p) { return stableStringify(p.id) === stableStringify(retention); });
  return tmp ? tmp.name : '';
}

function prettyBackupContents(contents) {
  if (!contents) return 'Everything';

  // compute include or exclude links
  const links = [];
  for (const appId of (contents.include || contents.exclude)) {
    if (appId === 'box') {
      links.unshift('System &amp; email'); // keep this as first item
    } else {
      const label = allApps[appId] ? (allApps[appId].label || allApps[appId].fqdn) : appId;
      links.push(`<a href="#/app/${appId}/info">${label}</a>`);
    }
  }

  if (contents.include) {
    return `Only ${links.join(', ')}`;
  }

  if (contents.exclude) {
    return `Everything except ${links.join(', ')}`;
  }

  return '';
}

async function onRemoveSite(site) {
  const yes = await inputDialog.value.confirm({
    title: t('backup.site.removeDialog.title'),
    message: t('backup.site.removeDialog.description', { name: site.name }),
    confirmLabel: t('main.action.remove'),
    confirmStyle: 'danger',
    rejectLabel: t('main.dialog.cancel'),
    rejectStyle: 'secondary',
  });

  if (!yes) return;

  const [error] = await backupSitesModels.del(site.id);
  if (error) console.error(error);

  await refresh();

  systemBackupList.value.refresh();
}

async function onRemount(site) {
  site.status.busy = true;

  const [error] = await backupSitesModels.remount(site.id);
  if (error) return console.error(error);

  const [statusError, status] = await backupSitesModels.status(site.id);
  if (statusError) console.error(statusError);

  site.status.state = status.state === 'active' ? 'success' : 'danger';
  site.status.message = status.message;
  site.status.busy = false;
}

async function onStartBackup(site) {
  const [error, result] = await backupSitesModels.createBackup(site.id);
  if (error) {
    if (error.status === 409) {
      if (error.body.message.indexOf('full_backup') !== -1) window.pankow.notify({ text: 'Backup already in progress. Please retry later.', type: 'danger' });
      else window.pankow.notify({ text: 'App task is currently in progress. Please retry later.', type: 'danger' });
    }

    return console.error(error);
  }

  const [taskError, task] = await tasksModel.get(result);
  if (taskError) return console.error(taskError);

  site.task = task;

  setTimeout(waitForSiteTask.bind(null,site), 2000);
}

async function onStartCleanup(site) {
  const [error, result] = await backupSitesModels.cleanup(site.id);
  if (error) return console.error(error);

  const [taskError, task] = await tasksModel.get(result);
  if (taskError) return console.error(taskError);

  site.task = task;

  setTimeout(waitForSiteTask.bind(null,site), 2000);
}

function createActionMenu(site) {
  return [{
    icon: 'fa-solid fa-screwdriver-wrench',
    label: t('backups.configAction'),
    visible: profile.value.isAtLeastOwner,
    action: onEditConfig.bind(null, site),
  }, {
    icon: 'fa-solid fa-box-open',
    label: t('backups.contentAction'),
    visible: profile.value.isAtLeastOwner,
    action: onEditContent.bind(null, site),
  }, {
    icon: 'fa-solid fa-clock',
    label: t('backups.schedule.title'),
    visible: profile.value.isAtLeastOwner,
    action: onEditSchedule.bind(null, site),
  },{
    visible: profile.value.isAtLeastOwner,
    separator: true,
  }, {
    icon: 'fa-solid fa-plus',
    label: t('backups.listing.backupNow'),
    disabled: !!site.task?.active,
    quickAction: true,
    action: onStartBackup.bind(null, site),
  }, {
    icon: 'fa-solid fa-broom',
    label: t('backups.listing.cleanupBackups'),
    disabled: !!site.task?.active,
    action: onStartCleanup.bind(null, site),
  }, {
    icon: 'fa-solid fa-sync-alt',
    label: t('backups.location.remount'),
    visible: site.provider === 'sshfs' || site.provider === 'cifs' || site.provider === 'nfs' || site.provider === 'ext4' || site.provider === 'xfs',
    quickAction: true,
    action: onRemount.bind(null, site),
  }, {
    visible: profile.value.isAtLeastOwner,
    separator: true,
  }, {
    icon: 'fa-solid fa-trash',
    label: t('volumes.removeVolumeDialog.removeAction'),
    visible: profile.value.isAtLeastOwner,
    action: onRemoveSite.bind(null, site),
  }];
}

async function waitForSiteTask(site) {
  const [error, result] = await tasksModel.get(site.task.id);
  site.progress = result.percent;
  if (error) {
    console.error(error);
    setTimeout(waitForSiteTask.bind(null, site), 2000);
  } else if (result.active) {
    site.task = result;
    setTimeout(waitForSiteTask.bind(null, site), 2000);
  } else {
    systemBackupList.value.refresh();
    site.task = result;
  }
}

async function onCancelTask(taskId) {
  const [error] = await tasksModel.stop(taskId);
  if (error) console.error('Failed to cancel task:', error);
}

async function refreshStatusForSite(site) {
  const [error, status] = await backupSitesModels.status(site.id);
  if (error) return console.error(error);

  site.status.state = status.state === 'active' ? 'success' : 'danger';
  site.status.message = status.message;
  site.status.busy = false;
}

async function refreshTaskForSite(site) {
  const [error, tasks] = await tasksModel.getByType(TASK_TYPES.TASK_FULL_BACKUP_PREFIX + site.id);
  if (error) return console.error(error);

  if (tasks[0]) {
    site.task = tasks[0];
    if (site.task.active) setTimeout(waitForSiteTask.bind(null, site), 2000);
  } else {
    site.task = null;
  }

  site.taskLoaded = true;
}

async function refresh() {
  busy.value = true;

  const [error, results] = await backupSitesModels.list();
  if (error) return console.error(error);

  const sortedResults = results.sort((a, b) => a.name.localeCompare(b.name));

  const sitesWithDetails = [];
  for (const result of sortedResults) {
    // have to make it a reactive object as we manipulate property objects
    const site = reactive(result);
    site.status = { busy: true, state: '', message: '' };
    site.task = null;
    site.taskLoaded = false;

    // do not wait for it
    refreshStatusForSite(site);
    refreshTaskForSite(site);

    sitesWithDetails.push(site);
  }

  sites.value = sitesWithDetails;
  busy.value = false;
}

onMounted(async () => {
  const [error, result] = await appsModel.list();
  if (error) return console.error(error);
  allApps.value = {};
  result.forEach(app => allApps[app.id] = app);

  await refresh();
});

</script>

<template>
  <div class="content">
    <InputDialog ref="inputDialog" />
    <BackupSiteAddDialog ref="backupSiteAddDialog" @success="refresh()"/>
    <BackupSiteContentDialog ref="backupSiteContentDialog" @success="refresh()"/>
    <BackupSiteConfigDialog ref="backupSiteConfigDialog" @success="refresh()"/>
    <BackupSiteScheduleDialog ref="backupSiteScheduleDialog" @success="refresh()"/>

    <Section :title="$t('backup.sites.title')">
      <template #header-buttons>
        <Button v-if="profile.isAtLeastOwner" @click="onAdd()"> {{ $t('main.action.add') }}</Button>
      </template>

      <div>{{ $t('backup.sites.description') }}</div>

      <br/>

      <ProgressBar mode="indeterminate" v-if="busy" slim :show-label="false" />
      <div class="warning-label" style="margin-bottom: 10px;" v-if="!busy && sites.length > 0 && !hasUpdateBackupSite">{{ $t('backup.sites.noAutomaticUpdateBackupWarning') }}</div>
      <div v-if="!busy && sites.length === 0" class="empty-placeholder">{{ $t('backup.sites.emptyPlaceholder') }}</div>
      <div class="backup-site" v-for="site in sites" :key="site.id">
          <div style="display: flex; align-items: start; margin-top: 6px;">
            <StateLED :busy="site.status.busy" :state="site.status.state"/>
          </div>
          <div class="backup-site-details">
            <div style="margin-bottom: 5px; display: flex; justify-content: space-between; align-items: baseline;">
              <div><b style="font-size: 18px">{{ site.name }}</b></div>
              <ActionBar :actions="createActionMenu(site)"/>
            </div>

            <div v-if="site.encrypted">
              <span v-if="site.encryptedFilenames">{{ $t('backups.useFileAndFileNameEncryption') }}</span>
              <span v-else>{{ $t('backups.useFileEncryption') }}</span>
              <i class="fa-solid fa-lock"></i>
            </div>

            <div>
              <b>Storage:</b> {{ site.provider }} ({{ site.format }})
              <span>at {{ site.locationLabel }}</span>
            </div>

            <div>
              <b>Content:</b> <span v-html="prettyBackupContents(site.contents)"></span>
            </div>
            <div>
              <b>{{ $t('backups.configureBackupStorage.automaticUpdates.title') }}:</b> {{ site.enableForUpdates ? $t('main.dialog.yes') : $t('main.dialog.no') }}
            </div>

            <div>
              <b>{{ $t('backups.schedule.schedule') }}:</b> {{ prettySchedule(site.schedule) }}
            </div>
            <div>
              <b>{{ $t('backups.schedule.retentionPolicy') }}:</b> {{ prettyBackupRetention(site.retention) }}
            </div>
            <div class="backup-site-task">
              <div v-if="!site.task">
                <b>{{ $t('backup.sites.lastRun') }}:</b>
                <span v-if="site.taskLoaded"> Never</span>
                <span v-else> ...</span>
              </div>
              <div v-if="site.task && site.task.success"><b>{{ $t('backup.sites.lastRun') }}:</b> {{ prettyLongDate(site.task.ts) }}</div>
              <div v-if="site.task && site.task.error">
                <b>{{ $t('backup.sites.lastRun') }}:</b> {{ prettyLongDate(site.task.ts) }}
                <div style="margin-top: 5px">
                  <a :href="`/logs.html?taskId=${site.task.id}`" target="_blank"><span class="error-label">{{ site.task.error.message }} <Button small plain tool>Logs</Button></span></a>
                </div>
              </div>
              <div style="margin-top: 10px;" class="text-danger" v-if="site.status.message" v-html="site.status.message"></div>
              <div v-if="site.task && site.task.running" style="margin-top: 10px; display: grid; grid-template-columns: 1fr auto auto; column-gap: 10px; align-items: center;">
                <div style="overflow: hidden;">
                  <ProgressBar :busy="true" :show-label="false" :value="site.task.percent" :mode="site.task.percent <= 0 ? 'indeterminate' : null" />
                </div>
                <Button plain tool :href="`/logs.html?taskId=${site.task.id}`" target="_blank">Logs</Button>
                <Button danger plain tool icon="fa-solid fa-xmark" @click="onCancelTask(site.task.id)"></Button>
                <div style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden;">{{ site.task.percent }}% {{ site.task.message }}</div>
              </div>
            </div>
          </div>
      </div>
    </Section>

    <SystemBackupList ref="systemBackupList"/>
  </div>
</template>

<style scoped>

/* should match pankow-table-placeholder */
.empty-placeholder {
  text-align: center;
  margin-top: 60px;
}

.backup-site {
  display: flex;
  border-radius: var(--pankow-border-radius);
  padding: 10px;
  gap: 10px;
  margin-bottom: 10px;
  justify-content: space-between;
}

.backup-site-details {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex-grow: 1;
  overflow: hidden;
}

.backup-site-action {
  display: flex;
  justify-content: center;
  flex-direction: column;
}
</style>