import { Component, Mixins, Prop, Vue, Watch } from 'vue-property-decorator';
import { notifyError, notifySuccess } from '@utils/notify';
import { oidcClientComputed } from '@state/modules/oidc-client';
import QColumn from '@/types/QColumn';
import { pushRoute } from '@utils/routes';
import ServiceService from '@services/entities/ServiceService';
import { DocumentPrototype } from '@interfaces/entities/Document/DocumentPrototype';
import { ServiceFileNameFactory } from '@utils/file/ServiceFileNameFactory';
import S3UrlSignerFilesService from '@services/s3urlsigner/files';
import { saveAs } from 'file-saver';
import SearchBar from '@components/signed-applications/search-bar.vue';
import { StatementsSearchQuery } from '@components/statements/index';
import {
  convertDateFormatWithoutLocale,
  convertDateFromBackendFormat,
} from '@utils/format-date';
import { DateFormat } from '@constants/utils/format-date';
import { USER_SCOPES } from '@/constants/entities/Scope';
import get from 'lodash/get';
import differenceWith from 'lodash/differenceWith';
import isEqual from 'lodash/isEqual';
import { WorkflowServiceApi } from '@openapi/wf';
import SearchQuery from '@interfaces/entities/SearchQuery';
import { DocumentApi } from '@openapi/document-registry';
import { DocumentModel } from '@interfaces/base/DocumentModel';

const documentApi = new DocumentApi();

const MAX_SIGNATURES = process.env.VUE_APP_MAXIMUM_NUMBER_OF_SIGNATURES;
const MAX_APPS_FOR_SIGNIN = 30;

const statements = Vue.extend({
  computed: {
    ...oidcClientComputed,
  },
});

const wfApi = new WorkflowServiceApi();

interface StampedDocument {
  digest?: string;
  id: string;
  name: string;
  s3url: string;
  sign?: any;
}

@Component({
  components: {
    SearchBar,
  },
  methods: {
    get,
  },
})
class SignedApplications extends Mixins(statements) {
  @Prop() searchQuery: StatementsSearchQuery | undefined;
  USER_SCOPES = USER_SCOPES;
  private sortByDefault = 'modified';
  private selected: any[] = [];
  private queryString = '';
  private fulltextString = '';
  private fullQuery = '';
  private pending = 0;
  private isSignDialogOpen = false;
  private certificate: any = null;
  private certificates = [];
  private loading = false;
  private isProgressStart = false;
  private progress = 5;
  private positionFromAccount = null;
  private errorIds: string[] = [];

  model: DocumentModel = {
    loading: false,
    pagination: {
      descending:
        this.$route.query.descending === undefined
          ? true
          : this.$route.query.descending === 'true',
      rowsPerPage: this.$route.query.rowsPerPage
        ? Number(this.$route.query.rowsPerPage)
        : 10,
      sortBy: this.$route.query.sortBy
        ? this.$route.query.sortBy
        : this.sortByDefault,
      rowsNumber: 0,
      page: this.$route.query.page ? Number(this.$route.query.page) : 1,
    },
    records: [],
  };

  @Watch('selected')
  setSelectedMaxLength(selected: any): void {
    if (MAX_SIGNATURES && selected.length > MAX_SIGNATURES) {
      notifyError(this.$t('signedApplications.maxLength').toString());
      selected.pop();
    }
  }

