import mongoose from 'mongoose';
import dayjs from 'dayjs';
import Decimal from 'decimal.js';
import _ from 'lodash';

import { IProductBrokerDocWithBroker } from '../models/ProductBroker.model';
import { ELoanChargeType } from '../enums/loan-charge-type.enum';
import { ELoanTypes } from '../enums/loan-types.enum';
import { LoanProduct } from '../models/LoanProducts.model';
import { BorrowerModel } from '../models/Borrower.model';
import { ETermLoanStatus, TermLoanModel } from '../models/TermLoan.model';
import { EChargeType } from '../db/reports.db';
import { IBankView } from '../models/Banks.model';
import { IQuickbooksAccount, QuickbooksAccount } from '../models/QuickbooksAccount.model';
import { IQuickbooksUploadItem } from '../classes/quickbook-item';
import { IUploadedBankTransactionWithMemo } from '../models/UploadedBankTransaction.model';
import { BanksService } from './banks.service';
import { BankUploadedTransactionsService } from './bank-uploaded-transactions.service';
import { BorrowersDB } from './borrowers.db';
import { BrokersService } from './brokers.service';
import { CashAllocationService } from './cash-allocation.service';
import { CompaniesService } from './companies.service';
import { LoanChargesService } from './loan-charges.service';
import { LoanPaymentsService } from './loan-payments.service';
import { isProductActive } from '../helpers/date.helper';

export type QuickBookReportType = 'accrual' | 'payment' | 'cash';

interface IQBReportTransaction {
  service: string;
  id: string;
  type: string;
  date: string;
  account: string;
  class: string;
  amount: number | string;
  document: string;
  memo: string;
  productId?: string;
  location?: string;
}

interface IQBOReportTransaction {
  journalNumber: string;
  journalDate: string;
  memo: string;
  account: string;
  amount: number | string;
  description: string;
  name: string;
  location: string;
  class: string;
}

const headersIIF: IQBReportTransaction[] = [
  {
    service: '!TRNS',
    id: 'TRNSID',
    type: 'TRNSTYPE',
    date: 'DATE',
    account: 'ACCNT',
    class: 'CLASS',
    amount: 'AMOUNT',
    document: 'DOCNUM',
    memo: 'MEMO',
  },
  {
    service: '!SPL',
    id: 'SPLID',
    type: 'TRNSTYPE',
    date: 'DATE',
    account: 'ACCNT',
    class: 'CLASS',
    amount: 'AMOUNT',
    document: 'DOCNUM',
    memo: 'MEMO',
  },
  {
    service: '!ENDTRNS',
    id: '',
    type: '',
    date: '',
    account: '',
    class: '',
    amount: '',
    document: '',
    memo: '',
  },
];

const IIF: IQBReportTransaction[] = [
  {
    service: 'ENDTRNS',
    id: '',
    type: '',
    date: '',
    account: '',
    class: '',
    amount: '',
    document: '',
    memo: '',
  },
];

const headersCSV = (): IQBOReportTransaction => {
  return {
    journalNumber: 'Journal No',
    journalDate: 'Journal Date',
    memo: 'Memo',
    account: 'Account',
    amount: 'Amount',
    description: 'Description',
    name: 'Name',
    location: 'Location',
    class: 'Class',
  };
};

const transactionsDateFormat = 'DDMMMYYYY';
const IIFDateFormat = 'M/D/YYYY';

export class QuickbooksService {

  constructor(
    private readonly banksService: BanksService,
    private readonly bankUploadedTransactionsService: BankUploadedTransactionsService,
    private readonly borrowersDB: BorrowersDB,
    private readonly brokersService: BrokersService,
    private readonly cashAllocationService: CashAllocationService,
    private readonly companiesService: CompaniesService,
    private readonly loanChargesService: LoanChargesService,
    private readonly loanPaymentsService: LoanPaymentsService,
  ) {
  }

  async uploadAccounts(accounts: IQuickbooksUploadItem[], deleteOld: boolean) {
    const accountsWithId = accounts.map((account) => ({ _id: 'new_', ...account }));
    await this.saveAccounts(accountsWithId, deleteOld);
  }

  async getAllAccounts() {
    return QuickbooksAccount
      .find({}, { createdAt: 0, updatedAt: 0, __v: 0 })
      .sort({ order: 1 }).lean();
  }

