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

import { rawDataToDTO } from '../helpers/db-data.helper';
import { ISelectedMonth } from '../helpers/date.helper';

import {
  FinancialSpreading,
  FinancialSpreadingDTO,
  financialSpreadingParamsValidationSchema,
  financialSpreadingViewValidationSchema,
  IFinancialSpreading,
  IFinancialSpreadingParams,
  IFinancialSpreadingUpload,
  IFinancialSpreadingView,
  IFinancialSpreadingViewWithDeepData,
} from '../models/FinancialSpreading.model';
import {
  EFinanceSpreadingBSTotal,
  EFinanceSpreadingPLTotal,
  EFinancialSpreadingType,
  financeSpreadingLists,
  FinancialSpreadingSheet,
  FinancialSpreadingSheetDTO,
  financialSpreadingSheetViewValidationSchema,
  financialSpreadingTotalDictionary,
  IFinancialSpreadingSheet,
  IFinancialSpreadingSheetView,
} from '../models/FinancialSpreadingSheet.model';
import { IExcelDataCellWithStyles, UploadsService } from './uploads.service';

export const getShiftedMonth = (month: ISelectedMonth, shift: number) => {
  const nextMonth = dayjs().utcOffset(0).year(month.year).month(month.month - 1).add(shift, 'month');
  return { year: nextMonth.year(), month: nextMonth.month() + 1 };
};

const rowStyleMap = {
  orangeOnGray: { font: { italic: true, color: { rgb: 'EC5B00' } }, fill: { fgColor: { rgb: 'D3D3D3' } } },
  whiteOnBlack: { font: { color: { rgb: 'FFFFFF' } }, fill: { fgColor: { rgb: '000000' } } },
};

const rowStyles = {
  'GROSS_MARGIN': rowStyleMap.orangeOnGray,
  'OPERATING_MARGIN': rowStyleMap.orangeOnGray,
  'INCOME_FROM_OPERATIONS': rowStyleMap.whiteOnBlack,
  'PRE_TAX_INCOME': rowStyleMap.whiteOnBlack,
  'NET_INCOME': rowStyleMap.whiteOnBlack,
  'TOTAL_NON_CURRENT_ASSETS': rowStyleMap.whiteOnBlack,
  'TOTAL_CURRENT_LIABILITIES': rowStyleMap.whiteOnBlack,
  'LONG_TERM_LIABILITIES': rowStyleMap.whiteOnBlack,
  'TOTAL_CURRENT_ASSET': rowStyleMap.whiteOnBlack,
  'TOTAL_ASSETS': rowStyleMap.whiteOnBlack,
  'TOTAL_LIABILITIES': rowStyleMap.whiteOnBlack,
};

export class FinancialSpreadingService {

  constructor(
    private readonly uploadsService: UploadsService,
  ) {
  }

  clearData(rawData: IFinancialSpreading[], rawSheets: IFinancialSpreadingSheet[]) {
    const data = rawDataToDTO<FinancialSpreadingDTO, IFinancialSpreading>(FinancialSpreadingDTO, rawData);
    const sheets = rawDataToDTO<FinancialSpreadingSheetDTO, IFinancialSpreadingSheet>(FinancialSpreadingSheetDTO, rawSheets);
    return { data, sheets };
  }

  async getLastMonth(borrowerId: string): Promise<ISelectedMonth> {
    const lastMonthData = await FinancialSpreading.aggregate<IFinancialSpreading>([
      {
        $match: { 'borrowerId': new mongoose.Types.ObjectId(borrowerId) },
      }, {
        $sort: { 'year': -1, 'month': -1 },
      }, {
        $limit: 1,
      },
    ]);
    if (lastMonthData.length === 0) {
      return { year: 2024, month: 6 };
    }
    return getShiftedMonth(lastMonthData[0], 1);
  }

