<template>
  <div class="q-pa-md q-gutter-y-md">
    <div
      class="flex justify-between items-center bg-white q-pa-md rounded-borders"
    >
      <div class="column full-width">
        <div>
          <div
            class="text-h6 flex items-center q-gutter-y-md text-grey-9 col q-pr-md"
          >
            <q-btn
              :loading="loading"
              padding="xs"
              class="text-grey-7 q-mr-md"
              color="grey-3"
              icon="arrow_back"
              @click="returnToParentView(parentRoute)"
            />
            <div>
              <div class="text-h5">{{ title }}</div>
              <div class="text-caption">
                {{ $t('registries.registryOrder.subtitle') }}
              </div>
            </div>
            <q-space />
            <div class="row items-center q-gutter-x-md q-gutter-y-sm">
              <q-btn
                v-if="$AC.haveOneOfScope([USER_SCOPES.ORDER_ADMIN])"
                :label="$t('registries.unloadingList.orderGenerateBtn')"
                color="primary"
                :loading="loading"
                @click="generateAnOrder"
              />
              <q-btn
                color="primary"
                :label="$t('registries.unloadingList.archiveDownloadBtn')"
                @click="exportRecords"
                :loading="loading"
              />
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="bg-white rounded-borders">
      <BaseTable
        :columns="columns"
        :data="data"
        :selected.sync="selected"
        :pagination.sync="pagination"
        :loading="tableLoading"
        @update:selected="selectionHandler"
        @update="updateTableData"
        @requestTable="updateTableData"
      >
        <template #search>
          <SearchBar
            :loading="loading"
            :show-search-button="true"
            :statusOptions="statusOptions"
            @query="search"
          />
        </template>
        <template v-slot:body-item-selection="scope">
          <q-checkbox
            :disable="!scope.props.row.signed && !scope.props.row.cancelDate"
            v-model="scope.props.selected"
          />
        </template>
        <template v-slot:body-item-sign="props">
          <div class="row no-wrap text-no-wrap float-right">
            <q-btn
              flat
              dense
              icon="info"
              size="16"
              style="color: rgba(0, 0, 0, 0.54)"
              color="primary"
              @click="showInfo(props.row)"
            >
              <q-tooltip>
                {{ $t('registries.objectCard.annulInfo') }}
              </q-tooltip>
            </q-btn>

            <q-btn
              :disable="!hasFilesForSign(props.row)"
              flat
              dense
              icon="arrow_downward"
              size="16"
              style="color: rgba(0, 0, 0, 0.54)"
              color="primary"
              @click="fetchDocumentAttachment(props.row)"
            >
              <q-tooltip>
                {{ $t('registries.objectCard.download') }}
              </q-tooltip>
            </q-btn>

            <q-btn
              v-if="props.row.signed"
              :disable="
                !$AC.haveOneOfScope([
                  USER_SCOPES.ORDER_OPERATOR,
                  USER_SCOPES.ORDER_ADMIN,
                ]) || !get(props.row, 'certificate')
              "
              flat
              dense
              icon="gesture"
              size="16"
              style="color: rgba(0, 0, 0, 0.54)"
              color="positive"
              @click="getSignInfo(props.row)"
            >
              <q-tooltip>
                {{ $t('registries.objectCard.viewingEDSData') }}
              </q-tooltip>
            </q-btn>
            <q-btn
              v-else
              :disable="
                !$AC.haveScope(USER_SCOPES.ORDER_ADMIN) ||
                !hasFilesForSign(props.row) ||
                !!get(props.row, 'cancelDate')
              "
              flat
              dense
              icon="gesture"
              size="16"
              style="color: rgba(0, 0, 0, 0.54)"
              color="primary"
              @click="
                addSign({
                  registryOrderCode: code,
                  exportId: props.row.id,
                  documents: get(props.row, 'filesForSign'),
                })
              "
            >
              <q-tooltip>
                {{ $t('registries.objectCard.addEDS') }}
              </q-tooltip>
            </q-btn>
            <AnnulUnloading
              v-if="$AC.haveOneOfScope([USER_SCOPES.ORDER_ADMIN])"
              @update="updateTableData"
              :registry="props.row"
              :registryCode="registry && registry.code"
              :user-id="userId"
              process-key="RegistryOrderCancelling"
            />
          </div>
        </template>
      </BaseTable>
      <UploadCertDialog
        process-key="StampOrderProcess"
        :openDialog.sync="signModal"
        :model="dataForSigner"
        :parent-process="process"
        @signed="onSigned"
      />
      <SignInfoDialog
        :openDialog.sync="signInfoModal"
        :model="currentItem"
        @listRefresh="updateTableData"
      />
      <InfoDialog
        :openDialog.sync="infoModal"
        :model="currentItem"
        :hide-nulls="true"
        :padded-groups="true"
        form-key="OrderInfo"
      />
      <a ref="export" />
    </div>
  </div>
