import { Inject, Injectable, Logger } from '@nestjs/common';
import { EventBus } from '@nestjs/cqrs';
import { UserProvider, UsersSession } from 'src/core/sessions';
import { BLANK_USER } from 'src/core/strings/constants/base.constants';
import {
  EventTopics,
  validateRelations,
} from 'src/core/strings/constants/interface.constants';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { QueryRunner } from 'typeorm';

@Injectable()
export abstract class BaseManager {
  public user: UsersSession;
  public dataService: any;
  protected data: any;
  public queryRunner: QueryRunner;
  protected tableName: TABLE_NAME;
  @Inject()
  protected userProvider: UserProvider;
  @Inject()
  protected eventBus: EventBus;
  abstract get validateRelations(): validateRelations[];

  // sebagai optional yang dapat digunakan
  public dataServiceFirstOpt: any;

  // sebagai optional yang dapat digunakan
  public dataServiceSecondOpt: any;

  private readonly baseLog = new Logger(BaseManager.name);

  setUser() {
    try {
      this.user = this.userProvider?.user ?? BLANK_USER;
    } catch (error) {
      this.user = BLANK_USER;
    }
  }

  setService(
    dataService,
    tableName,
    dataServiceOpt = null,
    dataServiceSecondOpt = null,
  ) {
    this.dataService = dataService;
    this.tableName = tableName;
    this.queryRunner = this.dataService
      .getRepository()
      .manager.connection.createQueryRunner(tableName);

    if (dataServiceOpt) this.dataServiceFirstOpt = dataServiceOpt;
    if (dataServiceSecondOpt) this.dataServiceSecondOpt = dataServiceSecondOpt;
  }

  abstract get eventTopics(): EventTopics[];

  async execute(): Promise<void> {
    try {
      this.setUser();

      this.queryRunner.startTransaction();
      this.baseLog.verbose('prepareData');
      await this.prepareData();

      if (!this.dataService) {
        throw new Error('data or service not implemented.');
      }

      this.baseLog.verbose('validateProcess');
      await this.validateProcess();

      this.baseLog.verbose('beforeProcess');
      await this.beforeProcess();

      this.baseLog.verbose('process');
      await this.process();

      this.baseLog.verbose('afterProcess');
      await this.afterProcess();

      this.baseLog.verbose('commitTransaction');
      await this.queryRunner.commitTransaction();
    } catch (e) {
      if (e.response) throw new Error(JSON.stringify(e.response));
      else throw new Error(e.message);
    } finally {
      await this.queryRunner.release();
    }
  }

  abstract prepareData(): Promise<void>;

  abstract validateProcess(): Promise<void>;

  abstract beforeProcess(): Promise<void>;

  abstract process(): Promise<void>;

  abstract afterProcess(): Promise<void>;
}