  async addNewMonth(params: IFinancialSpreadingParams) {
    const newMonth = await this.getLastMonth(params.borrowerId);
    await Promise.all([EFinancialSpreadingType.PROFIT_LOSS, EFinancialSpreadingType.BALANCE_SHEET].map(async (type) => {
      const allSheets = await this.getCreateAllBorrowerSheet({ ...params, financialSpreadingType: type });
      await Promise.all(allSheets.map(async (sheet) => {
        const modelInstance = new FinancialSpreading({
          borrowerId: params.borrowerId,
          amount: 0,
          sheetId: sheet._id, ...newMonth,
        });
        await modelInstance.save();
      }));
    }));
    return newMonth;
  }

  async clearMonth(params: IFinancialSpreadingParams) {
    const monthAllSpreading = await FinancialSpreading.find({
      borrowerId: params.borrowerId,
      month: params.selectedMonth.month,
      year: params.selectedMonth.year,
    });
    await Promise.all(monthAllSpreading.map(async (spreading) => {
      spreading.amount = 0;
      await spreading.save();
    }));
    const selectedMonth: ISelectedMonth = { ...params.selectedMonth };
    return selectedMonth;
  }

  async resetAll(params: IFinancialSpreadingParams) {
    await FinancialSpreading.deleteMany({ borrowerId: params.borrowerId });
    await FinancialSpreadingSheet.deleteMany({ borrowerId: params.borrowerId });
    const selectedMonth: ISelectedMonth = { ...params.selectedMonth };
    return selectedMonth;
  }

  async resetSheets() {
    await FinancialSpreadingSheet.deleteMany({});
    await FinancialSpreading.deleteMany({});
  }

  async getFinancialSpreadingData(params: IFinancialSpreadingParams, monthDeep = 3) {
    const validationRes = financialSpreadingParamsValidationSchema.validate(params);
    if (validationRes.error) {
      console.error(validationRes.error);
      return { data: [], sheets: [] };
    }
    const range = Array.from({ length: monthDeep }, (_, i) => i + 1);
    const shifts = range.reduce((acc, curr) => {
      acc[`minus_${curr}`] = -curr;
      return acc;
    }, { 'amount': 0 });
    const rawSheets = await this.getCreateAllBorrowerSheet(params);
    const rawData = await Promise.all(rawSheets.map(async (sheet) => {
      const combinedData: IFinancialSpreadingView = {
        _id: '',
        borrowerId: params.borrowerId,
        sheetId: sheet._id.toString(),
        year: params.selectedMonth.year,
        month: params.selectedMonth.month,
        amount: 0,
        minus_1: 0,
        minus_2: 0,
        minus_3: 0,
      };
      await Promise.all(Object.entries(shifts).map(async ([shiftName, shiftValue]) => {
        const filter = {
          borrowerId: params.borrowerId,
          sheetId: sheet._id,
          ...getShiftedMonth(params.selectedMonth, shiftValue),
        };
        const foundData = await FinancialSpreading.findOne(filter).lean();
        if (shiftValue === 0) {
          combinedData._id = foundData ? foundData._id.toString() : `new_${sheet._id}`;
        }
        combinedData[shiftName] = foundData ? foundData.amount : 0;
      }));
      return combinedData as unknown as IFinancialSpreading;
    }));
    return this.clearData(rawData, rawSheets);
  }

