import { Component, Prop, Watch } from 'vue-property-decorator';
import Vue from 'vue';
import { i18n } from '@plugins/i18n';
import isEmpty from 'lodash/isEmpty';
import has from 'lodash/has';
import isEqual from 'lodash/isEqual';
import find from 'lodash/find';
import get from 'lodash/get';
import { QTable, QSelect } from 'quasar';
import { BaseCardCommon } from '@/interfaces';
import { Organization } from '@openapi/idm';
import { cloneDeep } from 'lodash';

declare type BaseChecksRefs = Vue | Element | Vue[] | Element[];

declare interface BaseChecksTableRefs {
  checksTable: QTable;
  [key: string]: BaseChecksRefs;
}

const ADD_MANUAL_VERIFICATIONS = 'addManualVerifications';
const ADD_AVAILABLE_MANUAL_VERIFICATIONS = 'addAvailableManualVerifications';

@Component({
  methods: {
    get,
  },
})
export default class BaseChecksTable extends Vue {
  $refs!: BaseChecksTableRefs;

  @Prop({
    default: i18n.t('base.decisionTable.checkTitle'),
  })
  private checkTitle?: string;

  @Prop({
    default: i18n.t('base.decisionTable.columnsTitle'),
  })
  private columnsTitle?: string;

  @Prop({
    default: () => {
      return {
        objects: [],
        groups: [],
      };
    },
  })
  private tableChecks?: any;

  @Prop({}) private common?: BaseCardCommon;

  @Prop({
    default: () => {
      return {};
    },
  })
  private model?: any;

  @Prop({
    default: () => {
      return {};
    },
  })
  private service?: any;

  @Prop({
    default: () => {
      return 'default';
    },
  })
  private tableMode?: string;

  @Prop({
    default: () => false,
  })
  private isEmulationPage?: boolean;

  @Watch('tableChecks.objects', { immediate: true, deep: true })
  private objectsWatcher(val: any) {
    if (this.stateObjects.length) {
      val.forEach((obj: any, i: number) => {
        if (!isEqual(obj, this.stateObjects[i])) {
          this.$store.dispatch(
            'commonComponents/baseChecksTable/setLoading',
            false
          );
        }
      });
    }
    this.setAvailablePassedChecks();
  }

  @Watch('tableChecks.groups', { immediate: true, deep: true })
  private async onGroupsChange(val: any) {
    await this.getOrganization()
      .then(res => {
        this.setNoEditMap(val, res.id); // custom for refugees 
        this.setNoEditMapDefault(val, res.id);
      })
      .catch(() => {
        this.setNoEditMap(val, ''); // custom for refugees 
        this.setNoEditMapDefault(val, '');
      });
  }

  @Watch('common.models.checks.needToBeValidated', {
    immediate: true,
    deep: true,
  })
  private commonValidWatcher(val: boolean) {
    if (val) {
      this.focusVerification();
    }
  }

  @Watch('globalReady', { immediate: true, deep: true })
  private readyHandler(val: boolean) {
    if (val) {
      this.$store.dispatch(
        'commonComponents/baseChecksTable/setPassedChecks',
        this.passedManualChecks
      );
      this.updateButtons(this.passedManualChecks);
    }
  }

  private icons = {
    ok: {
      color: 'positive',
      name: 'check_circle_outline',
      // tooltip: 'Ответ получен'
    },
    error: {
      color: 'negative',
      name: 'remove_circle_outline',
      // tooltip: 'Ошибка'
    },
    pending: {
      color: 'warning',
      name: 'update',
      // tooltip: 'Ожидание ответа'
    },
  };

  private fullscreen = false;
  private docBaseReady = false;
  private globalReady = false;
  private expands: Expand[] = [];
  private results: Result[] = [];
  private docBasedResults: Result[] = [];
  private selectOptions: SelectOption[] = [
    {
      label: {
        color: 'positive',
        icon: 'check_circle_outline',
        size: '30px',
      },
      value: true,
    },
    {
      label: {
        color: 'negative',
        icon: 'remove_circle_outline',
        size: '30px',
      },
      value: false,
    },
  ];