  async saveAccounts(accounts: (IQuickbooksAccount & { _id: string })[], deleteOld: boolean) {
    if (!accounts.length) {
      return;
    }

    if (deleteOld && accounts.length === 0) {
      throw new Error('Refusing to sync accounts: empty payload');
    }

    if (deleteOld) {
      const existingIds = accounts
        .filter((acc) => !acc._id.startsWith('new_'))
        .map((acc) => new mongoose.Types.ObjectId(acc._id));

      if (existingIds.length === 0) {
        await QuickbooksAccount.deleteMany({});
      } else {
        await QuickbooksAccount.deleteMany({
          _id: { $nin: existingIds },
        });
      }
    }

    const accountSavePromises = accounts.map(async (account, order) => {
      if (account._id.startsWith('new_')) {
        const { _id, ...accountWithoutId } = account;
        return QuickbooksAccount.create({ ...accountWithoutId, order });
      } else {
        return QuickbooksAccount.findByIdAndUpdate(account._id, { ...account, order }, { new: true, upsert: true });
      }
    });
    return Promise.all(accountSavePromises);
  }

  async getAllProducts() {
    const borrowers = await this.borrowersDB.getActiveBorrowers();
    const borrowerIds = borrowers.map((borrower) => borrower._id);
    return LoanProduct.aggregate([
      {
        '$match': {
          borrowerId: { $in: borrowerIds },
        },
      }, {
        $lookup: {
          from: 'borrowers',
          localField: 'borrowerId',
          foreignField: '_id',
          as: 'borrower',
        },
      }, {
        $unwind: {
          path: '$borrower',
        },
      }, {
        $project: {
          '_id': 0,
          'data': {
            $mergeObjects: [
              {
                $arrayToObject: {
                  $filter: {
                    input: { $objectToArray: '$$ROOT' },
                    cond: { $in: ['$$this.k', ['name', '_id', 'type']] },
                  },
                },
              }, {
                'clientName': '$borrower.name',
                'productAccrualStatus': '$borrower.accrualStatus',
              },
            ],
          },
        },
      }, {
        $replaceRoot: {
          'newRoot': '$data',
        },
      }, {
        $project: {
          '_id': 0,
          'productId': '$_id',
          'productName': '$name',
          'productType': '$type',
          'clientName': 1,
          'productAccrualStatus': 1,
        },
      }, {
        $sort: {
          'clientName': 1,
          'order': 1,
        },
      }, {
        $addFields: {
          'isSelected': null,
        },
      }, {
        $lookup: {
          from: 'term_loans',
          let: {
            productIdVar: '$productId',
            productTypeVar: '$productType',
          },
          pipeline: [
            {
              $match: {
                $expr: {
                  $and: [
                    {
                      $eq: [
                        '$productId',
                        '$$productIdVar',
                      ],
                    },
                    {
                      $ne: [
                        '$$productTypeVar',
                        'REVOLVER',
                      ],
                    },
                    {
                      $eq: ['$actual', true],
                    },
                  ],
                },
              },
            },
          ],
          as: 'term',
        },
      }, {
        $unwind: {
          path: '$term',
          preserveNullAndEmptyArrays: true,
        },
      }, {
        $addFields: {
          selectable: {
            $cond: {
              if: '$term',
              then: {
                $eq: [
                  '$term.calculationStatus',
                  'CALCULATED',
                ],
              },
              else: true,
            },
          },
        },
      },
    ]);
  }

  async getChargeTransactionsForAccrual(productIds: string[], reportDate: Date) {
    if (productIds.length === 0) {
      return [];
    }
    const transactionsFilter = {
      productIds: productIds,
      start: dayjs(reportDate).utcOffset(0).startOf('month').toDate(),
      end: dayjs(reportDate).utcOffset(0).endOf('month').toDate(),
    };
    return await this.loanChargesService.getLoanStatementsForProduct(transactionsFilter);
  }

  async getChargeTransactionsForPayments(productIds: string[], reportDate: Date) {
    const start = dayjs(reportDate).utcOffset(0).startOf('day').toDate();
    const end = dayjs(reportDate).utcOffset(0).endOf('day').toDate();
    const payments = await this.loanPaymentsService.getPaymentsForPeriod(productIds, start, end);
    if (!payments.length) {
      return [];
    }
    const transactionIds = payments.reduce((acc, payment) => [...acc, ...payment.paid.map((p) => p.statementId)], []);
    const transactionsFilter = { productIds, transactionIds, start, end };
    return await this.loanChargesService.getLoanStatementsForIdsAndCharge(transactionsFilter);
  }