  async updateFinancialSpreadingData(params: IFinancialSpreadingParams, data: IFinancialSpreadingView[], sheets: IFinancialSpreadingSheetView[]) {
    const warnings: string[] = [];

    const editableSheets = sheets.filter((sheet) => !sheet.isTotal);
    const editableSheetIds = editableSheets.map((sheet) => sheet._id);
    const sheetsValidationResult = Joi.array().items(financialSpreadingSheetViewValidationSchema).validate(editableSheets, { abortEarly: false });
    if (sheetsValidationResult.error) {
      return { success: false, data, sheets, message: 'Invalid sheets' };
    }

    if (params.financialSpreadingType === EFinancialSpreadingType.BALANCE_SHEET) {
      const totals: { [totalType: string]: number } = {};
      await Promise.all(editableSheets.map(async (sheet) => {
        const foundData = data.find((dataRow) => dataRow.sheetId === sheet._id);
        if (totals[sheet.rowType]) {
          totals[sheet.rowType] = new Decimal(totals[sheet.rowType]).add(foundData.amount).toNumber();
        } else {
          totals[sheet.rowType] = foundData.amount;
        }
      }));
      const calculatedTotals = this.calculateBS(totals);
      if (calculatedTotals[EFinanceSpreadingBSTotal.CHECK] !== 0) {
        warnings.push('Check must be equal 0');
      }
    }

    const originalSheets = await FinancialSpreadingSheet.find({
      borrowerId: params.borrowerId,
      isTotal: false,
      financialSpreadingType: params.financialSpreadingType,
    });
    const redundantSheetIdSet = new Set(originalSheets.map((sheet) => sheet._id.toString()));

    const sheetMap: { [viewSheetId: string]: string } = {};
    await Promise.all(editableSheets.map(async (sheet) => {
      if (sheet._id.includes('new_')) {
        const newSheet = await this.createNewSheet(sheet);
        sheetMap[sheet._id] = newSheet._id.toString();
      } else {
        redundantSheetIdSet.delete(sheet._id);
        const update: Partial<IFinancialSpreadingSheet> = {
          name: sheet.name,
          order: sheet.order,
          suborder: sheet.suborder,
          rowType: sheet.rowType as EFinanceSpreadingPLTotal | EFinanceSpreadingBSTotal,
        };
        if ('isFixedCost' in sheet) {
          update.isFixedCost = sheet.isFixedCost;
        }
        await FinancialSpreadingSheet
          .findByIdAndUpdate(sheet._id, update, { new: true })
          .lean();
      }
    }));

    const editableData = data.filter((dataRow) => editableSheetIds.includes(dataRow.sheetId));
    const validationResult = Joi.array().items(financialSpreadingViewValidationSchema).validate(editableData, { abortEarly: false });
    if (validationResult.error) {
      console.error(validationResult.error);
      return { success: false, data, sheets, message: 'Invalid data' };
    }
    await Promise.all(editableData.map(async (item) => {
      const { _id, ...rest } = item;

      const update: Partial<IFinancialSpreading> = {
        amount: rest.amount,
      };

      if (_id.includes('new_')) {
        if (rest.sheetId.includes('new_')) {
          rest.sheetId = sheetMap[rest.sheetId];
        }
        await FinancialSpreading
          .findOneAndUpdate(
            { borrowerId: rest.borrowerId, year: rest.year, month: rest.month, sheetId: rest.sheetId },
            { $set: update },
            { new: true, upsert: true },
          );
      } else {
        await FinancialSpreading
          .findByIdAndUpdate(
            _id,
            { $set: update },
            { new: true, upsert: true },
          );
      }
    }));
    await this.deleteRedundantSheets(Array.from(redundantSheetIdSet));
    await this.calculateTotals(params);
    const warningsString = warnings.length > 0 ? `Data saved but has errors: ${warnings.join(', ')}` : '';
    return { success: true, message: warningsString };
  }