  private pagination: any = {
    rowsPerPage: 0,
  };

  private get mayClickOnLink() {
    return !this.isEmulationPage ? 'click' : null;
  }

  private get loading() {
    return (
      this.$store.getters['commonComponents/baseChecksTable/loading'] || false
    );
  }

  private get stateObjects() {
    return (
      this.$store.getters['commonComponents/baseChecksTable/objects'] || []
    );
  }

  private get columns(): any[] {
    return this.docBaseReady ? this.checksColumns(this.tableChecks) : [];
  }

  private get dataCast(): any[] {
    return this.docBaseReady ? this.checksRows(this.tableChecks) : [];
  }

  private get serviceDocuments(): any[] {
    return this.service?.documents || [];
  }

  private get fullColumns(): any {
    const defaultColumns = {
      align: 'left',
      name: 'title', // Используется в части представления в качестве :key;
      field: 'title', // Свойство, которое будет взято из объекта массива data для формирования результата проверки;
      i: -1, // порядковый номер колонки, используется в выделении ячейки;
      sortable: true, // Свойство колонки как и у компонента quasar'а. Участвует ли в сортировке колонка;
    };

    return [defaultColumns, ...this.columns!] || [];
  }

  private get manualVerifications(): any[] {
    return (
      this.tableChecks.groups.reduce((acc: any[], group: any) => {
        return [
          ...acc,
          ...group.verifications?.filter((v: any) => v.type === 'manual'),
        ];
      }, []) || []
    );
  }

  private get passedManualChecks(): boolean {
    const docBasedResultsLength = this.docBasedResults.length;
    const resultsLength = this.results.length;
    const manualVerificationsLength = docBasedResultsLength + resultsLength;
    if (docBasedResultsLength === manualVerificationsLength) {
      return true;
    } else if (!docBasedResultsLength && resultsLength) {
      return false;
    } else if (!resultsLength) {
      return true;
    }
    return false;
  }

  private setAvailablePassedChecks() {
    const checks: string[] = this.tableChecks.objects.reduce(
      (acc: string[], obj: { verifications?: { key: string } }) => {
        return [
          ...acc,
          ...Object.keys(obj.verifications || {}).map((key: string) => {
            return key;
          }),
        ];
    }, []);

    let availableChecks = [];

    if (!isEmpty(this.noEditMap)) {
      for (const key in this.noEditMap) {
        if (this.noEditMap[key] === false) availableChecks.push(key);
      }
    }
    if (availableChecks.length) {
      availableChecks = availableChecks.filter(
        (el) => !checks.find((check) => check === el)
      );
    }

    this.$emit('commonModels', {
      checks: {
        avilableCheksPassed: !availableChecks.length,
      },
    });
  }

  get notPassedChecks(): Result[] {
    let notPassedChecks = this.results.filter(
      (res: Result) => res.value === null
    );
    if (!isEmpty(this.noEditMap)) {
      notPassedChecks = notPassedChecks.filter(
        (el) => this.noEditMap[el.code] === false
      );
    }
    return notPassedChecks;
  }

  private mounted() {
    this.checkManualVerifications();
  }

  private toggleFullscreen(): void {
    this.$refs.checksTable.toggleFullscreen();
    this.fullscreen = !this.fullscreen;
  }

  private focusVerification(): void {
    if (this.notPassedChecks.length) {
      const select = this.$refs[
        `${this.notPassedChecks[0].code}-${this.notPassedChecks[0].objectId}`
      ] as QSelect;
      if (select) {
        select.focus();
      }
    }
  }

  private customSort(rows: any[], sortBy: string, descending: boolean) {
    let data: any[] = [];

    const sortedGroups: any[] = Object.values(
      rows.reduce((acc, item) => {
        if (!has(acc, item.rowType)) {
          acc[item.rowType] = {
            type: item.rowType,
            value: item[sortBy],
            values: [],
          };
        }
        acc[item.rowType].values.push(item);
        return acc;
      }, {})
    ).sort((a: any, b: any) => {
      const x = descending ? b : a;
      const y = descending ? a : b;
      if (this.formatSortValue(x.value) > this.formatSortValue(y.value)) {
        return 1;
      } else if (
        this.formatSortValue(x.value) < this.formatSortValue(y.value)
      ) {
        return -1;
      } else {
        return 0;
      }
    });

    sortedGroups.forEach((item) => {
      data = [...data, ...item.values];
    });

    return data;
  }

