<template>
  <div>
    <div v-if="!allDocumentsSigned && !docsAreAttached">
      <q-file 
        class="q-mb-md" 
        :label="$t('labelAgreementFile')" 
        outlined accept=".pdf" 
        :value="agreementFileModel"
        @input="(f) => handleFile(f, 'agreementFileModel')" 
      />
      <q-file 
        class="q-mb-md" 
        :label="$t('labelInputFile')" 
        outlined accept=".pdf" 
        :value="statementFileModel"
        @input="(f) => handleFile(f, 'statementFileModel')" 
      />
    </div>

    <div v-if="statementFileModel && agreementFileModel" class="q-gutter-md">
      <q-btn
        v-if="!docsAreAttached"
        unelevated
        @click="attachDocs"
        color="secondary"
        :label="$t('attachDocumentsWithoutSignature')"
        class="q-mb-md"
      />
      <q-btn
        v-if="!docsAreAttached"
        unelevated
        @click="openCertificateDialog"
        color="secondary"
        :label="$t('signatureDocuments')"
        class="q-mb-md"
      />
    </div>

    <div class="q-mb-lg row items-start q-gutter-md" v-if="files && files.length">
        <q-card bordered class="document-card" v-for="file in files || []" :key="file.id">
          <q-card-section class="entry header text-h6">
            {{
                file.type.data.type === 'document'
                  ? $t('document')
                  : $t('signature')
            }}
          </q-card-section>

          <q-separator inset />

          <q-card-section class="entry">
            <div class="grid-item-label text-weight-medium text-grey-9">
              {{ $t('typeFile') }}
            </div>
            <div class="grid-item-value">
              {{ file.typeDocument.label }}
            </div>
          </q-card-section>

          <q-card-section class="entry">
            <div class="grid-item-label text-weight-medium text-grey-9">
              {{ $t('nameFile') }}
            </div>
            <div class="grid-item-value">
              {{ file.file.name.slice(0, 30) }}
              <span v-show="file.file.name.length > 30">...</span>
              <q-tooltip :content-style="{ 'font-size': '12px' }">
                {{ file.file.name }}
              </q-tooltip>
            </div>
          </q-card-section>

          <q-card-actions align="right">
            <q-btn @click="removeFile(file)" text-color="negative">
              {{ $t('base.delete') }}
            </q-btn>
          </q-card-actions>
        </q-card>
    </div>

    <q-inner-loading :showing="loading">
      <q-spinner-gears color="primary" size="50px" />
    </q-inner-loading>

    <q-dialog v-model="openDialog" :persistent="certificateLoading">
      <q-card class="full-width no-shadow" style="max-width: 580px">
        <q-card-section>
          <div class="text-h6">
            {{ $t('signatureDocuments') }}
          </div>
        </q-card-section>

        <q-card-section class="q-gutter-y-md">
          <q-select v-model="certificate" :disable="!certificates.length" :options="certificates" filled
            :label="$t('certificate')">
            <template #option="{ opt, toggleOption, selected }">
              <q-item @click="toggleOption(opt)" :class="{
                'bg-red-1': isExpired(opt.data.validTo),
                'bg-blue-1': selected,
              }" clickable>
                <div>
                  <div class="text-grey-8">{{ opt.label }}</div>
                  <div :style="{ fontSize: '0.75rem' }">
                    {{ $t('certificateValidTo') }}:
                    {{ convertDateFromBackendFormat(opt.data.validTo) }}
                  </div>
                </div>
              </q-item>
            </template>
          </q-select>
        </q-card-section>

        <q-card-actions align="right">
          <q-btn 
            :disable="!certificate" 
            :loading="certificateLoading" 
            :label="$t('selectCertificate')" 
            unelevated
            color="primary"
            @click="signDocuments"
          >
            <template #loading>
              <q-spinner-gears />
            </template>
          </q-btn>
        </q-card-actions>

        <q-inner-loading :showing="certificateLoading">
          <q-spinner-gears color="primary" size="50px" />
        </q-inner-loading>
      </q-card>
    </q-dialog>
  </div>
</template>

<script lang="ts">
import { Vue, Component, Watch, Prop } from 'vue-property-decorator';
import get from 'lodash/get';
import { v4 } from 'uuid';
import { notifyError, notifySuccess } from '@utils/notify';
import { convertDateFromBackendFormat, isPastDate, DateFormat } from '@utils/format-date';
import { stampPdf } from '@/utils/statement-print';
import cloneDeep from 'lodash/cloneDeep';

type FileModel = {
  id: string;
  type: {
    data: {
      type:'document' | 'sign',
      code: string
  }};
  file: File;
  typeDocument: string;
};

type Certificate = {
  label: string;
  value: any;
  data: any;
};

const statementDocumentCode = 'ApplicationPrint';
const agreementDocumentCode = 'PersonalInfoReception';