  async getPrincipalTransactionsForPayments(productId: string, reportDate: Date) {
    const start = dayjs(reportDate).utcOffset(0).startOf('day').toDate();
    const end = dayjs(reportDate).utcOffset(0).endOf('day').toDate();
    const payments = await this.loanPaymentsService.getPaymentsForPeriod([productId], start, end);
    if (!payments.length) {
      return <{ [settlementCode: string]: number }>{};
    }
    return payments.reduce((acc, payment) => {
      const settlementCode = payment.settlementCode ? payment.settlementCode : null;
      const amount = payment.loanTransactions.reduce((acc, transaction) => new Decimal(acc).add(transaction.amount).toNumber(), 0);
      return {
        ...acc,
        [settlementCode]: amount,
      };
    }, <{ [settlementCode: string]: number }>{});
  }

  async getQuickbooksReport(params: {
    reportType: QuickBookReportType,
    productIds: string[],
    chargeTypes: string[],
    reportDate: Date,
    format: 'IIF' | 'CSV',
    companyIds: string[],
  }) {
    const reportErrorsSet = new Set<string>();
    await Promise.all(params.productIds.map(async (productId) => {
      const product = await this.loanChargesService.getLoanProductById(productId);
      if (product.type === ELoanTypes.TERM) {
        const termLoan = await TermLoanModel.findOne({ productId, actual: true }).lean();
        if (termLoan && termLoan.calculationStatus !== ETermLoanStatus.CALCULATED) {
          reportErrorsSet.add(`${product.name} is not calculated`);
        }
      }
    }));
    if (reportErrorsSet.size > 0) {
      return { reportData: [], reportErrors: Array.from(reportErrorsSet) };
    }

    const companyFilter = { companyId: { $in: params.companyIds.map((id) => new mongoose.Types.ObjectId(id)) } };

    const { reportType, productIds, chargeTypes, reportDate } = params;
    const reportDateFormatted = dayjs(reportDate).utcOffset(0).format('MM/DD/YYYY').toUpperCase();
    const reportDateTrFormatted = dayjs(reportDate).utcOffset(0).format(transactionsDateFormat).toUpperCase();
    const currentDateTrFormatted = dayjs(new Date).utcOffset(0).format(transactionsDateFormat).toUpperCase();
    const isAccrual = reportType === 'accrual';
    const mainData: IQBReportTransaction[] = [];

    const trTemplate: IQBReportTransaction = {
      service: 'SPL',
      id: '',
      type: 'GENERAL JOURNAL',
      date: reportDateFormatted,
      account: '',
      class: '',
      amount: 0,
      document: '',
      memo: '',
    };

    await Promise.all(productIds.map(async (productId) => {
      const product = await this.loanChargesService.getLoanProductById(productId);
      const charges = await this.loanChargesService.getLoanChargeForProduct(productId);
      const filteredCharges = charges.filter((charge) => chargeTypes.includes(charge.chargeType));
      const productBrokers = await this.brokersService.getProductBrokers(productId);
      const borrower = await BorrowerModel.findById(product.borrowerId).lean();

      const allTransactions = isAccrual
        ? await this.getChargeTransactionsForAccrual([productId], reportDate)
        : await this.getChargeTransactionsForPayments([productId], reportDate);

      await Promise.all(filteredCharges.map(async (charge) => {
        const chargeTransactions = allTransactions.filter((tr) => tr.chargeId.toString() === charge._id.toString());
        if (!chargeTransactions.length) {
          return;
        }

        const totalAmountGroups = isAccrual
          ? { [charge.PLCode]: chargeTransactions.reduce((acc, tr) => new Decimal(acc).add(tr.amount).toNumber(), 0) }
          : chargeTransactions.reduce((acc, tr) => {
            if (tr.settlementCode) {
              return {
                ...acc,
                [tr.settlementCode]: new Decimal(acc[tr.settlementCode] || 0).add(tr.amount).toNumber(),
              };
            }
            return {
              ...acc,
              [charge.PLCode]: new Decimal(acc[charge.PLCode] || 0).add(tr.amount).toNumber(),
            };
          }, <{ [PLCode: string]: number }>{});

        await Promise.all(Object.entries(totalAmountGroups).map(async ([PLCode, totalAmountValue]) => {
          if (totalAmountValue === 0) {
            return;
          }
          let totalAmount = product.masterLoanProductId != null ? -totalAmountValue : totalAmountValue;
          totalAmount = isAccrual ? totalAmount : -totalAmount;
          const quickbooksAccountBS = await QuickbooksAccount.findOne({ accountCode: charge.code, ...companyFilter }).lean();
          const quickbooksAccountPL = await QuickbooksAccount.findOne({ accountCode: PLCode, ...companyFilter }).lean();
          if (!quickbooksAccountBS || !quickbooksAccountPL) {
            if (!quickbooksAccountBS) {
              reportErrorsSet.add(charge.code);
            }
            if (!quickbooksAccountPL) {
              reportErrorsSet.add(PLCode);
            }
            return;
          }
          const tr: IQBReportTransaction = {
            ...trTemplate,
            productId,
            class: borrower.code,
            memo: `${borrower.code} ${product.masterLoanProductId != null ? 'PARTICIPANT' : ''} ${EChargeType[charge.chargeType].toUpperCase()} ${product.type.toUpperCase()} - TRANS: ${reportDateTrFormatted} - POST: ${currentDateTrFormatted}`,
          };

          tr.account = quickbooksAccountBS.fullName;
          tr.amount = totalAmount;
          mainData.push({ ...tr });

          tr.account = quickbooksAccountPL.fullName;
          tr.amount = -totalAmount;
          mainData.push({ ...tr });

          if (chargeTypes.includes('BROKERS') && isAccrual) {

            const handleFee = async (broker: IProductBrokerDocWithBroker, shareName: 'adminShare' | 'interestShare' | 'otherShare') => {

              const quickbooksAccountBrokerBS = await QuickbooksAccount.findOne({ accountCode: broker.BSCode, ...companyFilter }).lean();
              const quickbooksAccountBrokerPL = await QuickbooksAccount.findOne({ accountCode: broker.PLCode, ...companyFilter }).lean();
              if (!quickbooksAccountBrokerBS || !quickbooksAccountBrokerPL) {
                if (!quickbooksAccountBrokerBS) {
                  reportErrorsSet.add(broker.BSCode);
                }
                if (!quickbooksAccountBrokerPL) {
                  reportErrorsSet.add(broker.PLCode);
                }
                return;
              }

              const tr: IQBReportTransaction = {
                ...trTemplate,
                productId,
                class: borrower.code,
                amount: new Decimal(-totalAmount).mul(broker[shareName]).toDP(2).toNumber(),
                memo: `${borrower.code} ${EChargeType[charge.chargeType].toUpperCase()} BROKER ${broker.order + 1}`,
              };

              tr.account = quickbooksAccountBrokerBS.fullName;
              mainData.push({ ...tr });

              tr.account = quickbooksAccountBrokerPL.fullName;
              tr.amount = -tr.amount;
              mainData.push({ ...tr });
            };

            const handlers = {
              [ELoanChargeType.ADMIN_FEE]: async (broker: IProductBrokerDocWithBroker) => await handleFee(broker, 'adminShare'),
              [ELoanChargeType.INTEREST_FEE]: async (broker: IProductBrokerDocWithBroker) => await handleFee(broker, 'interestShare'),
              [ELoanChargeType.OTHER]: async (broker: IProductBrokerDocWithBroker) => await handleFee(broker, 'otherShare'),
            };
            await Promise.all(productBrokers.map(async (broker) => {
              if (handlers[charge.chargeType]) {
                await handlers[charge.chargeType](broker);
              }
            }));

          }
        }));
      }));

      const principalPayments = isAccrual || product.type === ELoanTypes.REVOLVER
        ? {}
        : await this.getPrincipalTransactionsForPayments(productId, reportDate);

      await Promise.all(Object.entries(principalPayments).map(async ([settlementCode, amount]) => {
        const tr: IQBReportTransaction = {
          ...trTemplate,
          productId,
          class: borrower.code,
          amount: product.masterLoanProductId != null ? +amount : -+amount,
          memo: `${borrower.code} ${product.masterLoanProductId != null ? 'PARTICIPANT' : ''} PRINCIPAL PAID TERM - TRANS: ${reportDateTrFormatted} - POST: ${currentDateTrFormatted}`,
        };

        if (settlementCode === 'null') {
          const products = await this.loanChargesService.getLoanProducts(borrower._id.toString());
          const revolverProduct = products.find((product) => product.type === ELoanTypes.REVOLVER && isProductActive(product.startDate, product.deactivationDate, reportDate));
          if (revolverProduct) {
            settlementCode = revolverProduct.code;
          }
        }

        const quickbooksAccountPaymentBS = await QuickbooksAccount.findOne({ accountCode: product.code, ...companyFilter }).lean();
        const quickbooksAccountPaymentPL = await QuickbooksAccount.findOne({ accountCode: settlementCode, ...companyFilter }).lean();
        if (!quickbooksAccountPaymentBS || !quickbooksAccountPaymentPL) {
          if (!quickbooksAccountPaymentBS) {
            reportErrorsSet.add(product.code);
          }
          if (!quickbooksAccountPaymentPL) {
            reportErrorsSet.add(settlementCode);
          }
          return;
        }
        tr.account = quickbooksAccountPaymentBS.fullName;
        tr.amount = -tr.amount;
        mainData.push({ ...tr });

        tr.amount = -tr.amount;
        tr.account = quickbooksAccountPaymentPL.fullName;
        mainData.push({ ...tr });
      }));
    }));

    const groupedData = _.groupBy(mainData, (obj) => obj.service + obj.id + obj.type + obj.date + obj.account + obj.class + obj.document + obj.memo + obj.productId);

    const mergedData: IQBReportTransaction[] = Object.keys(groupedData).reduce((acc, key) => {
      const group = groupedData[key];
      const mergedObject: IQBReportTransaction = {
        account: group[0].account,
        class: group[0].class,
        date: group[0].date,
        document: group[0].document,
        memo: group[0].memo,
        service: group[0].service,
        type: group[0].type,
        id: group[0].id,
        amount: group.reduce((acc, curr) => new Decimal(acc).add(+curr.amount).toNumber(), 0),
      };
      return [...acc, mergedObject];
    }, <IQBReportTransaction[]>[]);

    const reportData = (_.orderBy(mergedData, ['class', 'memo', 'account'], ['asc', 'asc', 'asc'])).filter((record) => record.amount !== 0);
    if (reportData.length > 0) {
      reportData[0].service = 'TRNS';
    }

    return { reportData, reportErrors: Array.from(reportErrorsSet).map((code) => `No account for ${code}`) };
  }