  async calculateTotals(params: IFinancialSpreadingParams) {
    const allSheets = await this.getAllBorrowerSheets(params);
    const totalSheets = allSheets.filter((sheet) => sheet.isTotal);
    const rowSheets = allSheets.filter((sheet) => !sheet.isTotal);
    const totals: { [totalType: string]: number } = {};
    await Promise.all(totalSheets.map(async (totalSheet) => {
      const sumRows = rowSheets.filter((simpleSheet) => simpleSheet.rowType === totalSheet.rowType);
      const data = await FinancialSpreading.find({
        borrowerId: params.borrowerId,
        year: params.selectedMonth.year,
        month: params.selectedMonth.month,
        sheetId: { $in: sumRows.map((row) => row._id) },
      }).lean();
      const totalAmount = data.reduce((acc, d) => new Decimal(acc).add(d.amount).toNumber(), 0);
      await FinancialSpreading.findOneAndUpdate({
        borrowerId: params.borrowerId,
        year: params.selectedMonth.year,
        month: params.selectedMonth.month,
        sheetId: totalSheet._id,
      }, { amount: totalAmount });
      totals[totalSheet.rowType] = totalAmount;
    }));

    const calculationMap = {
      [EFinancialSpreadingType.PROFIT_LOSS]: this.calculatePL,
      [EFinancialSpreadingType.BALANCE_SHEET]: this.calculateBS,
    };

    const updatedCalculation = calculationMap[params.financialSpreadingType](totals);

    await Promise.all(totalSheets.map(async (sheet) => {
      await FinancialSpreading.findOneAndUpdate({
          borrowerId: params.borrowerId,
          year: params.selectedMonth.year,
          month: params.selectedMonth.month,
          sheetId: sheet._id,
        },
        { amount: updatedCalculation[sheet.rowType] },
        { upsert: true },
      );
    }));
  }

  private calculatePL(totals: { [totalType: string]: number }) {
    totals[EFinanceSpreadingPLTotal.GROSS_PROFIT] = new Decimal(totals[EFinanceSpreadingPLTotal.SALES]).sub(totals[EFinanceSpreadingPLTotal.COST_OF_SALES]).toNumber();
    totals[EFinanceSpreadingPLTotal.GROSS_MARGIN] = totals[EFinanceSpreadingPLTotal.SALES] === 0
      ? 0
      : new Decimal(totals[EFinanceSpreadingPLTotal.GROSS_PROFIT]).div(totals[EFinanceSpreadingPLTotal.SALES]).mul(100).toDecimalPlaces(2).toNumber();
    totals[EFinanceSpreadingPLTotal.INCOME_FROM_OPERATIONS] = new Decimal(totals[EFinanceSpreadingPLTotal.GROSS_PROFIT]).sub(totals[EFinanceSpreadingPLTotal.OPERATING_EXPENSES]).toNumber();
    totals[EFinanceSpreadingPLTotal.OPERATING_MARGIN] = totals[EFinanceSpreadingPLTotal.SALES] === 0
      ? 0
      : new Decimal(totals[EFinanceSpreadingPLTotal.INCOME_FROM_OPERATIONS]).div(totals[EFinanceSpreadingPLTotal.SALES]).toDecimalPlaces(2).toNumber();
    totals[EFinanceSpreadingPLTotal.PRE_TAX_INCOME] = new Decimal(totals[EFinanceSpreadingPLTotal.INCOME_FROM_OPERATIONS])
      .sub(totals[EFinanceSpreadingPLTotal.NON_OPERATING_EXPENSES])
      .add(totals[EFinanceSpreadingPLTotal.NON_OPERATING_INCOME])
      .sub(totals[EFinanceSpreadingPLTotal.FINANCING_COSTS])
      .sub(totals[EFinanceSpreadingPLTotal.DEPRECIATION_AMORTIZATION])
      .toNumber();
    totals[EFinanceSpreadingPLTotal.NET_INCOME] = new Decimal(totals[EFinanceSpreadingPLTotal.PRE_TAX_INCOME]).sub(totals[EFinanceSpreadingPLTotal.TAX]).toNumber();
    return totals;
  }