  private get columns(): QColumn[] {
    const columns = [
      {
        align: 'left',
        field: (val: any) => val,
        formatField: 'common.orderId',
        handler: (row: any, options: any) => {
          pushRoute(
            this.$router,
            {
              name: 'cards',
              params: {
                id: row as string,
                path: 'statements',
              },
              query: {
                active: 'author',
                components: this.$store.getters[
                  'cardComponents/statements/components'
                ],
              },
            },
            options?.targetBlank
          );
        },
        handlerField: 'id',
        label: this.$t('statements.id'),
        name: 'common.orderId',
        required: true,
        sortable: true,
      },
      {
        align: 'left',
        field: 'system.applicant.fullName',
        label: this.$t('statements.applicant'),
        name: 'system.applicant.fullName',
        style: 'white-space: pre-wrap',
        sortable: true,
      },
      {
        align: 'left',
        field: 'system.shortName',
        label: this.$t('statements.serviceType'),
        name: 'system.shortName',
        style: 'white-space: pre-wrap',
        sortable: true,
      },
      {
        align: 'left',
        label: this.$t('registries.objectCard.solutionType'),
        name: 'resDocument.type.name',
        field: 'resDocument.type.name',
        style: 'white-space: pre-wrap',
      },
      {
        align: 'left',
        label: this.$t('registries.objectCard.decisionNumber'),
        name: 'resDocument.resNumber',
        field: (val: any) =>
          val.resDocument?.resNumber
            ? val.resDocument?.resNumber
            : val.resDocument?.number,
      },
      {
        align: 'center',
        label: this.$t('statements.CommonCard.dueDate'),
        name: 'common.dueDate',
        format: (val: any) => (val ? convertDateFormatWithoutLocale(val) : ''),
        field: 'common.dueDate',
        sortable: true,
      },
      {
        align: 'left',
        label: this.$t('registries.objectCard.dateFinish'),
        name: 'resDocument.resDate',
        field: 'resDocument.resDate',
        sortable: true,
      },
      {
        align: 'left',
        label: this.$t('statements.CommonCard.dateCreate'),
        name: 'system.startDateIso',
        field: 'system.startDate',
        sortable: true,
      },
      {
        align: 'left',
        label: this.$t('signedApplications.actions'),
        name: 'resDoc',
        field: 'resDoc',
      },
    ];
    return columns;
  }

  private async fetch(q?: SearchQuery) {
    this.selected = [];
    const prevQuery = this.fullQuery;
    const prevFulltext = this.fulltextString;
    const query = ['system.status.system == 09.2'];
    if (typeof q !== 'undefined') {
      this.queryString = q?.query || '';
      this.fulltextString = q?.fulltext || '';
    }
    if (this.queryString) {
      query.push(`${this.queryString}`);
    }

    if (this.pending) {
      this.pending++;
      return;
    }
    this.pending = 1;
    this.fullQuery = query.join(' and ');
    this.model.loading = true;

    if (
      !prevQuery ||
      prevQuery !== this.fullQuery ||
      prevFulltext !== this.fulltextString
    ) {
      try {
        const { data: rowsNumber } = await documentApi.countDocuments(
          'Applications',
          this.fullQuery,
          this.fulltextString
        );
        this.model.pagination.rowsNumber = rowsNumber || 0;
      } catch (e) {
        notifyError(e.message.toString());
      }
    }

    try {
      const { data: statementsPaging } = await documentApi.pagingDocuments(
        'Applications',
        this.model.pagination.page,
        this.model.pagination.rowsPerPage,
        this.fullQuery,
        this.fulltextString,
        this.sort,
        'documents'
      );
      this.model.records = statementsPaging.records?.map((item: any) => {
        return {
          ...item,
          resDocument: item.documents?.find((el: any) =>
            el.resNumber ? el.resNumber : el.number
          ),
          documents: item.documents?.filter(
            (el: any) =>
              el.source.toLowerCase() === 'result' &&
              el.file?.name && el.file?.url &&
              (el.resNumber ? !el.resNumber : !el.number)
          ),
        };
      });
    } catch (e) {
      notifyError(e.message.toString());
    } finally {
      this.model.loading = false;
    }

    if (this.pending > 2) {
      this.pending = 0;
      await this.fetch();
      return;
    }
    this.pending = 0;
  }

  private get sort(): string {
    return `${this.model.pagination.descending ? '-' : '+'}${
      this.model.pagination.sortBy
    }`;
  }

  private get signDate() {
    return convertDateFromBackendFormat(new Date(), DateFormat.date);
  }

  private init(val: any = null): void {
    console.log('INIT', val);
    
    if (
      get(
        {
          val,
        },
        'val.pagination'
      )
    ) {
      this.model.pagination.descending = val.pagination.descending;
      this.model.pagination.rowsPerPage = val.pagination.rowsPerPage;
      this.model.pagination.page = val.pagination.page;
      this.model.pagination.sortBy =
        val.pagination.sortBy === null
          ? this.sortByDefault
          : val.pagination.sortBy;
    }

    this.fetch();
  }

  private resetPagination(): void {
    this.model.pagination.page = 1;
  }

