import { EventBus, EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { ChangeDocEvent } from 'src/modules/configuration/couch/domain/events/change-doc.event';
import { TransactionType } from '../../../constants';
import { TransactionDataService } from '../../../data/services/transaction-data.service';
import { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-data.service';
import { SalesPriceFormulaDataService } from 'src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service';
import { FormulaType } from 'src/modules/transaction/sales-price-formula/constants';
import {
  BLANK_USER,
  OPERATION,
  STATUS,
} from 'src/core/strings/constants/base.constants';
import { TransactionModel } from '../../../data/models/transaction.model';
import { mappingRevertTransaction } from '../managers/helpers/mapping-transaction.helper';
import { apm } from '../../../../../../core/apm';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import {
  TransactionChangeStatusEvent,
  TransactionCreateQueueEvent,
} from '../../entities/event/transaction-change-status.event';
import { PriceCalculator } from '../calculator/price.calculator';

@EventsHandler(ChangeDocEvent)
export class PosTransactionHandler implements IEventHandler<ChangeDocEvent> {
  constructor(
    private dataService: TransactionDataService,
    private taxService: TaxDataService,
    private formulaService: SalesPriceFormulaDataService,
    private eventBus: EventBus,
    private calculator: PriceCalculator,
  ) {}

  async handle(event: ChangeDocEvent) {
    const envSkipTransaction = process.env.SKIP_TRANSACTION_FEATURE ?? 'false';
    const activeSkipTransaction = envSkipTransaction == 'true';

    const apmTransactions = apm.startTransaction(
      `ChangeDocEvent ${event?.data?.database}`,
      'handler',
    );
    try {
      const database = event.data.database;
      const data = { ...event.data.data };

      // jika bukan database transaksi, return langsung
      if (database != 'transaction') return;

      const sales_formula = await this.formulaService.getOneByOptions({
        where: {
          type: FormulaType.SALES_PRICE,
        },
      });

      const profit_formula = await this.formulaService.getOneByOptions({
        where: {
          type: FormulaType.PROFIT_SHARE,
        },
      });

      const queryRunner = this.dataService
        .getRepository()
        .manager.connection.createQueryRunner();

      // jika delete
      if (data._deleted ?? false) {
        // FIXME => This comment for ignore delete from POS data
        // await this.dataService.deleteById(
        //   queryRunner,
        //   TransactionModel,
        //   data._id,
        // );
      }

      // jika update // create
      else {
        // const tax_datas = taxes?.map((tax) => {
        //   return {
        //     tax_id: tax.id,
        //     tax_name: tax.name,
        //     tax_value: tax.value,
        //   };
        // });

        mappingRevertTransaction(data, TransactionType.COUNTER);

        Object.assign(data, {
          profit_share_formula: profit_formula.formula_string,
          sales_price_formula: sales_formula.formula_string,
        });

        try {
          const { dpp_value, tax_datas, other } =
            await this.calculator.calculate(data);
          Object.assign(data, {
            taxes: tax_datas,
            payment_total_dpp: dpp_value,
            profit_share_formula: profit_formula.formula_string,
            sales_price_formula: sales_formula.formula_string,
            payment_total_share: other.total_profit_share,
            payment_total_tax: other.payment_total_tax,
          });
        } catch (error) {
          apm.captureError(error);
        }

        apmTransactions.setLabel('Id', data?.id);
        apmTransactions.setLabel('Code', data?.code);
        apmTransactions.setLabel('Status', data?.status);

        // Check if this transaction should be sent to the "black hole" (not saved)
        // This is only applicable for SETTLED transactions
        const shouldSkipSaving =
          activeSkipTransaction &&
          data.status === STATUS.SETTLED &&
          (await this.formulaService.sentToBlackHole());

        // If we shouldn't skip saving, then save the transaction
        if (!shouldSkipSaving) {
          await this.dataService.create(queryRunner, TransactionModel, data);
        }

        /**
         * When transaction is cancel, set booking to Pending
         * And tell the POS to update the "Penjualan" status to Pending
         */
        if (data.status == STATUS.PENDING) {
          this.eventBus.publish(
            new TransactionChangeStatusEvent({
              id: data.id,
              old: event.data.data,
              data: data,
              user: BLANK_USER,
              description: 'Cancel Booking',
              module: TABLE_NAME.TRANSACTION,
              op: OPERATION.UPDATE,
            }),
          );
        }

        if (data.status == STATUS.SETTLED) {
          this.eventBus.publish(
            new TransactionCreateQueueEvent({
              id: data.id,
              old: event.data.data,
              data: data,
              user: BLANK_USER,
              description: '',
              module: TABLE_NAME.TRANSACTION,
              op: OPERATION.UPDATE,
            }),
          );
        }
        apmTransactions.result = 'Success';
      }
    } catch (error) {
      apmTransactions.result = 'Failed';
      apm.captureError(error);
      console.log('error handling pos transaction couch');
    } finally {
      apmTransactions.end();
    }
  }
}