@Component({})
export default class OperatorStatementSign extends Vue {
  @Prop({ default: false }) hasStatementBeenDownloaded: boolean;

  loading = false;

  modelForm: any = {
    employee: {
      officeName: '',
      officeAdress: '',
      fio: '',
      position: '',
    },
  };
  statementFileModel: File | null = null;
  agreementFileModel: File | null = null;
  files: FileModel[] = [];
  documentsToSubmit: FileModel[] = [];

  certificates: Certificate[] = [];
  certificate: Certificate | null = null;
  certificateLoading = false;

  openDialog = false;
  base64 = null;

  @Watch('files', { deep: true })
  onFilesChange(files: FileModel[]) {
    if (!files.length) {
      this.setStatementDocuments([]);
    }
  }

  get statementDocuments() {
    return this.$store.getters['formGenerator/statementDocuments'];
  }

  @Watch('statementDocuments', { deep: true, immediate: true })
  onStatementDocumentsChange(docs: FileModel[]) {
    if (docs.length && !this.files.length) {
      this.$set(this, 'files', cloneDeep(docs));
    }
  }

  get allDocumentsSigned(): boolean {
    return this.statementDocuments.length === 4;
  }

  get filesByDocuments() {
    return this.files.filter((v) => v.type.data.type === 'document');
  }

  get filesBySign() {
    return this.files.filter((v) => v.type.data.type === 'sign');
  }

  get docsAreAttached() {
    return this.files.some((f: FileModel) => f.type.data.code === agreementDocumentCode) && 
     this.files.some((f: FileModel) => f.type.data.code === statementDocumentCode);
  }

  signedDocumentCode(documentCode: string): string {
    return documentCode + 'Sig';
  }

  async generateBase64BySelectedCertificate() {
    if (!this.certificate) {
      return notifyError(this.$t('isNotValid').toString());
    }
    this.loading = true;
    const isValid = await this.$store
      .dispatch(
        'utils/cryptoPro/isValidSystemCertificate',
        this.certificate?.value
      )
      .catch((e) => {
        const message = get(e, 'message');
        if (message) {
          notifyError(message);
        } else {
          notifyError(this.$t('statements.SignDialog.cryptoProIncorrectlyConfigured').toString());
        }
        this.loading = false;
      });
    if (typeof isValid !== 'undefined' ? isValid : false) {
      const base64 = await this.$store
        .dispatch('utils/cryptoPro/generateBase64ByCertificate', {
          certificate: this.certificate?.value,
        })
        .catch((e) => notifyError(e));

      this.openDialog = false;
      this.loading = false;

      return base64;
    }
    return null;
  }

  async signDocuments() {
    if (!this.agreementFileModel || !this.statementFileModel) {
      return;
    }

    this.base64 = await this.generateBase64BySelectedCertificate();

    const stampedAgreement = await stampPdf(this.agreementFileModel, this.base64);
    const stampedStatement = await stampPdf(this.statementFileModel, this.base64);

    this.addFileDocument(stampedAgreement, this.agreementFileModel.name, agreementDocumentCode);
    this.agreementFileModel = null;
    
    this.addFileDocument(stampedStatement, this.statementFileModel.name, statementDocumentCode);
    this.statementFileModel = null;

    await this.onSignatureDocuments();

    this.setStatementDocuments(this.files);
  }

  handleFile(file: File, model: string) {
    const { size } = file;

    if (!size || size / 1024 / 1024 > 10) {
      return notifyError(this.$t('errorSizeFile').toString());
    }

    this.$set(this, model, file);
  }

  addFileDocument(fileModel: Blob | File, filename: string, documentCode: string) {
    const file = filename ? new File([fileModel], filename) : fileModel as File;
    const id = v4();

    this.files.push({
      id,
      type: {data: {type: 'document', code: documentCode}},
      file,
      typeDocument: 'Заявление',
    });
  }

  removeFile(file: FileModel) {
    this.files = this.files.filter((v) => v.id !== file.id);

    if (file.type.data.type === 'document' && !this.filesByDocuments.length) {
      this.files = [];
    }
  }

  convertDateFromBackendFormat(date: any, dateFormat?: string) {
    return convertDateFromBackendFormat(date, dateFormat);
  }

  isExpired(date: string): boolean {
    return isPastDate(date);
  }

  async openCertificateDialog() {
    this.certificate = null;

    try {
      const isValid = await this.$store.dispatch(
        'utils/cryptoPro/isValidSystemSetup'
      );

      if (!isValid) {
        throw new Error(this.$t('cryptoProIncorrectlyConfigured').toString());
      }

      const certificates = await this.$store.dispatch(
        'utils/cryptoPro/fetchCertificates'
      );

      this.certificates = certificates.map((certificate: any) => ({
        label: certificate.subjectName,
        value: certificate,
        data: certificate,
      }));

      this.openDialog = true;
    } catch (error: any) {
      const message = get(error, 'message');

      if (message) {
        notifyError(message);
        return;
      }

      notifyError(error);
    }
  }