  private formatSortValue(value: any): string {
    return String(value).toLowerCase();
  }

  private checksColumns(tableChecks: any) {
    const { objects } = tableChecks;
    const columns: any[] = [];

    if (Array.isArray(objects) && objects.length) {
      objects.forEach((subject: any, i: number) => {
        const { name, type } =
          typeof subject === 'object'
            ? subject
            : {
                name: subject,
                type: 'null',
              };

        columns.push({
          align: 'center', // Куда прижат текст в колонке
          field: 'relative' + i, // Свойство, которое будет взято из объекта массива data для формирования результата проверки;
          name: 'relative' + i, // Если name === title, то выведется значение для данной ячейки;
          i, // Порядковый номер колонки, используется в выделении ошибок как целой строки (если i < error для объекта из массива data, то ячейка будет выделена цветом)
          sortable: false,
          label: name, // Заголовок таблицы проверяемой сущности;
          type, // Заголовок над названием колонки проверяемого объекта, в данном случае это заголовок над Иванов И. И.
          error: -1,
        });
      });
    }

    return columns;
  }

  private checksRows(tableChecks: any) {
    const { groups } = tableChecks;
    let rows: any[] = [];
    this.$store.dispatch('commonComponents/baseChecksTable/setObjects', [
      ...tableChecks.objects,
    ]);

    if (Array.isArray(groups)) {
      groups.forEach((group: any, index: number) => {
        rows.push({
          type: 'header',
          title: group.title,
          linkedObjects: {
            head: group.title,
            objects: group.linkedObjects,
          },
          isTitle: true,
          rowType: `${group.type + index}`,
        });
        const verifications =
          this.prepareRows(group.verifications, `${group.type + index}`) || [];
        rows = [...rows, ...verifications];
      });
    }

    rows.forEach(() => {
      this.expands.push({
        val: false,
      });
    });

    this.globalReady = true;
    this.setAvailablePassedChecks();
    return rows;
  }

  private prepareRows(arr: Verification[], rowType: string): any[] | null {
    const status = {
      OK: 'ok',
      ERROR: 'error',
      PENDING: 'pending',
    };

    if (!arr || !Array.isArray(arr) || !arr.length) {
      console.warn('[base-checks-table] Verification missing for row-type:', {
        rowType,
        verifications: arr,
      });

      return null;
    }

    return arr.map(
      ({ name, basedOn, code, type, applicableForObjectType, withComment }: any) =>
        (this.tableChecks.objects || []).reduce(
          (obj: any, value: any, i: number) => {
            if (
              value.verifications?.hasOwnProperty(String(code)) ||
              type !== 'manual'
            ) {
              if (Object.keys(value.verifications || {}).includes(code)) {
                if (this.checkCorrection(code, value.corrections)) {
                  obj['relative' + i] = {
                    value: !value.verifications[code],
                    // Временный костыль перед релизом todo refactor
                    icon:
                      typeof value.verifications[code] !== 'boolean'
                        ? status.PENDING
                        : value.verifications[code]
                        ? status.OK
                        : status.ERROR,
                    code: code,
                    type: type,
                    correction: value.corrections[code],
                    id: value.id,
                  };
                } else {
                  obj['relative' + i] = {
                    value: !value.verifications[code],
                    // Временный костыль перед релизом todo refactor
                    icon:
                      typeof value.verifications[code] !== 'boolean'
                        ? status.PENDING
                        : value.verifications[code]
                        ? status.OK
                        : status.ERROR,
                    code: code,
                    type: type,
                    id: value.id,
                  };
                }
                if (obj['relative' + i].icon === status.ERROR) {
                  obj.error = i;
                }
              }
            } else if (
              !this.docBasedResults.some((res: any) => res.code === code)
            ) {
              if (Array.isArray(applicableForObjectType)) {
                if (applicableForObjectType.includes(value.subjectType)) {
                  const result = {
                    value: null,
                    code: code,
                    objectId: value.id,
                  };
                  if (
                    !this.results.length ||
                    !this.results.some(
                      (res) =>
                        res.code === result.code &&
                        res.objectId === result.objectId
                    )
                  ) {
                    this.results.push(result);
                    obj['relative' + i] = result;
                  } else if (
                    this.results.some(
                      (res) =>
                        res.code === result.code &&
                        res.objectId === result.objectId
                    )
                  ) {
                    obj['relative' + i] = find(this.results, {
                      code: result.code,
                      objectId: result.objectId,
                    });
                  }
                }
              }
            }
            return obj;
          },
          {
            type: type,
            rowType: rowType, // Тип для сортировки;
            error: -1,
            title: name, // Заголовок наименования проверки;
            baseDocument: basedOn.title || 'Базовый тип документа',
            expand: false,
            withComment
          }
        )
    );
  }

