/* eslint-disable no-unused-vars */
/* eslint-disable no-await-in-loop */
/* eslint-disable no-restricted-syntax */
import { Inject, Injectable } from '@severo-tech/injection-decorator';
import { Agent, AgentType } from '../../../../domain/agent';
import { PromisesUtil } from '../../../utils';
import { ConciliationBase } from './conciliation-base';
import { Debit } from '../../../../domain/debit';
import { IAgentClient, IDebitClient, IInvoiceClient } from '../../../contracts';
import { IConfig } from '../../../contracts/config';

interface IGroupedDebit {
  cnpj: string;
  value: number;
  debits: Debit[];
}

@Injectable()
export class ConciliationOneToMany extends ConciliationBase {
  @Inject('Agent01EUSTClient')
  private readonly agentClient!: IAgentClient;

  @Inject('Debit01EUSTClient')
  private readonly debitClient!: IDebitClient;

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

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

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

    await this.executeBy((debit) => this.groupDebitsByTransmitterCNPJ(debit), year, month, user, startDate, endDate);

    await this.executeBy((debit) => this.groupDebitsByTransmitterRootCNPJ(debit), year, month, user, startDate, endDate);

    await this.executeByInvoice(year, month, user, startDate, endDate);

    this.logger.info('"ConciliationOneToMany.execute" - Finished One-To-Many debits conciliation');
  }

  private async executeBy(groupMethod: (debits: Debit[]) => Promise<IGroupedDebit[]>, year: number, month: number, user: Agent, startDate: Date, endDate: Date): Promise<void> {
    const debits = await this.debitClient.find({
      year,
      month,
      userCode: user.code,
      reconciled: false,
    });
    const groupedDebits = await groupMethod(debits);

    this.logger.info(`"ConciliationOneToMany.execute" - Grouped ${groupedDebits.length} Debits to process`);

    const { maxParallelExecution } = this.config.service;
    await this.promisesUtil.groupExecute(groupedDebits, maxParallelExecution, async (group: IGroupedDebit): Promise<void> => {
      await this.tryToConciliateDebits(startDate, endDate, group.debits, group.cnpj, user.cnpj, group.value);
    });
  }

  private async executeByInvoice(year: number, month: number, user: Agent, startDate: Date, endDate: Date): Promise<void> {
    const debits = await this.debitClient.find({
      year,
      month,
      userCode: user.code,
      reconciled: false,
    });
    const groupedDebits = await this.groupDebitsByTransmitterRootCNPJ(debits);

    this.logger.info(`"ConciliationOneToMany.executeByInvoice" - Grouped ${groupedDebits.length} Debits to process`);

    const { maxParallelExecution } = this.config.service;
    await this.promisesUtil.groupExecute(groupedDebits, maxParallelExecution, async (group: IGroupedDebit): Promise<void> => {
      await this.tryToConciliateInvoice(startDate, endDate, group.debits, group.cnpj, user.cnpj);
    });
  }

  private async groupDebitsByTransmitterCNPJ(debits: Debit[]): Promise<IGroupedDebit[]> {
    let groupMap = new Map<string, IGroupedDebit>();
    for (const debit of debits) {
      if (debit.value > 0) { // Groups only debits that haven't been reconciled so far.
        const [transmitter] = await this.agentClient.find({ code: debit.transmitterCode, type: AgentType.Transmitter });
        if (transmitter) {
          const { cnpj } = transmitter;
          groupMap = this.setGroupMap(groupMap, cnpj, debit);
        }
      }
    }

    return [...groupMap.values()].filter((el: IGroupedDebit): boolean => el.debits.length > 1);
  }

  private async groupDebitsByTransmitterRootCNPJ(debits: Debit[]): Promise<IGroupedDebit[]> {
    let groupCnpjRootMap = new Map<string, IGroupedDebit>();

    for (const debit of debits) {
      if (debit.value > 0) { // Groups only debits that haven't been reconciled so far.
        const [transmitter] = await this.agentClient.find({ code: debit.transmitterCode, type: AgentType.Transmitter });
        if (transmitter) {
          const { cnpj } = transmitter;
          const cnpjRoot = cnpj.substring(0, 8);
          groupCnpjRootMap = this.setGroupMap(groupCnpjRootMap, cnpjRoot, debit);
        }
      }
    }

    return [...groupCnpjRootMap.values()].filter((el: IGroupedDebit): boolean => el.debits.length > 1);
  }

  private setGroupMap(group: Map<string, IGroupedDebit>, cnpj: string, debit: Debit): Map<string, IGroupedDebit> {
    const existingGrouped = group.get(cnpj);
    return group.set(cnpj, {
      cnpj,
      debits: [...(existingGrouped?.debits ?? []), debit],
      value: +((existingGrouped?.value ?? 0) + debit.value).toFixed(2),
    });
  }
}
