import mongoose from 'mongoose';
import _ from 'lodash';

import { MODEL_NAMES } from '../models/_models';
import { IPaginatorOptions } from '../interfaces/collaterals.interface';
import { ECollaterals } from '../enums/collaterals.enum';
import { ACCOUNT_PAYABLE_FIELDS, AccountPayableItemModel } from '../models/AccountPayableItem.model';
import { BBCDateModel, IBBCDateDoc } from '../models/BBCDate.model';
import { BBCSheetModel, IBBCSheetDoc } from '../models/BBCSheet.model';
import { INVENTORY_FIELDS, InventoryItemModel } from '../models/InventoryItem.model';
import { RECEIVABLE_FIELDS, ReceivableItemModel } from '../models/ReceivableItem.model';
import { getEnumKeyByEnumValue } from '../helpers/enums.helper';
import { getBBCSheetsByType } from './bbcSheets.db';
import { getBBCDatesByBorrower } from './bbcDates.db';
import { getBorrowerGroups } from './groups.db';
import { SALES_FIELDS, SalesItem } from '../models/SalesItem.model';

export interface IBbcDateWithSheets {
  _id: mongoose.Types.ObjectId;
  bbcDate: Date;
  collateralType?: string;
  sheets?: { name: string, _id: string }[];
  extraInfo?: string;
}

export const collateralMap = {
  [ECollaterals.INVENTORY]: InventoryItemModel,
  [ECollaterals.RECEIVABLE]: ReceivableItemModel,
  [ECollaterals.ACCOUNT_PAYABLE]: AccountPayableItemModel,
  [ECollaterals.SALES]: SalesItem,
};

export const collateralFields = {
  [ECollaterals.INVENTORY]: INVENTORY_FIELDS,
  [ECollaterals.RECEIVABLE]: RECEIVABLE_FIELDS,
  [ECollaterals.ACCOUNT_PAYABLE]: ACCOUNT_PAYABLE_FIELDS,
  [ECollaterals.SALES]: SALES_FIELDS,
};

export const COLLATERALS_LOOKUP = [
  {
    $lookup: {
      from: MODEL_NAMES.BBCSheets,
      localField: 'bbcSheetId',
      foreignField: '_id',
      as: 'bbcSheet',
    },
  },
  {
    $unwind: {
      path: '$bbcSheet',
    },
  },
  {
    $lookup: {
      from: MODEL_NAMES.BBCDates,
      localField: 'bbcSheet.bbcDateId',
      foreignField: '_id',
      as: 'bbc',
    },
  },
  {
    $unwind: {
      path: '$bbc',
    },
  },
];

export const createQuery = (groupFields: { [groupFields: string]: string }, collateralType: string) => {
  const enumKey = getEnumKeyByEnumValue(ECollaterals, collateralType);
  const itemFields = collateralFields[enumKey];
  if (!itemFields) {
    console.error('Incorrect collateralType - ', collateralType);
    return null;
  }
  const groupQueries = Object
    .entries(groupFields)
    .map(([fieldKey, fieldValue]) => ({ [fieldKey]: { $first: `$${fieldValue}` } }))
    .reduce((acc, group) => ({ ...acc, ...group }), {});
  const itemQueries = itemFields
    .map((fieldKey) => ({ [fieldKey]: `$${fieldKey}` }))
    .reduce((acc, group) => ({ ...acc, ...group }), {});
  return { groupQueries, itemQueries, enumKey };
};

export const ITEMS_PAGINATION = (paginatorOptions: IPaginatorOptions) => {
  if (!paginatorOptions) {
    return [];
  }
  if (paginatorOptions.pageSize === 0 && paginatorOptions.pageIndex === 0) {
    return [];
  }
  return !!paginatorOptions.pageSize
    ? [{ $skip: paginatorOptions.pageIndex * paginatorOptions.pageSize }, { $limit: paginatorOptions.pageSize }]
    : [];
};