  private async fetchDocumentAttachment(
    document: any,
    nameField = 'resDocument.template.name',
    urlField = 'resDocument.template.url',
    solution?: any
  ) {
    const service = await ServiceService.get(
      document.system.serviceId,
      'documents'
    );
    const documentPrototype = ((service as unknown) as {
      documents: DocumentPrototype[];
    }).documents.find((doc) => {
      return solution
        ? doc.code === solution.type.code
        : doc.code === document.resDocument.type.code;
    });
    const fileName =
      ServiceFileNameFactory.create({ ...this.model }, documentPrototype) ??
      get(document, nameField) ??
      nameField ??
      this.$t('registries.unnamed').toString();
    const url = solution ? urlField : get(document, urlField);

    if (url) {
      S3UrlSignerFilesService.fetchFileFromS3ByUrl({ url })
        .then(({ file }) => {
          saveAs(file, fileName);
        })
        .catch((e) => {
          notifyError(e.message.toString());
        });
    } else {
      notifyError(this.$t('registries.documentFileUrlIsEmpty').toString());
    }
  }

  private resetOptions() {
    this.isProgressStart = false;
    this.isSignDialogOpen = false;
    this.progress = 5;
    this.selected = [];
    this.fetch();
    if (this.errorIds.length) {
      navigator.clipboard.writeText(this.errorIds.toString()).catch((e) => {
        notifyError(e.message?.toString());
      });
    }
  }

  private async getTaskId(statementId: string) {
    const statement = this.selected.find(
      (statement) => statement.id === statementId
    );
    const assignee = this.$store.getters['oidcClient/currentUser'].sub;
    const processId = statement.system.processId;
    const {
      data: { tasks = [] },
    } = await wfApi.findUserTasks(processId, undefined, assignee);

    return tasks[0]?.userTask?.taskId;
  }

  private signerName() {
    let fullName;

    if (this.certificate) {
      const surnameCode = 'SN';
      const givenNameCode = 'G';

      const certData = Object.fromEntries(
        (this.certificate.data.subjectName || '')
          .split(', ')
          .map((part: any) => part.split('='))
      );

      const familyName = certData[surnameCode];
      const givenName = certData[givenNameCode];

      fullName = familyName && givenName ? `${familyName} ${givenName}` : null;
    }

    if (!fullName) {
      const familyName = get(this.currentUser, 'family_name', '');
      const givenName = get(this.currentUser, 'given_name', '');
      const middleName = get(this.currentUser, 'middle_name', '');

      fullName = this.currentUser
        ? `${familyName} ${givenName} ${middleName}`
        : '';
    }

    return fullName;
  }

  private async getAccountFromPosition() {
    const accId = this.currentUser.sub;
    const account = await this.$store.dispatch('idm/accounts/dispatchAccount', {
      id: accId,
    });

    this.positionFromAccount = get(account, 'position', null);
  }

  private hideEndProcess() {
    if (this.progress === 100) {
      this.fetch();
    }
  }

  private async doAction() {
    if (this.selected.length > MAX_APPS_FOR_SIGNIN) {
      return notifyError(this.$t('signedApplications.tooManyAppsForSign').toString());
    }
    this.isProgressStart = false;
    this.progress = 5;
    this.errorIds = [];
    try {
      const isValid = await this.$store.dispatch(
        'utils/cryptoPro/isValidSystemSetup'
      );
      if (isValid) {
        const certificates = await this.$store.dispatch(
          'utils/cryptoPro/fetchCertificates'
        );
        this.certificates = certificates.map((certificate: any) => ({
          label: certificate.subjectName,
          data: certificate,
        }));
        this.isSignDialogOpen = true;
      } else {
        notifyError(
          this.$t(
            'signedApplications.cryptoProIncorrectlyConfigured'
          ).toString()
        );
        this.loading = false;
      }
    } catch (e) {
      notifyError(
        e.message?.toString() ||
          this.$t('signedApplications.cryptoProIncorrectlyConfigured')
      );
    }
  }

  private async isCertificateValid() {
    return await this.$store
      .dispatch(
        'utils/cryptoPro/isValidSystemCertificate',
        this.certificate.data
      )
      .catch((e) => {
        notifyError(
          e.message?.toString() ||
            this.$t('signedApplications.cryptoProIncorrectlyConfigured')
        );
      });
  }

