import { BatchResult } from 'src/core/response/domain/ok-response.interface';
import { BaseManager } from '../base.manager';
import { HttpStatus, NotFoundException } from '@nestjs/common';
import { OPERATION, STATUS } from 'src/core/strings/constants/base.constants';
import { ValidateRelationHelper } from 'src/core/helpers/validation/validate-relation.helper';
import { RecordLog } from 'src/modules/configuration/log/domain/entities/log.event';
import * as _ from 'lodash';

export abstract class BaseBatchUpdateStatusManager<Entity> extends BaseManager {
  protected dataIds: string[];
  protected relations: string[] = [];
  protected result: BatchResult;
  protected dataStatus: STATUS;
  protected oldData: Entity;
  abstract get entityTarget(): any;

  setData(ids: string[], status: STATUS): void {
    /**
     * // TODO: Handle case confirm multiple tabs;
     * Pola ids yang dikirim dirubah menjadi data_id___updated_at
     * Untuk mendapatkan value id nya saja dan menghindari breaking change
     * karena sudah digunakan sebelumnya lakukan split dari data ids yang dikirim
     * Example:
     * this.dataIds = ids.map((i)=> {
     *    return i.split('___')[0]
     * })
     *
     * Simpan data ids yang mempunyai update_at kedalam valiable baru
     * Example:
     * this.dataIdsWithDate = ids.map((i)=> {
     *  return {
     *      id: i.split('___')[0],
     *      updated_at: i.split('___')[1]
     *  }
     * })
     */

    this.dataIds = ids;
    this.dataStatus = status;
  }

  validateProcess(): Promise<void> {
    return;
  }

  prepareData(): Promise<void> {
    return;
  }

  async process(): Promise<void> {
    let totalFailed = 0;
    let totalSuccess = 0;
    const messages = [];

    /**
     * // TODO: Handle case confirm multiple tabs;
     * Lopping data diambil dari dataIdsWithDate
     * exp:  for (const item of this.dataIdsWithDate)
     */

    for (const id of this.dataIds) {
      try {
        /**
         * // TODO: Handle case confirm multiple tabs;
         * buat variable:
         * const id = item.id
         * const updated_at = item.updated_at
         */

        const entity = await this.dataService.getOneByOptions({
          where: {
            id: id,
          },
          relations: this.relations,
        });

        if (!entity) {
          throw new NotFoundException({
            statusCode: HttpStatus.NOT_FOUND,
            message: `Gagal! data dengan id ${id} tidak ditemukan`,
            error: 'Entity Not Found',
          });
        }
        this.oldData = _.cloneDeep(entity);
        await this.validateData(entity);

        Object.assign(entity, {
          status: this.dataStatus,
          editor_id: this.user.id,
          editor_name: this.user.name,
          updated_at: new Date().getTime(),
        });
        await new ValidateRelationHelper(
          id,
          this.dataService,
          this.validateRelations,
          this.tableName,
        ).execute();

        /**
         * // TODO: Handle case confirm multiple tabs;
         * lakukan update data dengan where condition id dan updated_at
         * EXPECTATION => status akan berubah jika updated_at yang dikirim dari FE sama dengen yang di database
         * IF => updated_at beda tidak perlu melakukan update status dan tidak perlu memanggil eventBus tetapi tetap dihitung sebagai aksi yang berhasil
         * IF => FE tidak menambahkan updated_at makan lakukan update dan publishEvent
         */

        const result = await this.dataService.update(
          this.queryRunner,
          this.entityTarget,
          { id: id },
          entity,
        );

        this.publishEvents(this.oldData, result);

        totalSuccess = totalSuccess + 1;
      } catch (error) {
        totalFailed = totalFailed + 1;
        messages.push(error.response?.message ?? error.message);
      }
    }

    this.result = {
      total_items: this.dataIds.length,
      total_failed: totalFailed,
      total_success: totalSuccess,
      messages: messages,
    };
  }

  abstract validateData(data: Entity): Promise<void>;
  abstract getResult(): BatchResult;

  async publishEvents(dataOld, dataNew) {
    this.eventBus.publish(
      new RecordLog({
        id: dataNew['id'],
        old: dataOld,
        data: dataNew,
        user: this.user,
        description: `${this.user.name} update batch data ${this.tableName}`,
        module: this.tableName,
        op: OPERATION.UPDATE,
      }),
    );

    if (!this.eventTopics.length) return;
    for (const topic of this.eventTopics) {
      let data;
      if (topic.relations?.length) {
        data = await this.dataService.getOneByOptions({
          where: {
            id: dataNew.id,
          },
          relations: topic.relations,
        });
      }

      this.eventBus.publishAll([
        new topic.topic({
          id: data?.['id'] ?? dataNew?.['id'],
          old: dataOld,
          data: data ?? dataNew,
          user: this.user,
          description: '',
          module: this.tableName,
          op: OPERATION.UPDATE,
        }),
      ]);
    }
  }
}