export const findCollateralsWithSheets = async (borrowerId: string, collateralTypes?: ECollaterals[]) => {
  const bbcDates = await getBBCDatesByBorrower(borrowerId);
  const bbcFilter = { 'bbcDateId': { $in: bbcDates.map((bbcDate) => bbcDate._id) } };
  const collateralFilter = collateralTypes
    ? {
      $and: [
        bbcFilter,
        { 'dataType': { $in: collateralTypes } },
      ],
    }
    : bbcFilter;
  return BBCSheetModel.aggregate<IBbcDateWithSheets>([
    {
      $match: collateralFilter,
    },
    {
      $lookup: {
        from: MODEL_NAMES.BBCDates,
        localField: 'bbcDateId',
        foreignField: '_id',
        as: 'bbcDate',
      },
    },
    {
      $unwind: {
        path: '$bbcDate',
      },
    },
    {
      $group: {
        '_id': {
          'collateralType': '$dataType',
          'bbcDate': '$bbcDate.bbcDate',
          '_id': '$bbcDate._id',
        },
        'sheets': {
          $addToSet: {
            'name': '$sheetName',
            '_id': '$_id',
          },
        },
      },
    },
    {
      $addFields: {
        'collateralType': '$_id.collateralType',
        'bbcDate': '$_id.bbcDate',
        'id': '$_id._id',
      },
    },
    {
      $project: {
        '_id': '$id',
        'sheets': '$sheets',
        'collateralType': '$collateralType',
        'bbcDate': '$bbcDate',
      },
    },
    {
      $sort: {
        'bbcDate': -1,
      },
    },
  ]);
};

export const getBBCSheetsForBorrowerAndType = async (borrowerId: string, collateralType: ECollaterals) => {
  const bbcDates = await getBBCDatesByBorrower(borrowerId);
  return getBBCSheetsByType(bbcDates.map((bbcDate) => String(bbcDate._id)), collateralType);
};

export const getCollateralListBySheet = async (sheetIds: string[], collateralType: ECollaterals) => {
  const enumKey = getEnumKeyByEnumValue(ECollaterals, collateralType);
  const sheetIdsMapped = sheetIds.map((sheetId) => new mongoose.Types.ObjectId(sheetId));

  return collateralMap[enumKey].aggregate([
    {
      $match: {
        'bbcSheetId': { $in: sheetIdsMapped },
      },
    },
    {
      $lookup: {
        from: MODEL_NAMES.BBCSheets,
        localField: 'bbcSheetId',
        foreignField: '_id',
        as: 'bbcSheet',
      },
    },
    {
      $unwind: {
        path: '$bbcSheet',
      },
    },
    {
      $lookup: {
        from: MODEL_NAMES.BBCDates,
        localField: 'bbcSheet.bbcDateId',
        foreignField: '_id',
        as: 'bbc',
      },
    },
    {
      $unwind: {
        path: '$bbc',
      },
    },
    {
      $sort: {
        'order': 1,
        'bbc.bbcDate': 1,
      },
    },
  ]);
};

export const getPreviousCollateralDocument = async (borrowerId: string, date: Date, collateralType: ECollaterals, limit = 1) => {
  return BBCSheetModel.aggregate<{ _id: mongoose.Types.ObjectId, bbcDate: Date }>([
    {
      $match: {
        'dataType': collateralType,
      },
    },
    {
      $lookup:
        {
          from: MODEL_NAMES.BBCDates,
          localField: 'bbcDateId',
          foreignField: '_id',
          as: 'bbc',
        },
    },
    {
      $unwind:
        {
          path: '$bbc',
        },
    },
    {
      $match: {
        'bbc.borrowerId': new mongoose.Types.ObjectId(borrowerId),
        'bbc.bbcDate': { $lt: date },
      },
    },
    {
      $group: {
        '_id': '$bbcDateId',
        'bbcDate': {
          $first: '$bbc.bbcDate',
        },
      },
    },
    {
      $sort: {
        bbcDate: -1,
      },
    },
    {
      $limit: limit,
    },
  ]);
};