  private calculateBS(totals: { [totalType: string]: number }) {
    totals[EFinanceSpreadingBSTotal.TOTAL_CURRENT_ASSET] = new Decimal(totals[EFinanceSpreadingBSTotal.CASH_EQUIVALENTS])
      .add(totals[EFinanceSpreadingBSTotal.TRADE_RECEIVABLES])
      .add(totals[EFinanceSpreadingBSTotal.INVENTORY])
      .add(totals[EFinanceSpreadingBSTotal.OTHER_CURRENT_ASSETS])
      .toNumber();
    totals[EFinanceSpreadingBSTotal.TOTAL_NON_CURRENT_ASSETS] = new Decimal(totals[EFinanceSpreadingBSTotal.FIXED_ASSETS])
      .add(totals[EFinanceSpreadingBSTotal.OTHER_NON_CURRENT_ASSETS])
      .toNumber();
    totals[EFinanceSpreadingBSTotal.TOTAL_ASSETS] = new Decimal(totals[EFinanceSpreadingBSTotal.TOTAL_CURRENT_ASSET])
      .add(totals[EFinanceSpreadingBSTotal.TOTAL_NON_CURRENT_ASSETS])
      .toNumber();
    totals[EFinanceSpreadingBSTotal.TOTAL_CURRENT_LIABILITIES] = new Decimal(totals[EFinanceSpreadingBSTotal.ACCOUNTS_PAYABLE])
      .add(totals[EFinanceSpreadingBSTotal.OTHER_CURRENT_LIABILITIES])
      .toNumber();
    totals[EFinanceSpreadingBSTotal.LONG_TERM_LIABILITIES] = new Decimal(totals[EFinanceSpreadingBSTotal.COMPANY_DEBT])
      .add(totals[EFinanceSpreadingBSTotal.OTHER_DEBT])
      .add(totals[EFinanceSpreadingBSTotal.OTHER_LONG_TERM_LIABILITIES])
      .toNumber();
    totals[EFinanceSpreadingBSTotal.TOTAL_LIABILITIES] = new Decimal(totals[EFinanceSpreadingBSTotal.TOTAL_CURRENT_LIABILITIES])
      .add(totals[EFinanceSpreadingBSTotal.LONG_TERM_LIABILITIES])
      .toNumber();
    const preCheck = new Decimal(totals[EFinanceSpreadingBSTotal.RETAINED_EARNINGS])
      .add(totals[EFinanceSpreadingBSTotal.EQUITY_RESERVES])
      .toNumber();
    totals[EFinanceSpreadingBSTotal.CHECK] = new Decimal(totals[EFinanceSpreadingBSTotal.TOTAL_ASSETS])
      .sub(totals[EFinanceSpreadingBSTotal.TOTAL_LIABILITIES])
      .sub(preCheck)
      .toNumber();
    return totals;
  }

  async createNewSheet(sheet: IFinancialSpreadingSheetView) {
    const allDates = await FinancialSpreading.aggregate([
      { $match: { borrowerId: new mongoose.Types.ObjectId(sheet.borrowerId) } },
      { $group: { _id: { year: '$year', month: '$month' } } },
    ]);
    const totalSheet = await FinancialSpreadingSheet.findOne({
      'borrowerId': new mongoose.Types.ObjectId(sheet.borrowerId),
      'financialSpreadingType': sheet.financialSpreadingType,
      'rowType': sheet.rowType,
      'isTotal': true,
    }).lean();
    const lastSheets = await FinancialSpreadingSheet.aggregate<IFinancialSpreadingSheet>([
      {
        $match: {
          'borrowerId': new mongoose.Types.ObjectId(sheet.borrowerId),
          'financialSpreadingType': sheet.financialSpreadingType,
          'rowType': sheet.rowType,
          'isTotal': false,
        },
      },
    ]);
    const order = totalSheet.order;
    const suborder = lastSheets.length;
    const uniqueDates: { year: number, month: number }[] = allDates.map((r) => r._id);
    const { _id, ...rest } = sheet;
    const newSheet = new FinancialSpreadingSheet({ ...rest, order, suborder });
    const newSheetData = await newSheet.save();
    await Promise.all(uniqueDates.map(async (uniqueDate) => {
      const newFinanceSpreading = new FinancialSpreading({
        sheetId: newSheetData._id,
        borrowerId: sheet.borrowerId,
        amount: 0,
        ...uniqueDate,
      });
      await newFinanceSpreading.save();
    }));
    return newSheetData;
  }