  private checkResults(
    results: Result[],
    save = true,
    passedState = false,
    props: any
  ): void {
    if (this.hasApproveStep) {
      this.addAvailableManualVerifications(results, props);
      return;
    }

    // Because beforeDestroy hook does not work on page refresh
    if (save) {
      localStorage.setItem(this.model.id, JSON.stringify(this.results));
    }

    const isNotValid = results.some((result) => result.value === null);
    if (!isNotValid && results.length) {
      // Clear storage after submit
      this.$store.dispatch(
        'commonComponents/baseChecksTable/setPassedChecks',
        passedState || this.passedManualChecks
      );

      localStorage.removeItem(this.model.id);

      this.$store.dispatch('commonComponents/baseChecksTable/setLoading', true);
      this.$emit('commonModels', {
        index: {
          submit: {
            buttonId: ADD_MANUAL_VERIFICATIONS,
            force: true,
            data: {
              manualVerifications: this.prepareData(results),
            },
          },
        },
      });
      this.updateButtons(passedState || this.passedManualChecks);
    }
  }

  private addAvailableManualVerifications(results: any, props: any) {
    const currentResult = results.find((r: any) => 
      r.code === props.value.code && r.objectId === props.value.objectId
    );
    this.$store.dispatch('commonComponents/baseChecksTable/setLoading', true);
    const corrections = [
      {
        "value": currentResult?.value,
        "id": currentResult?.objectId,
        "code": currentResult?.code,
        "comment": null,
        "document": {
          "comment": null,
          "schema": "AttachedDocuments",
          "type": {
            "code": "criterRedefining",
            "name": "Изменение критерия"
          }
        }
      }
    ];

    this.$emit('commonModels', {
      index: {
        submit: {
          buttonId: ADD_AVAILABLE_MANUAL_VERIFICATIONS,
          force: true,
          data: {
            corrections,
          },
        },
      },
    });
  }

  private onManualCommentInput(
    value: boolean,
    results: Result[],
    props: any
  ) {
    // Some kind of preventDefault function
    props.value.value = null;

    const localResults = cloneDeep(results);
    const currentResult = localResults.find(r => 
      r.code === props.value.code && r.objectId === props.value.objectId
    );
    if (!currentResult) {
      return;
    }

    const currentResultIndex = localResults.findIndex((r) => r.code === props.value.code)
    localResults[currentResultIndex].value = null;
    localStorage.setItem(this.model.id, JSON.stringify(localResults));
    
    currentResult.value = value;
    const correction: Correction = {
      value,
      type: 'manual',
      code: props.value.code || currentResult?.code,
      name: props.row.title,
      status: props.value.icon || 'pending',
      id: currentResult?.objectId,
      withComment: true,
      results: localResults,
      prepareData: this.prepareData,
      updateButtons: () => this.updateButtons(true)
    }
    this.openOverridingModal(correction);
  }