  private async startProcessSign() {
    this.isProgressStart = true;

    const isCertificateValid = await this.isCertificateValid();
    if (isCertificateValid) {
      const base64 = await this.$store
        .dispatch('utils/cryptoPro/generateBase64ByCertificate', {
          certificate: this.certificate.data,
        })
        .catch((e) => {
          notifyError(e.message.toString());
          this.resetOptions();
        });

      const selectedIds = this.selected.map((item) => item.id);

      const data = {
        certificateAsBase64: base64,
        signerPosition: this.positionFromAccount,
        signerName: this.signerName(),
        signDate: this.signDate,
        applicationIds: selectedIds,
      };

      if (base64) {
        const process = await this.$store
          .dispatch('wf/processes/dispatchProcess', {
            processKey: 'MultipleDigestGenerationProcess',
            data,
          })
          .catch((e) => {
            notifyError(e.message?.toString());
            this.resetOptions();
          });

        const traceId = process?.traceId;

        const signListener = async (e: any) => {
          const data = JSON.parse(get(e, 'data', '{}'));

          const dataIds = data.body?.digestMap
            ? Object.keys(data.body.digestMap)
            : [];
          const errorIds = differenceWith(selectedIds, dataIds, isEqual);
          // catching statements that not found in digestMap
          this.errorIds.push(...errorIds);

          if (traceId === get(data, 'traceId')) {
            this.$websockets.removeListener(signListener);

            const dataMap: Record<string, StampedDocument[]> =
              data.body?.digestMap || {};
            let count = 1;
            let statementDocumentsToSend = [];

            console.log('[startProcessSign][signListener] dataMap', dataMap);

            if (dataIds.length === 0) {
              this.resetOptions();
              notifyError(
                this.$t('signedApplications.errorDocument').toString()
              );
              return;
            }

            for (const [statementId, documents] of Object.entries(dataMap)) {
              this.progress = Math.round(100 / (dataIds.length / count));

              for (const document of documents) {
                const name = get(document, 'name', 'file');
                const url = get(document, 's3url', '');

                let s3SignUrl;

                if (document.digest) {
                  const signature = await this.$store
                    .dispatch('utils/cryptoPro/createSignature', {
                      hash: document.digest,
                      certificate: this.certificate.data,
                    })
                    .catch((e) => {
                      this.errorIds.push(document.id);
                      notifyError(e.message.toString());
                    });
                  const blob = new Blob([signature], {
                    type: 'text/plain',
                  });
                  const file = new File([blob], `${name}.sig`);
                  s3SignUrl = await S3UrlSignerFilesService.uploadFileToS3ByPath(
                    {
                      file,
                      path: `Applications/${statementId}/sign/${file.name}`,
                    }
                  ).catch((e) => {
                    this.errorIds.push(document.id);
                    notifyError(e.message.toString());
                  });
                }

                const fullDocument = this.selected
                  .map((item) => item.resDocument)
                  .find((el) => {
                    return el.id === document.id;
                  });

                const documentToSend = {
                  ...document,
                  type: fullDocument?.type,
                  source: fullDocument?.source,
                  file: {
                    url,
                    name,
                  },
                };

                if (s3SignUrl) {
                  documentToSend.sign = {
                    certificate: {
                      subjectName: this.certificate.data.subjectName,
                      issuerName: this.certificate.data.issuerName,
                    },
                    name: name.endsWith('.sig') ? name : `${name}.sig`,
                    url: s3SignUrl,
                  };
                }

                statementDocumentsToSend.push(documentToSend);
              }

              const taskId = await this.getTaskId(statementId);
              console.log(
                '[startProcessSign][signListener] SUBMIT',
                statementId,
                statementDocumentsToSend.map(
                  (d) => `${d.file?.name} # ${d.file?.url}`
                )
              );
              if (taskId) {
                // BaseResultApplicationProcess
                await wfApi
                  .submitForm(taskId, {
                    buttonId: 'sign',
                    data: {
                      documents: statementDocumentsToSend,
                    },
                  })
                  .catch((e) => {
                    this.errorIds.push(statementId);
                    notifyError(e.message.toString());
                  })
                  .finally(() => {
                    if (this.progress === 100 && !this.errorIds.length) {
                      this.resetOptions();
                      notifySuccess('Документы подписаны', 'done_all');
                    }
                  });
              }

              statementDocumentsToSend = [];
              count++;
            }
          }
        };
        this.$websockets.addListener(signListener);
      }
    } else {
      this.resetOptions();
      notifyError(this.$t('signedApplications.isNotValid').toString());
    }
  }

  async created() {
    await this.getAccountFromPosition();
  }
}

export default SignedApplications;