  async deleteRedundantSheets(sheetIds: string[]) {
    await FinancialSpreading.deleteMany({ sheetId: { $in: sheetIds.map((id) => new mongoose.Types.ObjectId(id)) } });
    await FinancialSpreadingSheet.deleteMany({ _id: { $in: sheetIds.map((id) => new mongoose.Types.ObjectId(id)) } });
  }

  private async getAllBorrowerSheets(params: IFinancialSpreadingParams) {
    const allSheets = await FinancialSpreadingSheet.find({
      borrowerId: params.borrowerId,
      financialSpreadingType: params.financialSpreadingType,
    }).lean();
    return _.sortBy(allSheets, ['order', 'isTotal', 'suborder']);
  }

  async getCreateAllBorrowerSheet(params: IFinancialSpreadingParams) {
    const allSheets = await this.getAllBorrowerSheets(params);
    if (allSheets.length) {
      return allSheets;
    }
    const totalsMap = {
      [EFinancialSpreadingType.PROFIT_LOSS]: EFinanceSpreadingPLTotal,
      [EFinancialSpreadingType.BALANCE_SHEET]: EFinanceSpreadingBSTotal,
    };
    await Promise.all(Object.entries(financeSpreadingLists[params.financialSpreadingType]).map(async ([totalKey, isCalculated], order) => {
      const newPartialRow: Omit<IFinancialSpreadingSheet, 'isTotal'> = {
        borrowerId: new mongoose.Types.ObjectId(params.borrowerId),
        financialSpreadingType: params.financialSpreadingType,
        name: financialSpreadingTotalDictionary[totalKey],
        rowType: totalsMap[params.financialSpreadingType][totalKey],
        isCalculated,
        order,
        suborder: 0,
      };
      if (!isCalculated) {
        const newRow: IFinancialSpreadingSheet = {
          ...newPartialRow,
          name: financialSpreadingTotalDictionary[totalKey].toLowerCase(),
          isTotal: false,
        };
        const newRowItem = new FinancialSpreadingSheet(newRow);
        await newRowItem.save();
      }
      const newTotalRow: IFinancialSpreadingSheet = {
        ...newPartialRow,
        isTotal: true,
      };
      const newTotalRowItem = new FinancialSpreadingSheet(newTotalRow);
      await newTotalRowItem.save();
    }));
    return this.getAllBorrowerSheets(params);
  }

