import { Inject, Injectable } from '@severo-tech/injection-decorator';
import { ConciliationBase } from './conciliation-base';
import { Agent } from '../../../../domain/agent';
import { Installment, Debit as ONSDebit } from '../../../../domain/debit-notice';
import { PromisesUtil } from '../../../utils';
import { Debit } from '../../../../domain/debit';
import { DebitInstallment } from '../../../../domain/debit-installment';
import { IDebitClient } from '../../../contracts';
import { CreateDebitInstallmentUseCase } from '../../debit-installment';
import { IConfig } from '../../../contracts/config';

@Injectable()
export class ConciliationOneToOne extends ConciliationBase {
  @Inject('Debit01EUSTClient')
  private readonly debitClient!: IDebitClient;

  @Inject('CreateDebitInstallmentUseCase')
  private readonly createDebitInstallmentUseCase!: CreateDebitInstallmentUseCase;

  @Inject('PromisesUtil')
  private readonly promisesUtil!: PromisesUtil;

  @Inject('EnvConfig')
  private readonly config!: IConfig;

  public async execute(onsDebits: ONSDebit[], year: number, month: number, user: Agent, startDate: Date, endDate: Date): Promise<void> {
    this.logger.info(`"ConciliationOneToOne.execute" - Conciliating One User to One Transmitter debits - UserCode [${user.code}]`);

    const { maxParallelExecution } = this.config.service;

    await this.promisesUtil.groupExecute(onsDebits, maxParallelExecution, async (onsDebit: ONSDebit): Promise<void> => {
      if (onsDebit.total <= 0) return; // Skip zero values

      const debit = await this.getDebitOrCreate(year, month, user.code, onsDebit);
      if (debit?.invoices?.length) return; // Skip reconciled debit

      await this.tryToConciliateDebits(startDate, endDate, [debit], onsDebit.user.cnpj, user.cnpj, onsDebit.total);
    });
  }

  private async getDebitOrCreate(year: number, month: number, userCode: string, onsDebit: ONSDebit): Promise<Debit> {
    let [debit] = await this.debitClient.find({
      year,
      month,
      userCode,
      transmitterCode: onsDebit.user.code,
    });

    if (!debit) {
      debit = await this.debitClient.create({
        year,
        month,
        userCode,
        transmitterCode: onsDebit.user.code,
        value: onsDebit.total,
        createdBy: 'CONCILIATION',
      });

      await this.createInstallments(debit.id, onsDebit.installments);
    }
    return debit;
  }

  private async createInstallments(debitId: string, onsInstallments: Installment[]): Promise<void> {
    await Promise.all(
      onsInstallments.map(
        (installment: Installment, index: number): Promise<DebitInstallment> => this.createDebitInstallmentUseCase.execute({
          debitId,
          dueDate: installment.dueDate,
          value: installment.value,
          index: index + 1,
        }),
      ),
    );
  }
}