  async onSignatureDocument(context: FileModel) {
    if (!this.certificate?.value) return;

    try {
      const hash = await this.$store.dispatch('utils/cryptoPro/generateHash', {
        file: context.file,
      });

      const signature = await this.$store.dispatch(
        'utils/cryptoPro/createSignature',
        {
          hash,
          certificate: this.certificate.value,
        }
      );

      const blob = new Blob([signature], {
        type: 'text/plain',
      });

      const file = new File([blob], `${context.file.name}.sig`);
      const id = v4();

      const fileModel: FileModel = {
        id,
        type: {data: {type: 'sign', code: this.signedDocumentCode(context.type.data.code)}},
        file,
        typeDocument: context.typeDocument,
      };

      return fileModel;
    } catch (error: any) {
      const message = get(error, 'message');

      if (message) {
        notifyError(message);
        return;
      }

      notifyError(error);
    }
  }

  async onSignatureDocuments() {
    if (!this.certificate?.value) return notifyError(this.$t('isNotValid').toString());

    this.certificateLoading = true;

    try {
      const isValid = await this.$store.dispatch(
        'utils/cryptoPro/isValidSystemCertificate',
        this.certificate.value
      );

      if (!isValid) {
        throw new Error(this.$t('isNotValid').toString());
      }

      const files = await Promise.all(
        this.filesByDocuments.map((context) =>
          this.onSignatureDocument(context)
        )
      );

      (files || []).forEach((file) => {
        if (!file) return;
        this.files.push(file);
      });

      notifySuccess(this.$t('cryptoProSuccess').toString());
      this.openDialog = false;
    } catch (error: any) {
      const message = get(error, 'message');

      if (message) {
        notifyError(message);
        return;
      }

      notifyError(error);
    } finally {
      this.certificateLoading = false;
    }
  }

  setStatementDocuments(data: FileModel[]): void {
    this.$store.commit('formGenerator/setStatementDocuments', data);
  }

  attachDocs(): void {
    if (!this.statementFileModel || !this.agreementFileModel) {
      return;
    }

    this.addFileDocument(this.statementFileModel, '', statementDocumentCode); 
    this.statementFileModel = null;
    
    this.addFileDocument(this.agreementFileModel, '', agreementDocumentCode);
    this.agreementFileModel = null;

    this.setStatementDocuments(this.files);
  }
}
</script>

<style lang="scss">
.file-cell {
  white-space: pre-line;
}

.document-card {
  width: 100%;
  max-width: 350px;
}

.grid-item-value {
  overflow: hidden;
  text-overflow: ellipsis;
}
</style>

<i18n>
{
  "en": {
    "document": "Document",
    "signature": "Sign",
    "typeFile": "File type",
    "nameFile": "File name",
   "signatureDocuments": "To sign document",
   "certificate": "Сertificate",
   "selectCertificate": "Select",
   "certificateValidTo": "Up to",
   "cryptoProIncorrectlyConfigured": "The CryptoPro program is incorrectly configured or the browser plug-in is not installed.",
   "isNotValid": "Signature generation error. Invalid certificate.",
   "cryptoProSuccess": "Success",
   "sendSuccess": "Success",
   "notSelectedDocumentType": "Not selected document type",
   "labelInputFile": "Attach statement scan. *.pdf up to 10 Mb",
   "labelAgreementFile": "Attach agreement scan. *.pdf up to 10 Mb",
   "errorSizeFile": "Maximum file size exceeded - 10 MB",
   "downloadStatement": "Download statement",
   "attachDocumentsWithoutSignature": "Attach documents without signature"
  },
  "ru": {
   "document": "Документ",
   "signature": "Подпись",
   "typeFile": "Тип файла",
   "nameFile": "Название файла",
   "signatureDocuments": "Подписать документы",
   "certificate": "Сертификат",
   "selectCertificate": "Выбрать сертификат",
   "certificateValidTo": "Действителен до",
   "cryptoProIncorrectlyConfigured": "Неверно настроена программа \"КриптоПро\" или не установлен плагин для браузера.",
   "isNotValid": "Ошибка формирования подписи. Невалидный сертификат или лицензия.",
   "cryptoProSuccess": "Документы подписаны",
   "sendSuccess": "Файл отправлен",
   "notSelectedDocumentType": "Не выбран тип документа",
   "labelInputFile": "Прикрепить скан заявления. *.pdf до 10 Мб",
   "labelAgreementFile": "Прикрепить скан согласия. *.pdf до 10 Мб",
   "errorSizeFile": "Превышен максимальный объем файла - 10 Мб",
   "downloadStatement": "Скачать заявление",
   "attachDocumentsWithoutSignature": "Прикрепить документы без подписи"
  }
}
</i18n>