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

import {
  getInventoryAvailabilityByBBCDateId,
  saveInventoryAvailabilitySummary,
  setManualInputsToggle,
} from '../db/inventory-availability.db';
import { getBBCDateDoc } from '../db/bbcDates.db';
import { BBCDateModel, IBBCDateDoc } from '../models/BBCDate.model';
import { EInventoryAvailabilityResults } from '../enums/inventory-availability-results.enum';
import {
  IInventoryAvailability,
  IInventoryAvailabilityDocument, IInventoryAvailabilityResults,
  IInventoryAvailabilitySummary, InventoryAvailabilityModel,
  InventoryAvailabilityPaginators,
} from '../models/InventoryAvailability.model';

import {
  createInventoryAvailability,
  getInventoryAvailabilityItems,
} from '../db/inventory-availability';
import { ManualEntryService } from './manual-entry.service';
import { START_PAGINATORS } from './availability.service';

export class InventoryAvailabilityService {

  constructor(
    private readonly manualEntryService: ManualEntryService,
  ) {
  }

  async getLatestBBCFilters(excludedBBCDateId: string) {
    const bbcDateDoc = await BBCDateModel.findById(excludedBBCDateId).lean();
    if (!bbcDateDoc) {
      return [];
    }
    const latestBBCDate = await BBCDateModel.aggregate<IBBCDateDoc>([
      {
        $match: {
          '_id': { $ne: new mongoose.Types.ObjectId(excludedBBCDateId) },
          'borrowerId': new mongoose.Types.ObjectId(bbcDateDoc.borrowerId),
        },
      }, {
        $sort: {
          'bbcDate': -1,
        },
      },
    ]);
    if (latestBBCDate.length === 0) {
      return [];
    }
    for (const bbcDate of latestBBCDate) {
      const availability = await this.getInventoryAvailabilityFilters(bbcDate._id.toString());
      if (!!availability) {
        return availability.filters;
      }
    }
    return [];
  }

  async getInventoryAvailability(bbcDateId: string, paginators: InventoryAvailabilityPaginators) {
    const availability = await this.getInventoryAvailabilityFilters(bbcDateId);
    const { borrowerId } = await getBBCDateDoc(bbcDateId);
    if (!availability) {
      const lastFilters = await this.getLatestBBCFilters(bbcDateId);
      const newAvailability: Partial<IInventoryAvailabilityDocument> = {
        bbcDateId: new mongoose.Types.ObjectId(bbcDateId),
        borrowerId: new mongoose.Types.ObjectId(borrowerId),
        filters: lastFilters,
        options: { maxAdvanceRatePriority: true },
        useManualInputs: false,
        results: {
          [EInventoryAvailabilityResults.UNIQ_ITEMS]: {
            items: [],
            totalItems: 0,
            paginatorOptions: paginators[EInventoryAvailabilityResults.UNIQ_ITEMS],
          },
          [EInventoryAvailabilityResults.NON_UNIQ_ITEMS]: {
            items: [],
            totalItems: 0,
            paginatorOptions: paginators[EInventoryAvailabilityResults.NON_UNIQ_ITEMS],
          },
          [EInventoryAvailabilityResults.NOT_MATCHED_ITEMS]: {
            items: [],
            totalItems: 0,
            paginatorOptions: paginators[EInventoryAvailabilityResults.NOT_MATCHED_ITEMS],
          },
        },
        summary: [],
      };
      return newAvailability;
    }
    const items = await getInventoryAvailabilityItems(String(availability._id), paginators);
    availability.results = (<IInventoryAvailabilityResults>{ ...availability.results, ...items });
    return availability;
  }

  async getInventoryAvailabilityFilters(bbcDateId: string) {
    return await getInventoryAvailabilityByBBCDateId(bbcDateId);
  }

  async updateInventoryAvailabilityFilters(inventoryAvailability: IInventoryAvailability) {
    return await createInventoryAvailability(inventoryAvailability);
  }

  async getAllInventorySettings(bbcDateId: string = null, borrowerId: string) {
    const availabilities = await InventoryAvailabilityModel.aggregate([
      {
        $match: {
          $and: [
            { 'borrowerId': new mongoose.Types.ObjectId(borrowerId) },
            { 'bbcDateId': { '$ne': new mongoose.Types.ObjectId(bbcDateId) } },
          ],
        },
      },
    ]);
    return availabilities.reduce((acc, availability) => {
      const settings = _.pick(availability, ['filters', 'options']);
      return { ...acc, [availability.bbcDateId.toString()]: settings };
    }, {});
  }

  async checkCreateAvailability(bbcDateId: string) {
    const inventoryAvailability = await getInventoryAvailabilityByBBCDateId(bbcDateId);
    if (inventoryAvailability) {
      return;
    }
    const newAvailability = await this.getInventoryAvailability(bbcDateId, START_PAGINATORS);
    if (newAvailability) {
      await createInventoryAvailability(newAvailability);
    }
  }

  async toggleManualInventoryAvailability(bbcDateId: string, useManualInputs: boolean) {
    await this.checkCreateAvailability(bbcDateId);
    await setManualInputsToggle(bbcDateId, useManualInputs);
    const inventoryAvailability = await getInventoryAvailabilityByBBCDateId(bbcDateId);
    if (!inventoryAvailability) {
      return;
    }
    if (useManualInputs) {
      const manualRates = await this.manualEntryService.getInventoryManualEntry(bbcDateId);
      const summaryTotal: IInventoryAvailabilitySummary = {
        advanceRate: 0,
        availability: 0,
        inventoryName: 'TOTAL',
        isGroup: false,
        order: 1000,
        totalQty: 0,
        totalValue: 0,
      };
      const newSummary = manualRates.items.map((item, order) => {
        return <IInventoryAvailabilitySummary>{
          order,
          isGroup: false,
          inventoryName: item.inventoryName,
          totalQty: item.totalQty,
          totalValue: item.totalValue,
          advanceRate: new Decimal(item.advanceRate).div(100).toDP(2).toNumber(),
          availability: item.availability,
        };
      });

      const updateSummaryTotal = (summaryTotal: IInventoryAvailabilitySummary, item: IInventoryAvailabilitySummary) => {
        summaryTotal.availability += item.availability;
        summaryTotal.totalQty += item.totalQty;
        summaryTotal.totalValue += item.totalValue;
        if (summaryTotal.totalValue) {
          summaryTotal.advanceRate = summaryTotal.availability / summaryTotal.totalValue;
        } else {
          summaryTotal.advanceRate = 0;
        }
      };

      newSummary.forEach((item) => updateSummaryTotal(summaryTotal, item));
      summaryTotal.availability = new Decimal(summaryTotal.availability).toDP(2).toNumber();
      summaryTotal.totalQty = new Decimal(summaryTotal.totalQty).toDP(2).toNumber();
      summaryTotal.totalValue = new Decimal(summaryTotal.totalValue).toDP(2).toNumber();
      summaryTotal.advanceRate = new Decimal(summaryTotal.advanceRate).toDP(2).toNumber();
      await saveInventoryAvailabilitySummary(String(inventoryAvailability._id), [...newSummary, summaryTotal]);
    } else {
      await saveInventoryAvailabilitySummary(String(inventoryAvailability._id), []);
    }
  }
}
