import mongoose from 'mongoose';
import Decimal from 'decimal.js';

import { roundToXDigits } from '../helpers/numbers.helper';
import { getInventoryManualEntryData } from '../db/inventory-manual-entry.db';
import {
  IInventoryManualEntryDoc,
  IInventoryManualEntryItem,
  InventoryManualEntryModel,
} from '../models/InventoryManualEntry.model';
import { getInventoryAvailabilityByBBCDateId } from '../db/inventory-availability.db';
import { BBCDateModel } from '../models/BBCDate.model';
import { getBBCDatesByType } from '../db/bbcDates.db';
import { ECollaterals } from '../enums/collaterals.enum';
import {
  IAvailabilitySummary, IReceivableManualSummary,
} from '../models/ReceivableAvailability.model';
import {
  ReceivableAvailabilityManualSummary,
  SummaryMultipliers,
} from '../models/ReceivableAvailabilityManualSummary.model';

import { InventoryAvailabilityService } from './inventory-availability.service';

export const emptySummary: IAvailabilitySummary = {
  invoiceAmount: 0,
  creditNotes: 0,
  excludedCustomerDeduction: 0,
  grossAR: 0,
  permittedOver: 0,
  preliminaryEligibleBalance: 0,
  creditNotesNegative: 0,
  deduction: 0,
  partiallyPaidDeduction: 0,
  contraDeduction: 0,
  crossAgeDeduction: 0,
  waivedDeduction: 0,
  balanceAfterCrossAge: 0,
  finalResult: 0,
  insuredComponent: 0,
  uninsuredComponent: 0,
  insuredAvailability: 0,
  uninsuredAvailability: 0,
  ARReserves: 0,
  ARAvailability: 0,
};

const emptyMultipliers: SummaryMultipliers = {
  insuredAvailability: 0,
  uninsuredAvailability: 0,
};

const recalculateAvailabilitySummary = (summary: IReceivableManualSummary, multiplier: boolean, update: { [index: string]: number } = null): IReceivableManualSummary => {
  const updatedIndexes = (!update || multiplier) ? [] : Object.keys(update);

  summary.summary.grossAR = new Decimal(summary.summary.invoiceAmount)
    .plus(summary.summary.excludedCustomerDeduction)
    .plus(summary.summary.creditNotes)
    .toNumber();
  summary.summary.preliminaryEligibleBalance = new Decimal(summary.summary.grossAR).plus(summary.summary.permittedOver).toNumber();
  summary.summary.balanceAfterCrossAge = new Decimal(summary.summary.preliminaryEligibleBalance)
    .plus(summary.summary.deduction)
    .plus(summary.summary.partiallyPaidDeduction)
    .plus(summary.summary.contraDeduction)
    .plus(summary.summary.crossAgeDeduction)
    .plus(summary.summary.creditNotesNegative)
    .toNumber();
  summary.summary.finalResult = new Decimal(summary.summary.balanceAfterCrossAge).plus(summary.summary.waivedDeduction).toNumber();

  if(updatedIndexes.includes('insuredComponent')) {
    summary.summary.uninsuredComponent = new Decimal(summary.summary.finalResult).sub(summary.summary.insuredComponent).toNumber()
  } else if (updatedIndexes.includes('uninsuredComponent')) {
    summary.summary.insuredComponent = new Decimal(summary.summary.finalResult).sub(summary.summary.uninsuredComponent).toNumber()
  } else {
    summary.summary.uninsuredComponent = new Decimal(summary.summary.finalResult).sub(summary.summary.insuredComponent).toNumber()
  }
  summary.summary.insuredAvailability = new Decimal(summary.summary.insuredComponent)
    .mul(summary.multipliers.insuredAvailability || 0)
    .div(100)
    .toNumber();
  summary.summary.uninsuredAvailability = new Decimal(summary.summary.uninsuredComponent)
    .mul(summary.multipliers.uninsuredAvailability || 0)
    .div(100)
    .toNumber();
  summary.summary.ARAvailability = new Decimal(summary.summary.insuredAvailability)
    .plus(summary.summary.uninsuredAvailability)
    .sub(summary.summary.ARReserves)
    .toNumber();
  return summary;
};

export class ManualEntryService {

  constructor(
    private readonly getInventoryAvailabilityService: () => InventoryAvailabilityService,
  ) {
  }