  private updateButtons(state: boolean) {
    this.$emit('commonModels', {
      index: {
        updateButtons: [
          {
            id: 'correction',
            props: {
              disable: !state,
            },
          },
          {
            id: 'reject',
            props: {
              disable: !state,
            },
          },
        ],
      },
    });
  }

  private prepareData(arr: Result[]): any[] {
    const { objects } = this.tableChecks;
    return Array.isArray(objects)
      ? objects.map((obj: any) => {
          return {
            id: obj.id,
            verifications:
              arr.length &&
              arr.reduce((acc: Record<string, boolean>, item: Result) => {
                if (
                  item.objectId === obj.id &&
                  typeof item.value === 'boolean'
                ) {
                  acc[item.code] = item.value;
                }
                return acc;
              }, {}),
          };
        })
      : [];
  }

  private checkManualVerifications() {
    this.manualVerifications.forEach((ver: any) => {
      if (
        ver.basedOn?.type === 'document' &&
        this.checkManualVerConditionForFalse(ver.basedOn?.code, 'MVZ')
      ) {
        this.tableChecks.objects.forEach((obj: any) => {
          if (ver.applicableForObjectType?.includes(obj.subjectType)) {
            this.docBasedResults.push({
              value: false,
              code: ver.code,
              objectId: obj.id,
            });
          }
        });
      }
    });
    if (!this.checkManualFailedVerifications()) {
      this.checkResults(this.docBasedResults, false, false,  null);
    }
    this.docBaseReady = true;
  }

  private checkManualVerConditionForFalse(code: string, source: string) {
    if (this.serviceDocuments.length) {
      return this.serviceDocuments.some(
        (doc) => doc.code === code && doc.source === source
      );
    }
    return false;
  }

  private checkManualFailedVerifications(): boolean {
    let counter = 0;
    const checks = this.tableChecks.objects.reduce((acc: any[], obj: any) => {
      return [
        ...acc,
        ...Object.keys(obj.verifications || {}).map((key: string) => {
          return {
            code: key,
            id: Number(obj.id),
          };
        }),
      ];
    }, []);
    this.docBasedResults.forEach((res) => {
      if (
        checks.some(
          (check: any) =>
            check.id === Number(res.objectId) && check.code === res.code
        )
      ) {
        counter++;
      }
    });
    return counter === this.docBasedResults.length;
  }

  private checkCorrection(code: string, corrections: any): boolean {
    if (!isEmpty(corrections)) {
      return Object.keys(corrections).some(
        (key) => key.toLowerCase() === code.toLowerCase()
      );
    }
    return false;
  }

  private goToDocument(docId: string): void {
    if (docId) {
      const route = this.$route;
      this.$router.push({
        name: route.name || '',
        params: route.params || {},
        query: {
          ...route.query,
          active: 'documents',
          markeredDocuments: docId,
        },
      });
    }
  }

  private goToMvz(mvzId: string): void {
    this.$router.push({
      name: 'cards',
      params: {
        id: mvzId,
        path: 'inter_requests',
      },
      query: {
        components: this.$store.getters[
          'cardComponents/interRequests/components'
        ],
        active: 'response',
        statement: this.$store.getters['utils/cards/models'].statements.id,
      },
    });
  }

  private checkLinkedObjects(linkedObjects: any): void {
    if (
      Array.isArray(get(linkedObjects, 'objects')) &&
      get(linkedObjects, 'objects').length === 1
    ) {
      const object = linkedObjects.objects[0];
      switch (object.type) {
        case 'document':
          this.goToDocument(object.uuid);
          break;
        case 'mvz':
          this.goToMvz(object.uuid);
      }
    } else if (
      Array.isArray(get(linkedObjects, 'objects')) &&
      get(linkedObjects, 'objects').length > 1
    ) {
      this.openLinkedObjectsModal(linkedObjects);
    }
  }

  private openOverridingModal(correction: Correction) {
    if (this.tableMode === 'edit' || correction?.type === 'manual') {
      this.$emit('commonModels', {
        correctionDialogSingle: {
          data: correction,
        },
      });
    }
  }