</template>

<script lang="ts">
import { Component } from 'vue-property-decorator';
import { mixins } from 'vue-class-component';
import ReturnToParentView from '@mixins/ReturnToParentView';
import {
  ExportRegistryKompensDetskOtdMIR,
  RegistryApi,
} from '@openapi/registry-data/api';
import { convertDateFromTimestamp } from '@utils/format-date';
import { DateFormat } from '@constants/utils/format-date';
import QColumn from '@/types/QColumn';
import { Route } from 'vue-router/types';
import { TablePagination } from '@models/base/TablePagination';
import UploadCertDialog from '@components/common/base-cert-uploader/dialog.vue';
import SignInfoDialog from '@components/registries/sign_info_dialog.vue';
import InfoDialog from '@components/registries/info_dialog.vue';
import S3UrlSignerFilesService from '@services/s3urlsigner/files';
import { saveAs } from 'file-saver';
import SearchBar from '@components/registries/base-search-bar.vue';
import { USER_SCOPES } from '@constants/entities/Scope';
import get from 'lodash/get';
import SearchQuery from '@interfaces/entities/SearchQuery';
import axios from 'axios';
import AnnulUnloading from '@components/registries/annul_unloading.vue';
import { notifyError, notifySuccess } from '@utils/notify';
import { DocumentApi } from '@openapi/registry-data';
import {
  RegistryOrderApi,
  RegistryOrder,
  Page,
ServiceDocumentApi,
} from '@/openapi/registry-service';
import { searchByField } from '@/utils/query';
import { has, isEmpty, last } from 'lodash';

const REGISTRIES_LIST_URL = '/registries/order';
const CHILDRENS_REST_CASHBACK_CODE = 'RegistryKompensDetskOtdMIR';

@Component({
  components: {
    AnnulUnloading,
    SearchBar,
    UploadCertDialog,
    SignInfoDialog,
    InfoDialog,
  },
  methods: {
    get,
  },
})
export default class OrderList extends mixins(ReturnToParentView) {
  get: typeof get;

  selected: Array<ExportRegistryKompensDetskOtdMIR> = [];
  data: Array<ExportRegistryKompensDetskOtdMIR> = [];
  searchQuery: SearchQuery;
  queryString = '';
  fulltextString = '';
  tableLoading = false;
  currentItem: ExportRegistryKompensDetskOtdMIR | null = null;
  dataForSigner: Record<string, any> = {};
  loading = false;
  signModal = false;
  signInfoModal = false;
  infoModal = false;
  sortByDefault = 'created';
  pagination = new TablePagination({
    rowsPerPage: 100,
    descending: true,
    sortBy: 'created',
  });

  title = '';
  code = '';
  registry: RegistryOrder | null = null;

  USER_SCOPES = USER_SCOPES;

  get parentRoute(): { name: string } | string {
    const registryRoutes = this.$router.$history.filter((route: Route) => {
      if (!route.name || route.name === 'registry-order') return false;
      return /(registr(y|ies)|personal-accounts)/.test(route.name);
    });

    if (registryRoutes.length) {
      return registryRoutes[0].path;
    }
    return REGISTRIES_LIST_URL;
  }

  get statusOptions(): Record<string, string>[] {
    return [
      {
        label: 'Выплата сформирована',
        value: 'Выплата сформирована',
      },
      {
        label: 'Распоряжение сформировано',
        value: 'Распоряжение сформировано',
      },
      {
        label: 'Подписано',
        value: 'Подписано',
      },
      {
        label: 'Аннулировано',
        value: 'Аннулировано',
      },
    ];
  }

  get columns(): Array<QColumn> {
    return [
      {
        align: 'left',
        field: 'title',
        label: this.$t('registries.unloadingList.name'),
        name: 'title',
        required: true,
        sortable: true,
      },
      {
        align: 'left',
        label: this.$t('registries.unloadingList.createDate'),
        field: 'created',
        format: (val: any) =>
          val ? convertDateFromTimestamp(val, DateFormat.date) : '',
        name: 'created',
        required: true,
        sortable: true,
      },
      {
        align: 'left',
        label: this.$t('registries.unloadingList.departmentName'),
        field: 'departmentName',
        name: 'departmentName',
        required: true,
        sortable: true,
      },
      {
        align: 'left',
        label: this.$t('registries.unloadingList.state'),
        field: 'state',
        name: 'state',
        required: true,
        sortable: false,
      },
      {
        align: 'left',
        label: this.$t('registries.unloadingList.totalSum'),
        field: 'totalSum',
        format: (val: any) => `${val} ₽`,
        name: 'totalSum',
        required: true,
        sortable: false,
      },
      {
        align: 'left',
        label: this.$t('registries.unloadingList.sign'),
        field: 'sign',
        name: 'sign',
        required: true,
        sortable: false,
      },
    ];
  }