  async getQuickbooksCashReport(params: {
    reportType: QuickBookReportType,
    productIds: string[],
    chargeTypes: string[],
    reportDate: Date,
    format: 'IIF' | 'CSV',
    companyId: string,
  }) {
    const reportErrorsSet = new Set<string>();
    const { reportType, productIds, chargeTypes, reportDate } = params;
    const reportDateFormatted = dayjs(reportDate).utcOffset(0).format('MM/DD/YYYY').toUpperCase();
    const reportDateTrFormatted = dayjs(reportDate).utcOffset(0).format(transactionsDateFormat).toUpperCase();

    const startDate = dayjs(reportDate).utcOffset(0).startOf('day').toDate();
    const endDate = dayjs(reportDate).utcOffset(0).endOf('day').toDate();
    const transactions = await this.bankUploadedTransactionsService.getUploadedBankTransactions(startDate, endDate);
    const { references, products } = await this.cashAllocationService.getAllCashAllocations();
    const allAccounts = await this.getAllAccounts();
    const companyAccounts = allAccounts.filter((acc) => acc.companyId?.toString() === params.companyId);
    const banks = await this.banksService.getBanks();
    const banksMap = new Map<string, IBankView>();
    banks.forEach((bank) => banksMap.set(bank.bankAccountNumber, bank));

    const reportData = transactions
      .reduce((acc, transaction) => {
        // if (!transaction.isConverted) { // TODO only for converted?
        //   return acc;
        // }
        if (transaction.splitTransactions.length) {
          const restTransaction: IUploadedBankTransactionWithMemo = { ...transaction, splitMemo: '' };
          const splitTransactions: IUploadedBankTransactionWithMemo[] = transaction.splitTransactions.map((splitTransaction) => {
            restTransaction.amount = new Decimal(restTransaction.amount).sub(splitTransaction.amount).toNumber();
            return {
              ...transaction,
              reference: splitTransaction.reference,
              amount: splitTransaction.amount,
              splitMemo: splitTransaction.memo ?? '',
            };
          });
          if (restTransaction.amount !== 0) {
            return [...acc, restTransaction, ...splitTransactions];
          }
          return [...acc, ...splitTransactions];
        }
        return [...acc, { ...transaction, splitMemo: '' }];
      }, <IUploadedBankTransactionWithMemo[]>[])
      .reduce((acc, transaction) => {
        const foundReference = references.find((ref) => transaction.reference.toLowerCase().includes(ref.reference.toLowerCase()));
        if (!foundReference || !foundReference.cashAllocationProductId) {
          return acc;
        }
        const foundProduct = products.find((product) => product._id.toString() === foundReference.cashAllocationProductId.toString());
        const accounts = foundProduct.accounts[params.companyId];
        if (!accounts) {
          return acc;
        }
        const findAccountForBank = (): IQuickbooksAccount => {
          const foundBank = banksMap.get(transaction.accountNumber);
          if (!foundBank) {
            return null;
          }
          const accountCodeId = foundBank.ledgerAccountCodes[params.companyId];
          if (!accountCodeId) {
            return null;
          }
          const account = companyAccounts.find((account) => account._id.toString() === accountCodeId.toString());
          return account;
        };
        if (!accounts.accountId1 || !accounts.accountId2) {
          return acc;
        }
        const account1 = accounts.accountId1 === 'BANK'
          ? findAccountForBank()
          : companyAccounts.find((acc) => acc._id.toString() === accounts.accountId1.toString());
        if (!account1) {
          return acc;
        }
        const account2 = accounts.accountId2 === 'BANK'
          ? findAccountForBank()
          : companyAccounts.find((acc) => acc._id.toString() === accounts.accountId2.toString());
        if (!account2) {
          return acc;
        }
        const reportTransactions: Omit<IQBReportTransaction, 'account' | 'memo'> = {
          type: 'GENERAL JOURNAL',
          date: dayjs(transaction.date).format(IIFDateFormat),
          class: foundProduct.class,
          amount: transaction.amount,
          document: '',
          location: foundProduct.location,
          id: '',
          service: 'SPL',
        };
        const reportTransactions1: IQBReportTransaction = {
          ...reportTransactions,
          account: account1?.fullName ?? 'BANK',
          amount: -reportTransactions.amount,
          memo: +reportTransactions.amount > 0 ? foundProduct.DRMemo : foundProduct.CRMemo,
        };
        const reportTransactions2: IQBReportTransaction = {
          ...reportTransactions,
          account: account2?.fullName ?? 'BANK',
          amount: reportTransactions.amount,
          memo: +reportTransactions.amount > 0 ? foundProduct.DRMemo : foundProduct.CRMemo,
        };
        return [...acc, reportTransactions1, reportTransactions2];
      }, <IQBReportTransaction[]>[]);

    if (reportData.length > 0) {
      reportData[0].service = 'TRNS';
    }

    return { reportData, reportErrors: Array.from(reportErrorsSet).map((code) => `No account for ${code}`) };
  }

