// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

<template>
  <div>
    <a-button
      type="dashed"
      style="width: 100%; margin-bottom: 10px"
      @click="showAddModal"
      :loading="loading"
      :disabled="!('createVMSchedule' in $store.getters.apis)"
    >
      <template #icon><plus-outlined /></template> {{ $t('label.schedule.add') }}
    </a-button>
    <list-view
      :loading="tabLoading"
      :columns="columns"
      :items="schedules"
      :columnKeys="columnKeys"
      :selectedColumns="selectedColumnKeys"
      ref="listview"
      @update-selected-columns="updateSelectedColumns"
      @update-vm-schedule="updateVMSchedule"
      @remove-vm-schedule="removeVMSchedule"
      @refresh="this.fetchData"
    />
    <a-pagination
      class="row-element"
      style="margin-top: 10px"
      size="small"
      :current="page"
      :pageSize="pageSize"
      :total="totalCount"
      :showTotal="total => `${$t('label.showing')} ${Math.min(total, 1 + ((page - 1) * pageSize))}-${Math.min(page * pageSize, total)} ${$t('label.of')} ${total} ${$t('label.items')}`"
      :pageSizeOptions="pageSizeOptions"
      @change="changePage"
      @showSizeChange="changePage"
      showSizeChanger
      showQuickJumper
    >
      <template #buildOptionText="props">
        <span>{{ props.value }} / {{ $t('label.page') }}</span>
      </template>
    </a-pagination>
  </div>

  <a-modal
    :visible="showModal"
    :title="$t('label.schedule')"
    :maskClosable="false"
    :closable="true"
    @cancel="closeModal"
    @ok="submitForm"
  >
    <a-form
      layout="vertical"
      :ref="formRef"
      :model="form"
      :rules="rules"
      @finish="submitForm"
      v-ctrl-enter="submitForm"
    >
      <a-form-item
        name="description"
        ref="description"
        :wrapperCol="{ span: 24 }"
      >
        <template #label>
          <tooltip-label
            :title="$t('label.description')"
            :tooltip="apiParams.description.description"
          />
        </template>
        <a-input
          v-model:value="form.description"
          v-focus="true"
        />
      </a-form-item>
      <a-form-item
        name="action"
        ref="action"
        :wrapperCol="{ span: 24 }"
      >
        <template #label>
          <tooltip-label
            :title="$t('label.action')"
            :tooltip="apiParams.action.description"
          />
        </template>
        <a-radio-group
          v-model:value="form.action"
          button-style="solid"
          :disabled="isEdit"
        >
          <a-radio-button
            v-for="action in actions"
            :key="action.id"
            :value="action.value"
          >
            {{ $t(action.label) }}
          </a-radio-button>
        </a-radio-group>
      </a-form-item>
      <a-form-item
        name="timezone"
        ref="timezone"
        :wrapperCol="{ span: 24 }"
      >
        <template #label>
          <tooltip-label
            :title="$t('label.timezone')"
            :tooltip="apiParams.timezone.description"
          />
        </template>
        <a-select
          showSearch
          v-model:value="form.timezone"
          optionFilterProp="label"
          :filterOption="(input, option) => {
            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
          }"
          :loading="fetching"
        >
          <a-select-option
            v-for="opt in timeZoneMap"
            :key="opt.id"
            :label="opt.name || opt.description"
          >
            {{ opt.name || opt.description }}
          </a-select-option>
        </a-select>
      </a-form-item>
      <a-row justify="space-between">
        <a-col>
          <a-form-item
            name="startDate"
            ref="startDate"
          >
            <template #label>
              <tooltip-label
                :title="$t('label.start.date.and.time')"
                :tooltip="apiParams.startdate.description"
              />
            </template>
            <a-date-picker
              v-model:value="form.startDate"
              show-time
              :locale="this.$i18n.locale"
              :placeholder="$t('message.select.start.date.and.time')"
            />
          </a-form-item>
        </a-col>
        <a-col>
          <a-form-item
            name="endDate"
            ref="endDate"
          >
            <template #label>
              <tooltip-label
                :title="$t('label.end.date.and.time')"
                :tooltip="apiParams.enddate.description"
              />
            </template>
            <a-date-picker
              v-model:value="form.endDate"
              show-time
              :locale="this.$i18n.locale"
              :placeholder="$t('message.select.end.date.and.time')"
            />
          </a-form-item>
        </a-col>
      </a-row>
      <a-form-item
        name="schedule"
        ref="schedule"
        :wrapperCol="{ span: 24 }"
      >
        <template #label>
          <tooltip-label
            :title="$t('label.schedule')"
            :tooltip="apiParams.schedule.description"
          />
        </template>
        <a-row
          style="margin-bottom: 15px; text-align: center;"
          justify="space-around"
          align="middle"
        >
          <cron-ant
            v-if="!form.useCronFormat"
            v-model="form.schedule"
            :periods="periods"
            :button-props="{ type: 'primary', size: 'small', disabled: form.useCronFormat }"
            @error="error = $event"
          />
          <label
            v-if="form.useCronFormat">
            {{ generateHumanReadableSchedule(form.schedule) }}
          </label>
        </a-row>
        <a-row
          justify="space-between"
          align="middle"
        >
          <a-col>
            <label>{{ $t('label.cron.mode') }}</label>
          </a-col>
          <a-col>
            <a-switch v-model:checked="form.useCronFormat">
            </a-switch>
          </a-col>
          <a-col :span="12">
            <a-input
              :addonBefore="$t('label.cron')"
              v-model:value="form.schedule"
              :disabled="!form.useCronFormat"
              v-focus="true"
            />
          </a-col>
        </a-row>
      </a-form-item>
      <a-form-item
        name="enabled"
        ref="enabled"
        :wrapperCol="{ span: 24}"
      >
        <template #label>
          <tooltip-label
            :title="$t('label.enabled')"
            :tooltip="apiParams.enabled.description"
          />
        </template>
        <a-switch v-model:checked="form.enabled" />
      </a-form-item>
    </a-form>
  </a-modal>