  private async getPreviousInventoryEntries(bbcDateId: string) {
    const currentBBC = await BBCDateModel.findById(bbcDateId);
    if (currentBBC) {
      const allBBCs = await getBBCDatesByType(currentBBC.borrowerId.toString(), [ECollaterals.INVENTORY]);
      const previousBBCs = allBBCs.filter((bbc) => bbc.bbcDate < currentBBC.bbcDate);
      if (previousBBCs.length > 0) {
        const lastBBC = previousBBCs[0];
        const previousEntries = await getInventoryManualEntryData(lastBBC._id.toString());
        return previousEntries.items;
      } else {
        return [];
      }
    }
    return [];
  }

  async getInventoryManualEntry(bbcDateId: string) {
    const manualEntryData = await getInventoryManualEntryData(bbcDateId);
    if (!manualEntryData.items.length) {
      const previousEntries = await this.getPreviousInventoryEntries(bbcDateId);
      const mappedItems = previousEntries.map((e: (IInventoryManualEntryItem & { _id: string })) => {
        const { _id, ...rest } = e;
        return rest;
      });
      const newEntries = await InventoryManualEntryModel.findOneAndUpdate({ bbcDateId: bbcDateId }, { items: mappedItems }, {
        upsert: true,
        new: true,
      }).lean();
      manualEntryData.items = newEntries.items;
    }
    return manualEntryData;
  }

  async updateInventoryManualEntry(manualEntry: Partial<IInventoryManualEntryDoc>) {
    const foundEntry = await InventoryManualEntryModel.findOne({ bbcDateId: manualEntry.bbcDateId }).lean();
    manualEntry.items = manualEntry.items.map((item) => {
      item.availability = roundToXDigits(roundToXDigits(item.totalValue - item.ineligible) * item.advanceRate / 100);
      return item;
    });
    if (!foundEntry) {
      const mappedItems = manualEntry.items.map((item) => {
        const { _id, ...rest } = item;
        return rest;
      });
      const newEntry = await InventoryManualEntryModel.create({
        bbcDateId: new mongoose.Types.ObjectId(manualEntry.bbcDateId),
        items: mappedItems,
      });
      await newEntry.save();
      return;
    }
    const mappedItems = manualEntry.items.map((item) => {
      if (String(item._id).includes('new_')) {
        const { _id, ...rest } = item;
        return rest;
      }
      return item;
    });
    await InventoryManualEntryModel.findOneAndUpdate({ bbcDateId: manualEntry.bbcDateId }, { items: mappedItems }).lean();
    const inventoryAvailability = await getInventoryAvailabilityByBBCDateId(manualEntry.bbcDateId);
    if (inventoryAvailability && inventoryAvailability.useManualInputs) {
      const inventoryAvailabilityService = this.getInventoryAvailabilityService();
      await inventoryAvailabilityService.toggleManualInventoryAvailability(manualEntry.bbcDateId, true);
    }
  }

  async getReceivableManualSummary(bbcDateId: string) {
    const manualSummary = await ReceivableAvailabilityManualSummary.findOne({ bbcDateId });
    if (!manualSummary) {
      return { summary: emptySummary, multipliers: emptyMultipliers };
    }

    return {
      summary: manualSummary.summary ? manualSummary.summary:  emptySummary,
      multipliers: manualSummary.multipliers ? manualSummary.multipliers:  emptyMultipliers,
    };
  }

  async updateReceivableManualSummary(manualSummary: IReceivableManualSummary, update: { [index: string]: number }, multiplier: boolean) {
    const negativeKeys = ['permittedOver', 'deduction', 'partiallyPaidDeduction', 'contraDeduction', 'crossAgeDeduction', 'creditNotesNegative'];
    Object.entries(update).forEach(([index, value]) => {
      value = negativeKeys.includes(index) ? new Decimal(value).abs().neg().toNumber() : new Decimal(value).abs().toNumber();
      if (multiplier) {
        manualSummary.multipliers[index] = value;
      } else {
        manualSummary.summary[index] = value;
      }
    });
    return recalculateAvailabilitySummary(manualSummary, multiplier, multiplier ? null : update);
  }

  async saveReceivableManualSummary(bbcDateId: string, summary: IReceivableManualSummary) {
    const normalizedSummary = recalculateAvailabilitySummary(summary, false);
    const existingSummary = await ReceivableAvailabilityManualSummary.findOne({ bbcDateId });
    if (existingSummary) {
      existingSummary.summary = normalizedSummary.summary;
      existingSummary.multipliers = summary.multipliers;
      await existingSummary.save();
    } else {
      await ReceivableAvailabilityManualSummary.create({ bbcDateId, summary: normalizedSummary.summary, multipliers: summary.multipliers });
    }
  }
}
