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

import { EEquipmentTypes } from '../enums/equipment-types.enum';
import { BBCDateModel } from '../models/BBCDate.model';

import {
  copyEquipmentWithoutSaving,
  deleteEquipment,
  getEquipment,
  getEquipmentDates,
  getLatestEquipments,
  saveEquipmentDoc,
} from '../db/equipment.db';
import { IEquipment, IEquipmentItem, IEquipmentSummary } from '../models/Equipment.model';
import { LockService } from './lock.service';

interface ISummaryItem {
  rowDef: string;
  amount: number;
  advanceRate: number;
  availability: number;
}

export class EquipmentService {

  constructor(
    private readonly lockService: LockService,
  ) {
  }

  async getEquipmentDates(borrowerId: string): Promise<string[]> {
    const equipments = await getEquipmentDates(borrowerId);
    return equipments.map((equipment) => dayjs(equipment.date).format('YYYY-MM-DD'));
  }

  async getEquipmentForDate(borrowerId: string, date: Date): Promise<IEquipment> {
    const equipment = await getEquipment(borrowerId, date);
    if (!equipment) {
      const lastItems = [];
      const summary = this.calculateSummary(lastItems);
      return await copyEquipmentWithoutSaving(borrowerId, date, lastItems, summary);
    }
    return equipment;
  }

  async getLatestEquipmentForDate(borrowerId: string, date: Date): Promise<IEquipment> {
    const equipment = await getLatestEquipments(borrowerId, date);
    if (!equipment.length) {
      const lastItems = [];
      const summary = this.calculateSummary(lastItems);
      return await copyEquipmentWithoutSaving(borrowerId, date, lastItems, summary);
    }
    return equipment[0];
  }

  async getEquipmentSummaryForProduct(borrowerId: string, date: Date, productId: string): Promise<IEquipmentSummary> {
    const equipment = await getEquipment(borrowerId, date);
    const lastItems = equipment
      ? equipment.items.filter((item) => item?.productId?.toString() === productId)
      : [];
    return this.calculateSummary(lastItems);
  }

  async getEquipmentForBBC(bbcDateId: string): Promise<IEquipment> {
    if (!bbcDateId) {
      return null;
    }
    const bbcDateDoc = await BBCDateModel.findById(bbcDateId).lean();
    const { bbcDate, borrowerId } = bbcDateDoc;
    const equipmentDocs = await getLatestEquipments(borrowerId.toString(), bbcDate);
    if (equipmentDocs.length) {
      return equipmentDocs.pop();
    }
    const emptySummary = this.calculateSummary([]);
    return { borrowerId, date: bbcDate, items: [], summary: emptySummary };
  }

  async saveAndReturnEquipment(borrowerId: string, date: Date, items: IEquipmentItem[]) {
    const isBBCLockedResult = await this.lockService.isDateLocked(date, borrowerId);
    if (isBBCLockedResult) {
      return null;
    }
    const summary = this.calculateSummary(items);
    return await saveEquipmentDoc(borrowerId, date, items, summary);
  }

  async deleteEquipment(borrowerId: string, date: Date) {
    const isBBCLockedResult = await this.lockService.isDateLocked(date, borrowerId);
    if (isBBCLockedResult) {
      return;
    }
    await deleteEquipment(borrowerId, date);
  }

  async getLastCopyEquipmentItems(borrowerId: string, date: Date) {
    const equipment = await getLatestEquipments(borrowerId, date);
    if (equipment.length) {
      return equipment.pop().items;
    }
    return [];
  }

  calculateSummary(equipmentItems: IEquipmentItem[]): IEquipmentSummary {
    const netTotalTypes: string[] = [EEquipmentTypes.EQUIPMENT, EEquipmentTypes.REAL_ESTATE, EEquipmentTypes.BOOT_COLLATERAL];
    const summary: ISummaryItem[] = [];

    const result = Object.keys(EEquipmentTypes).map((equipmentType) => {
      const typeSummary = <ISummaryItem>{ rowDef: equipmentType, amount: 0, advanceRate: 0, availability: 0 };
      equipmentItems.forEach((item) => {
        if (item.type === equipmentType) {
          typeSummary.amount = new Decimal(typeSummary.amount).add(item.amount ?? 0).toNumber();
          typeSummary.availability = new Decimal(item.advanceRate ?? 0).mul(item.amount ?? 0).add(typeSummary.availability ?? 0).toNumber();
          typeSummary.advanceRate = typeSummary.amount === 0 ? 0 : new Decimal(typeSummary.availability ?? 0).div(typeSummary.amount ?? 0).toDP(4).toNumber();
        }
        return typeSummary;
      }, typeSummary);
      return typeSummary;
    });

    netTotalTypes.forEach((type) => {
      summary.push(...result.filter((r) => r.rowDef === type));
    });

    const calculateSummaryField = (acc: ISummaryItem, item: ISummaryItem) => {
      acc.amount = new Decimal(acc.amount).add(item.amount).toNumber();
      acc.availability = new Decimal(acc.availability).add(item.availability).toNumber();
      acc.advanceRate = acc.amount === 0 ? 0 : new Decimal(acc.availability).div(acc.amount).toDP(4).toNumber();
      return acc;
    };

    const netTotal = result.reduce((acc, e) => {
      if (netTotalTypes.includes(e.rowDef)) {
        acc = calculateSummaryField(acc, e);
      }
      return acc;
    }, { rowDef: 'netTotal', amount: 0, advanceRate: 0, availability: 0 });
    summary.push(netTotal);

    summary.push(...result.filter((r) => r.rowDef === EEquipmentTypes.INTELLECTUAL_PROPERTY));

    const grossTotal = result.reduce((acc, e) => {
      return calculateSummaryField(acc, e);
    }, { rowDef: 'grossTotal', amount: 0, advanceRate: 0, availability: 0 });
    summary.push(grossTotal);
    return summary.reduce((acc, s) => ({ ...acc, [s.rowDef]: s }), {});
  }

}