  private openLinkedObjectsModal(linkedObjects: any) {
    this.$emit('commonModels', {
      linkedObjectsDialog: {
        data: linkedObjects,
      },
    });
  }

  async getOrganization(): Promise<Organization> {
    const currentUser = this.$store.getters['oidcClient/currentUser'];
    const organization = await this.$store.dispatch(
      'idm/organizations/getOrganization',
      { id: currentUser.domain_id }
    );
    return organization;
  }

  hasApproveStep = false;
  noEditMap: NoEditMap = {};
  get isResponsiblePerson(): boolean {
    return !!(this.model.common?.assignee?.id !== 'null');
  }

  setNoEditMap(groups: any, orgId:string): void {
    groups.forEach((g: any) => {
      g.verifications.forEach((v: any) => {
        this.noEditMap[v.code] = false;
      })
    });
    if (!this.service.ApproveSteps?.length) {
      this.hasApproveStep = false;
      return;
    }
    const verifications = this.service.ApproveSteps
      ?.filter((s: {[key: string]: any}) => s.Verifiactions)
      ?.flatMap((s: {[key: string]: any}) => s.Verifiactions);

    verifications.map((v: string) => (this.noEditMap[v] = true));

    const step = this.service.ApproveSteps.find((s: any) => 
      s.departmentId === orgId || s.SubDepartment?.includes(orgId)
    );
    if (step?.Verifiactions) {
      for (const vCode in this.noEditMap) {
        step.Verifiactions.includes(vCode) && (this.noEditMap[vCode] = !step.Verifiactions.includes(vCode));
      }
    }
    this.hasApproveStep = true;
    this.docBaseReady = false;
    this.checkManualVerifications();
    this.setAvailablePassedChecks();
  }

  setNoEditMapDefault(groups: any[], orgId: string): void {
    const approveList =
    this.model?.approveRoute &&
    this.model?.approveRoute?.flatMap(
      (route: { [key: string]: string }) => route.dataPrepChecks
    );

    if (!approveList || !approveList?.length) {
      return;
    }

    const isHeadOrganisation = this.model.system.responsibleDepartment.id === orgId;

    if (isHeadOrganisation && this.isResponsiblePerson) {
      return;
    }

    groups.forEach((g: any) => {
      g.verifications.forEach((obj: { code: string }) => {
        this.noEditMap[obj.code] = true;
      })
    });

    if (this.isResponsiblePerson) {
      approveList.map((v: string) => (this.noEditMap[v] = true));
      const step = this.model.approveRoute.find(
        (s: ApproveRoute) => s.idOrg === orgId || s.subOrgs?.includes(orgId)
      );

      if (step?.dataPrepChecks) {
        for (const vCode in this.noEditMap) {
          step.dataPrepChecks.includes(vCode) && (this.noEditMap[vCode] = !step.dataPrepChecks.includes(vCode));
        }
      }
    }

    this.hasApproveStep = true;
    this.docBaseReady = false;
    this.checkManualVerifications();
    this.setAvailablePassedChecks();
  }

  isCheckBtnDisabled(row: {
    [key: string]: { code: string };
  }): boolean | undefined {
    for (const vCode in row) {
      if (vCode.startsWith('relative')) {
        return this.noEditMap[row[vCode].code];
      }
    }
  }
}

declare interface Verification {
  code?: string;
  name: string;
  baseDocument?: string;
  type?: string;
  values: any[];
}

declare interface Result {
  value: boolean | null;
  code: string;
  objectId: string | number;
}

declare interface Expand {
  val: boolean;
}

declare interface SelectOption {
  label: {
    color: string;
    icon: string;
    size: string;
  };
  value: boolean;
}

declare interface Correction {
  value: boolean;
  code: string;
  type: string;
  name: string;
  // icon: string;
  id: string | number;
  status: string;
  withComment?: boolean;
  results?: any,
  prepareData?: Function,
  updateButtons?: Function
}

declare type NoEditMap = {
  [key: string]: boolean;
};

declare type ApproveRoute = {
  idOrg: string;
  subOrgs: string[];
};