  async getExcelFile(params: IFinancialSpreadingParams) {
    const monthDeep = 11;

    const fullData = {
      [EFinancialSpreadingType.PROFIT_LOSS]: [],
      [EFinancialSpreadingType.BALANCE_SHEET]: [],
    };

    const range = Array.from({ length: monthDeep }, (_, i) => i + 1).reverse();
    const oldMonths = range.reduce((acc, shiftValue) => [...acc, getShiftedMonth(params.selectedMonth, -shiftValue)], []);
    const monthsHeader = [...oldMonths, params.selectedMonth].map((month) => dayjs(new Date(month.year, month.month)).utcOffset(0).format('MM/YYYY'));
    const headers = ['', '', ...monthsHeader];
    const fullHeaders = headers.map((header) => (<IExcelDataCellWithStyles>{
      v: header,
      s: { font: { bold: true } },
    }));

    const buildAggregatedRow = (
      title: string,
      rows: IFinancialSpreadingViewWithDeepData[],
    ): IExcelDataCellWithStyles[] => {
      const oldData = range.map((r) =>
        rows.reduce((sum, row) => new Decimal(sum).add(row[`minus_${r}`] as number ?? 0).toNumber(), 0),
      );

      const current = rows.reduce((sum, row) => new Decimal(sum).add(row.amount).toNumber(), 0);

      return [
        { v: '', s: { font: { bold: true } } },
        { v: title, s: { font: { italic: true } } },
        ...oldData.map((v) => ({ v, t: 'n' })),
        { v: current, t: 'n' },
      ];
    };

    await Promise.all([EFinancialSpreadingType.PROFIT_LOSS, EFinancialSpreadingType.BALANCE_SHEET].map(async (financialSpreadingType) => {
      const financialSpreadingData = await this.getFinancialSpreadingData(
        { ...params, financialSpreadingType },
        monthDeep,
      );

      const sheetsView = financialSpreadingData.sheets as IFinancialSpreadingSheetView[];
      const data = financialSpreadingData.data as unknown as IFinancialSpreadingViewWithDeepData[];

      const sheetById = _.keyBy(sheetsView, '_id');

      const dataByRowType = _.groupBy(data, (row) => {
        const sheet = sheetById[row.sheetId];
        return sheet?.rowType;
      });

      const rows: IExcelDataCellWithStyles[][] = [];

      for (const [rowType, rowsData] of Object.entries(dataByRowType)) {

        // ===== OPERATING EXPENSES (special case)
        if (rowType === EFinanceSpreadingPLTotal.OPERATING_EXPENSES) {

          const fixed = rowsData.filter(r => sheetById[r.sheetId]?.isFixedCost);
          const nonFixed= rowsData.filter(r => !sheetById[r.sheetId]?.isFixedCost);

          if (fixed.length) {
            rows.push(buildAggregatedRow('Fixed operating expenses', fixed));
          }

          if (nonFixed.length) {
            rows.push(buildAggregatedRow('Non-Fixed operating expenses', nonFixed));
          }

          continue;
        }

        // ===== DEFAULT ROW RENDERING
        rowsData.forEach((rowData) => {
          const sheet = sheetById[rowData.sheetId];
          if (!sheet) return;

          const oldData = range.map(r => rowData[`minus_${r}`]);

          const baseRow: IExcelDataCellWithStyles[] = [
            { v: sheet.isTotal ? sheet.name : '', s: { font: { bold: true } } },
            { v: sheet.isTotal ? '' : sheet.name, s: { font: { italic: true } } },
            ...oldData.map(v => ({ v, t: 'n' })),
            { v: rowData.amount, t: 'n' },
          ];

          const style = sheet.isTotal ? rowStyles[sheet.rowType] ?? {} : {};

          rows.push(
            baseRow.map(cell => ({
              ...cell,
              s: _.merge(cell.s, style),
            })),
          );
        });
      }

      fullData[financialSpreadingType] = [
        fullHeaders,
        ...rows,
      ];
    }));

    return await this.uploadsService.convertDataToFileWithStyleOld({
      'PROFIT LOSS': fullData[EFinancialSpreadingType.PROFIT_LOSS],
      'BALANCE SHEET': fullData[EFinancialSpreadingType.BALANCE_SHEET],
    }, { width: { 'PROFIT LOSS': { 'A': 5 }, 'BALANCE SHEET': { 'A': 5 } } });
  }

  async uploadData(data: IFinancialSpreadingUpload[], params: IFinancialSpreadingParams) {
    await Promise.all(data.map(async (item) => {
      const { _id, ...rest } = item;
      if (item.sheetId.includes('new_')) {
        const sheet: IFinancialSpreadingSheetView = {
          _id: null,
          borrowerId: params.borrowerId,
          financialSpreadingType: params.financialSpreadingType,
          isCalculated: false,
          isTotal: false,
          name: item.title,
          order: 0,
          rowType: item.sheetType,
          suborder: 0,
        };
        const newSheet = await this.createNewSheet(sheet);
        rest.sheetId = newSheet._id.toString();
      }
      await FinancialSpreading
        .findOneAndUpdate(
          {
            borrowerId: params.borrowerId,
            year: params.selectedMonth.year,
            month: params.selectedMonth.month,
            sheetId: rest.sheetId,
          },
          { amount: rest.amount },
          { upsert: true },
        );
    }));
    await this.calculateTotals(params);
  }
}