</template>

<script>

import { reactive, ref, toRaw } from 'vue'
import { api } from '@/api'
import ListView from '@/components/view/ListView'
import Status from '@/components/widgets/Status'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import { mixinForm } from '@/utils/mixin'
import { timeZone } from '@/utils/timezone'
import debounce from 'lodash/debounce'
import cronstrue from 'cronstrue/i18n'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'

dayjs.extend(utc)
dayjs.extend(timezone)

export default {
  name: 'InstanceSchedules',
  mixins: [mixinForm],
  components: {
    Status,
    ListView,
    TooltipLabel
  },
  props: {
    virtualmachine: {
      type: Object,
      required: true
    },
    loading: {
      type: Boolean,
      required: true
    }
  },
  data () {
    this.fetchTimeZone = debounce(this.fetchTimeZone, 800)
    return {
      tabLoading: false,
      columnKeys: ['action', 'enabled', 'description', 'schedule', 'timezone', 'startdate', 'enddate', 'created', 'vmScheduleActions'],
      selectedColumnKeys: [],
      columns: [],
      schedules: [],
      timeZoneMap: [],
      actions: [
        { value: 'START', label: 'label.start' },
        { value: 'STOP', label: 'label.stop' },
        { value: 'REBOOT', label: 'label.reboot' },
        { value: 'FORCE_STOP', label: 'label.force.stop' },
        { value: 'FORCE_REBOOT', label: 'label.force.reboot' }
      ],
      periods: [
        { id: 'year', value: ['month', 'day', 'dayOfWeek', 'hour', 'minute'] },
        { id: 'month', value: ['day', 'dayOfWeek', 'hour', 'minute'] },
        { id: 'week', value: ['dayOfWeek', 'hour', 'minute'] },
        { id: 'day', value: ['hour', 'minute'] }
      ],
      page: 1,
      pageSize: 20,
      totalCount: 0,
      showModal: false,
      isSubmitted: false,
      isEdit: false,
      error: '',
      pattern: 'YYYY-MM-DD HH:mm:ss'
    }
  },
  beforeCreate () {
    this.apiParams = this.$getApiParams('createVMSchedule')
  },
  computed: {
    pageSizeOptions () {
      var sizes = [20, 50, 100, 200, this.$store.getters.defaultListViewPageSize]
      if (this.device !== 'desktop') {
        sizes.unshift(10)
      }
      return [...new Set(sizes)].sort(function (a, b) {
        return a - b
      }).map(String)
    }
  },
  created () {
    this.selectedColumnKeys = this.columnKeys
    this.updateColumns()
    this.pageSize = this.pageSizeOptions[0] * 1
    this.initForm()
    this.fetchData()
    this.fetchTimeZone()
  },
  watch: {
    virtualmachine: {
      handler () {
        this.fetchSchedules()
      }
    }
  },
  methods: {
    initForm () {
      this.formRef = ref()
      this.form = reactive({
        action: 'START',
        schedule: '* * * * *',
        description: '',
        timezone: 'UTC',
        startDate: '',
        endDate: '',
        enabled: true,
        useCronFormat: false
      })
      this.rules = reactive({
        schedule: [{ type: 'string', required: true, message: this.$t('message.error.required.input') }],
        action: [{ type: 'string', required: true, message: this.$t('message.error.required.input') }],
        timezone: [{ required: true, message: `${this.$t('message.error.select')}` }],
        startDate: [{ required: false, message: `${this.$t('message.error.select')}` }],
        endDate: [{ required: false, message: `${this.$t('message.error.select')}` }]
      })
    },
    createVMSchedule (schedule) {
      this.resetForm()
      this.showAddModal()
    },
    removeVMSchedule (schedule) {
      api('deleteVMSchedule', {
        id: schedule.id,
        virtualmachineid: this.virtualmachine.id
      }).then(() => {
        if (this.totalCount - 1 === this.pageSize * (this.page - 1)) {
          this.page = this.page - 1 > 0 ? this.page - 1 : 1
        }
        const message = `${this.$t('label.removing')} ${schedule.description}`
        this.$message.success(message)
      }).catch(error => {
        console.error(error)
        this.$message.error(this.$t('message.error.remove.vm.schedule'))
        this.$notification.error({
          message: this.$t('label.error'),
          description: this.$t('message.error.remove.vm.schedule')
        })
      }).finally(() => {
        this.fetchData()
      })
    },
    updateVMSchedule (schedule) {
      this.resetForm()
      this.isEdit = true
      Object.assign(this.form, schedule)
      // Some weird issue when we directly pass in the moment with tz object
      this.form.startDate = dayjs(schedule.startdate).tz(schedule.timezone)
      this.form.endDate = schedule.enddate ? dayjs(dayjs(schedule.enddate).tz(schedule.timezone)) : null
      this.showAddModal()
    },
    showAddModal () {
      this.showModal = true
    },
    submitForm () {
      if (this.isSubmitted) return
      this.isSubmitted = true
      this.formRef.value.validate().then(() => {
        const formRaw = toRaw(this.form)
        const values = this.handleRemoveFields(formRaw)
        var params = {
          description: values.description,
          schedule: values.schedule,
          timezone: values.timezone,
          action: values.action,
          virtualmachineid: this.virtualmachine.id,
          enabled: values.enabled,
          startdate: (values.startDate) ? values.startDate.format(this.pattern) : null,
          enddate: (values.endDate) ? values.endDate.format(this.pattern) : null
        }
        let command = null
        if (this.form.id === null || this.form.id === undefined) {
          command = 'createVMSchedule'
        } else {
          params.id = this.form.id
          command = 'updateVMSchedule'
        }

        api(command, params).then(response => {
          this.$notification.success({
            message: this.$t('label.schedule'),
            description: this.$t('message.success.config.vm.schedule')
          })
          this.isSubmitted = false
          this.fetchData()
          this.closeModal()
        }).catch(error => {
          this.$notifyError(error)
          this.isSubmitted = false
        })
      }).catch(error => {
        this.$notifyError(error)
        if (error.errorFields !== undefined) {
          this.formRef.value.scrollToField(error.errorFields[0].name)
        }
      }).finally(() => {
        this.isSubmitted = false
      })
    },
    resetForm () {
      this.isEdit = false
      if (this.formRef.value) {
        this.formRef.value.resetFields()
      }
    },
    fetchTimeZone (value) {
      this.timeZoneMap = []
      this.fetching = true

      timeZone(value).then(json => {
        this.timeZoneMap = json
        this.fetching = false
      })
    },
    closeModal () {
      this.resetForm()
      this.initForm()
      this.showModal = false
    },
    fetchData () {
      this.fetchSchedules()
    },
    fetchSchedules () {
      this.schedules = []
      if (!this.virtualmachine.id) {
        return
      }
      const params = {
        page: this.page,
        pagesize: this.pageSize,
        virtualmachineid: this.virtualmachine.id,
        listall: true
      }
      this.tabLoading = true
      api('listVMSchedule', params).then(json => {
        this.schedules = []
        this.totalCount = json?.listvmscheduleresponse?.count || 0
        this.schedules = json?.listvmscheduleresponse?.vmschedule || []
        this.tabLoading = false
      })
    },
    changePage (page, pageSize) {
      this.page = page
      this.pageSize = pageSize
      this.fetchData()
    },
    updateSelectedColumns (key) {
      if (this.selectedColumnKeys.includes(key)) {
        this.selectedColumnKeys = this.selectedColumnKeys.filter(x => x !== key)
      } else {
        this.selectedColumnKeys.push(key)
      }
      this.updateColumns()
    },
    generateHumanReadableSchedule (schedule) {
      return cronstrue.toString(schedule, { locale: this.$i18n.locale, throwExceptionOnParseError: false, verbose: true })
    },
    updateColumns () {
      this.columns = []
      for (var columnKey of this.columnKeys) {
        if (!this.selectedColumnKeys.includes(columnKey)) continue
        this.columns.push({
          key: columnKey,
          // If columnKey is 'enabled', then title is 'state'
          // If columnKey is 'startdate', then the title is `start.date.and.time`
          // else title is columnKey
          title: columnKey === 'enabled'
            ? this.$t('label.state')
            : columnKey === 'startdate'
              ? this.$t('label.start.date.and.time')
              : columnKey === 'enddate'
                ? this.$t('label.end.date.and.time')
                : this.$t('label.' + String(columnKey).toLowerCase()),
          dataIndex: columnKey
        })
      }
      if (this.columns.length > 0) {
        this.columns[this.columns.length - 1].customFilterDropdown = true
      }
    }
  }
}
</script>