  get userId(): string {
    return this.$store.getters['oidcClient/currentUser'].sub;
  }

  created(): void {
    this.updateTableData();
  }

  async fetchData(): Promise<Page | undefined> {
    const query = this.searchQuery?.query || '';
    const fulltextString = this.searchQuery?.fulltext || '';
    const sortString =
      (this.pagination.sortBy &&
        `${this.pagination.descending ? '-' : '+'}${this.pagination.sortBy}`) ||
      '';

    const codeOrId =
      (Array.isArray(this.$route.params.id)
        ? this.$route.params.id[0]
        : this.$route.params.id) || '';

    const [, id] =
      codeOrId.match(
        /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/
      ) || [];

    if (id) {
      // Получаем реестр по id
      const { data } = await new RegistryOrderApi().getRegistryOrder(id);
      this.registry = data;
      this.code = this.registry.code;
    } else if (/^[a-zA-Z0-9]+$/.test(codeOrId)) {
      // Получаем реестр по code
      try {
        const { data } = await new RegistryOrderApi().searchRegistryOrder(
          searchByField('code', codeOrId)
        );
        this.registry = data[0];
        this.code = codeOrId;
      } catch (e) {
        notifyError('Не удалось найти реестр');
        return;
      }
    }

    // Тайтл как название реестра
    this.title = this.registry?.name || 'Реестр не найден';

    return new DocumentApi()
      .pagingDocuments(
        this.code,
        this.pagination.page,
        this.pagination.rowsPerPage,
        query,
        fulltextString,
        sortString
      )
      .then((it) => it.data);
  }

  search(query: SearchQuery): void {
    this.searchQuery = query;
    this.updateTableData();
  }

  async updateTableData(val?: any): Promise<void> {
    this.tableLoading = true;
    this.updatePagination(val?.pagination);

    try {
      const data = await this.fetchData();

      if (!data) {
        this.tableLoading = false;
        return;
      }

      this.pagination.rowsNumber = data.totalRows || 0;
      this.pagination.page = data.page;
      this.pagination.rowsPerPage = data.perPage;

      this.data = data.records || [];
    } catch (e) {
      notifyError(
        this.$t('registries.unloadingList.noRecordsFound').toString()
      );
      console.warn(`failed count ${e}`);
    } finally {
      this.tableLoading = false;
    }
  }

  updatePagination(pagination: TablePagination): void {
    if (pagination) {
      this.pagination.descending = pagination.descending;
      this.pagination.page = pagination.page;
      this.pagination.rowsPerPage = pagination.rowsPerPage;
      this.pagination.sortBy =
        pagination.sortBy === null ? this.sortByDefault : pagination.sortBy;
    }
  }

  async exportRecords(): Promise<void> {
    this.loading = true;

    if (!this.selected.length) {
      notifyError('Нет выбранных объектов для скачивания');
      this.loading = false;
      return;
    }

    const records = this.selected;
    const registryApi = new RegistryApi();

    try {
      for (const record of records) {
        const { request } = await registryApi.getRegistry(record.id);
        const archiveUrl = request.responseURL;
        const response = await axios.get(archiveUrl, {
          responseType: 'arraybuffer',
        });
        const blob = new Blob([response.data], { type: 'octet/stream' });
        saveAs(blob, `${record.title}.zip`);
      }
    } catch (err: any) {
      notifyError(err?.response?.data || err?.message);
    } finally {
      this.loading = false;
    }
  }

  exportRecordsByIds(): void {
    this.loading = true;

    const query = [];

    if (typeof this.searchQuery !== 'undefined') {
      this.queryString = this.searchQuery?.query || '';
      this.fulltextString = this.searchQuery?.fulltext || '';
    }

    if (this.queryString) {
      query.push(`${this.queryString}`);
    }

    this.$store
      .dispatch('wf/processes/dispatchProcess', {
        processKey: 'ExportElsCompencationCreateUploading',
        userId: this.$store.getters['oidcClient/currentUser'].sub,
        data: {
          registryCode: CHILDRENS_REST_CASHBACK_CODE,
          query: query.join(' and '),
          fulltext: this.fulltextString,
        },
      })
      .then(({ traceId }) => {
        const handler = (e: any) => {
          const data = JSON.parse(get(e, 'data', '{}'));

          if (data.traceId === traceId) {
            this.loading = false;

            if (data.body?.url) {
              S3UrlSignerFilesService.fetchFileFromS3ByUrl({
                url: data.body.url,
              }).then(({ file }) => {
                saveAs(
                  file,
                  `${this.$t('registries.unloadingList.title')}.xls`
                );
                notifySuccess(
                  'xls файл успешно выгружен',
                  'check_circle_outline'
                );
              });
            } else if (!data.body?.success) {
              notifyError(`Ошибка экспорта: ${data.body.description || ''}`);
            }
          }
        };
        this.$websockets.addListener(handler);
      });
  }