export const getCollateralDocsByBBC = async (bbcIds: string[], collateralType: ECollaterals) => {

  if (!collateralFields[collateralType]) {
    console.error('Incorrect collateralType! - ', collateralType);
    return [];
  }

  const bbcSheets = await getBBCSheetsByType(bbcIds, collateralType);

  const groupFields = {
    bbcDate: 'bbc.bbcDate',
    borrowerId: 'bbc.borrowerId',
  };

  const { groupQueries, itemQueries, enumKey } = createQuery(groupFields, collateralType);
  const groupingQuery = { _id: '$bbc._id', ...groupQueries, items: { $push: itemQueries } };

  const aggregationResult = await collateralMap[enumKey].aggregate([
    {
      $match: {
        'bbcSheetId': { $in: bbcSheets.map((bbcSheet) => bbcSheet._id) },
      },
    },
    ...COLLATERALS_LOOKUP,
    {
      $sort: {
        order: 1,
      },
    },
    {
      $group: groupingQuery,
    },
    {
      $sort: {
        bbcDate: 1,
      },
    },
  ]);
  if (!aggregationResult.length) {
    const BBCs = await BBCDateModel.find({ _id: { $in: bbcIds } }).lean();
    return BBCs.map((BBC) => ({ ...BBC, items: [] }));
  }
  return aggregationResult;
};

export const getCollateralListByBBCAndType = async (bbcDateId: string, collateralType: ECollaterals) => {
  const sheets = await getBBCSheetsByType([bbcDateId], collateralType);
  return getCollateralListBySheet(sheets.map((sheet) => String(sheet._id)), collateralType);
};

export const findCollateralSheets = (bbcDateId: string, collateralType: ECollaterals) => {
  return BBCSheetModel.find({ bbcDateId, dataType: collateralType });
};

export const findCollateralSheetsById = async (collateralsId: string): Promise<IBBCSheetDoc> => {
  return BBCSheetModel.findById(collateralsId);
};

const getCollateralsBySheet = async (sheet: IBBCSheetDoc, paginatorOptions?: IPaginatorOptions) => {

  const groupFields = {
    bbcDate: 'bbc.bbcDate',
    borrowerId: 'bbc.borrowerId',
    fileName: 'bbcSheet.fileName',
    sheetName: 'bbcSheet.sheetName',
  };

  const { groupQueries, itemQueries, enumKey } = createQuery(groupFields, sheet.dataType);
  const groupingQuery = { _id: '$bbcSheetId', ...groupQueries, items: { $push: itemQueries } };

  const collateralSheets = await collateralMap[enumKey].aggregate([
    {
      $match: {
        'bbcSheetId': sheet._id,
      },
    },
    ...COLLATERALS_LOOKUP,
    {
      $sort: {
        order: 1,
      },
    },
    ...ITEMS_PAGINATION(paginatorOptions),
    {
      $group: groupingQuery,
    },
  ]);
  if (collateralSheets.length === 0) {
    const bbcDates = await BBCDateModel.aggregate<IBBCDateDoc>([
      {
        $match: {
          '_id': sheet.bbcDateId,
        },
      },
    ]);
    if (bbcDates.length === 0) {
      return [];
    }
    return [{
      _id: sheet._id,
      bbcDate: bbcDates[0].bbcDate,
      borrowerId: bbcDates[0].borrowerId,
      fileName: sheet.fileName,
      sheetName: sheet.sheetName,
      items: [],
    }];
  }
  return collateralSheets;
};

export const getCollateralListBySheetId = async (sheet: IBBCSheetDoc, paginatorOptions?: IPaginatorOptions) => {
  const enumKey = getEnumKeyByEnumValue(ECollaterals, sheet.dataType);

  return collateralMap[enumKey].aggregate([
    {
      $match: {
        'bbcSheetId': sheet._id,
      },
    },
    ...COLLATERALS_LOOKUP,
    {
      $sort: {
        order: 1,
      },
    },
    ...ITEMS_PAGINATION(paginatorOptions),
  ]);
};

export const findCollaterals = async (collateralsId: string, group: boolean, paginatorOptions?: IPaginatorOptions) => {
  const sheet = await findCollateralSheetsById(collateralsId);
  if (group) {
    return await getCollateralsBySheet(sheet, paginatorOptions);
  }
  return await getCollateralListBySheetId(sheet, paginatorOptions);
};

export const excludeNoDataColumns = (data: any[], columns: string[]) => {
  return data.map((dataRow) => _.pick(dataRow, columns));
}

export const addCustomerGroups = async (data: any[], borrowerId: string) => {
  const groupName = 'customers';
  const groups = await getBorrowerGroups(borrowerId, groupName);
  return data.map((dataRow) => {
    const foundGroup = groups[groupName].find((group) => group.items.includes(dataRow.customerTitle));
    return {
      ...dataRow,
      customerGroup: foundGroup ? foundGroup.groupName : dataRow.customerTitle,
    };
  });
}