  convertQuickbooksReport(reportData: IQBReportTransaction[], format: 'IIF' | 'CSV', memo: string) {
    switch (format) {
      case 'IIF': {
        const fullReport = [...headersIIF, ...reportData, ...IIF];

        let iifContent = '';
        const getReportString = (transaction: IQBReportTransaction) => {
          const fields = [
            transaction.service,
            transaction.id,
            transaction.type,
            transaction.date,
            transaction.account,
            transaction.class,
            transaction.amount,
            transaction.document,
            transaction.memo,
          ];
          const joinedString = fields.join('\t');
          iifContent += `${joinedString}\n`;
        };

        fullReport.forEach(getReportString);
        return iifContent;
      }
      case 'CSV': {
        const ref = `REF${Math.floor(Math.random() * (999 - 100 + 1)) + 100}`;
        const qboReport: IQBOReportTransaction[] = reportData.map((transaction) => ({
          journalNumber: `${transaction.date} ${ref}`,
          journalDate: transaction.date,
          memo,
          account: transaction.account,
          amount: transaction.amount,
          description: transaction.memo,
          name: '',
          location: transaction.location,
          class: transaction.class,
        }));
        const fullReport = [headersCSV(), ...qboReport];
        return JSON.stringify({ fullReport });
      }
    }
  }

  async reassignAccounts() {
    const companies = await this.companiesService.getCompanies();
    const accounts = await QuickbooksAccount.find().lean();
    for (const [index, company] of companies.entries()) {
      if (index === 0) {
        await Promise.all(accounts.map(async (account) => {
          await QuickbooksAccount.findByIdAndUpdate(account._id, { companyId: company._id });
        }));
      } else {
        await Promise.all(accounts.map(async (account) => {
          const { _id, ...rest } = account;
          console.log(rest);
          const newAccount = new QuickbooksAccount({ ...rest });
          newAccount.companyId = company._id;
          await newAccount.save();
        }));
      }
    }
  }
}