  generateAnOrder(): void {
    this.loading = true;
    this.$q.loading.show();
    try {
      this.$store
        .dispatch('wf/processes/dispatchProcess', {
          processKey: 'BaseOrderCreate',
          data: {
            registryOrderCode: this.code,
            userId: this.$store.getters['oidcClient/currentUser'].sub,
          },
        })
        .then(({ traceId }) => {
          const handler = (e: any) => {
            const data = JSON.parse(get(e, 'data', '{}'));
            if (data.traceId === traceId) {
              this.loading = false;
              const type = get(data, 'body.type', null);
              const outData = get(data, 'body.userTask.outData', null);
              const taskId = get(data, 'body.userTask.taskId', null);
              if (type === 'error') {
                this.$q.loading.hide();
              }
              if (outData && outData.success) {
                this.$q.loading.hide();
                this.$websockets.removeListener(handler);

                this.addSign(
                  {
                    registryOrderCode: this.code,
                    exportId: outData.exportId,
                    documents: outData.filesForSign,
                  },
                  {
                    traceId,
                    taskId,
                  }
                );
              }
            }
          };
          this.$websockets.addListener(handler);
        });
    } catch (e) {
      this.$q.loading.hide();
      if (e instanceof Error) notifyError(e.message);
    }
    this.loading = false;
  }

  async onSigned({
    exportId,
    signedFiles,
    signed,
    state,
    certificate,
  }: any): Promise<void> {
    try {
      await new DocumentApi().updateDocument('AppealOrder', [
        {
          id: exportId,
          // @ts-ignore TODO openapi
          signedFiles,
          signed,
          state,
          certificate,
        },
      ]);
    } catch (error) {
      if (error instanceof Error) notifyError(error.message);
    }
    this.updateTableData();
  }

  selectionHandler(selection: Array<ExportRegistryKompensDetskOtdMIR>): void {
    this.selected = selection.filter((item) => item.signed || item.cancelDate);
  }

  process: Process | null = null;

  addSign(data: Record<string, any>, process: Process | null = null): void {
    this.dataForSigner = data;
    this.signModal = true;
    this.process = process;
  }

  showInfo(item: ExportRegistryKompensDetskOtdMIR): void {
    this.currentItem = item;
    this.infoModal = true;
  }

  getSignInfo(item: ExportRegistryKompensDetskOtdMIR): void {
    this.currentItem = item;
    this.signInfoModal = true;
  }

  hasFilesForSign(document: OrderDocument): boolean {
    return has(document, 'filesForSign');
  }

  isCancelled(document: OrderDocument): boolean {
    return !!get(document, 'cancelDate');
  }

  extractFiles(document: OrderDocument): SignedS3File[] | S3File[] {
    if (has(document, 'signedFiles')) {
      return get(document, 'signedFiles', []);
    } else {
      return get(document, 'filesForSign', []);
    }
  }

  async fetchDocumentAttachment(document: OrderDocument): Promise<void> {
    const fileName =
      document?.title ?? this.$t('registries.unnamed').toString();

    const files: SignedS3File[] = this.extractFiles(document);
    console.log(files);

    await Promise.all(
      files.map(async ({ s3url, s3Link, sig }) => {
        try {
          s3url = s3url || s3Link; // Исправить бардак

          if (s3url != null) {
            const extention = last(s3url?.split('/'))
              ?.toLowerCase()
              .match(/.[a-z]+$/);
            const { file } = await S3UrlSignerFilesService.fetchFileFromS3ByUrl(
              {
                url: s3url,
              }
            );

            saveAs(file, `${fileName}.${extention}`);
          }

          if (sig != null) {
            const { file } = await S3UrlSignerFilesService.fetchFileFromS3ByUrl(
              {
                url: sig,
              }
            );

            saveAs(file, `${fileName}.sig`);
          }
        } catch (e) {
          if (e instanceof Error) notifyError(e.message);
        }
      })
    );
  }
}

declare interface OrderDocument {
  filesForSign: S3File[];
  signedFiles: SignedS3File[];
  title: string;
}

declare interface Process {
  taskId?: string;
  traceId: string;
  success?: boolean;
}

declare interface S3File {
  s3Link: string;
  s3url: string;
  digest: string;
  format: string;
}

declare interface SignedS3File extends S3File {
  sig?: string;
  Sig?: string; // deprecated
}
</script>
