import mongoose from 'mongoose';

import { MODEL_NAMES } from './_models';
import { ILoanProductDoc } from './LoanProducts.model';
import { ELogActionType, ELogType } from './UserLog.model';
import { IBankDoc } from './Banks.model';
import { DeletedRecord } from './DeletedRecords.model';
import { createLog, getLogChanges } from '../db/user-logs.db';

export enum ELoanTransactionTypes {
  COLLECTION = 'COLLECTION',
  DISBURSEMENT = 'DISBURSEMENT',
  ADJUSTMENT = 'ADJUSTMENT',
  CHARGE = 'CHARGE',
}

export const LOAN_TRANSACTION_DOWNLOAD_FIELDS = [
  'date',
  'amount',
  'description',
  'reference',
  'customerId',
  'transactionType',
];

export const LOAN_TRANSACTION_FIELDS = [
  '_id',
  'date',
  'effectiveDate',
  'productId',
  'chargeId',
  'amount',
  'description',
  'reference',
  'customerId',
  'bankId',
  'transactionType',
  'balance',
  'floatedBalance',
  'loanPaymentId',
  'userId',
];

export interface ILoanTransactionUpload {
  date: Date;
  ledgerAccountCode: string;
  amount: number;
  description: string;
  reference: string;
  customerId: string;
  bankAccountNumber: string;
  transactionType: ELoanTransactionTypes;
}

export interface ILoanTransaction {
  order?: number;
  date: Date;
  effectiveDate: Date,
  productId: mongoose.Types.ObjectId;
  chargeId?: mongoose.Types.ObjectId;
  amount: number;
  description: string;
  reference: string;
  referenceRaw?: string;
  customerId: string;
  bankId: mongoose.Types.ObjectId;
  transactionType: ELoanTransactionTypes;
  balance: number;
  floatedBalance: number;
  loanPaymentId?: mongoose.Types.ObjectId;
  userId?: string;
  isSent?: boolean;
}

export interface ILoanTransactionDocWithProduct extends ILoanTransactionDoc {
  product: ILoanProductDoc;
}

export interface ILoanTransactionWithId extends ILoanTransaction {
  _id: mongoose.Types.ObjectId;
}

export type ILoanTransactionView = Omit<ILoanTransactionWithId, 'order'>;

export type ILoanTransactionViewWithBank = ILoanTransactionView & { bank: IBankDoc };

export interface ILoanTransactionDoc extends ILoanTransaction, mongoose.Document {
  _id: mongoose.Types.ObjectId;
  createdAt: Date;
  updatedAt: Date;
}

export interface ILoanTransactionLean extends ILoanTransaction {
  _id: mongoose.Types.ObjectId;
  createdAt: Date;
  updatedAt: Date;
}

export interface ILoanTransactionPlain extends Omit<ILoanTransaction, 'productId' | 'bankId' | 'loanPaymentId' | 'chargeId'> {
  _id: string;
  productId: string;
  bankId: string;
  loanPaymentId: string;
  chargeId: string;
}

export type TLoanTransactionModel = mongoose.Model<ILoanTransaction>;

export interface ILoanTransactionModel extends mongoose.Model<ILoanTransaction> {
  findByIdAndDeleteWithUserId(id: string, userId: string): Promise<void>;
}

export interface ITransactionsFilter {
  periodStart: Date;
  periodEnd: Date;
  borrowerId: string;
  productId: string;
  amount?: number;
  reference?: string;
}

export const LoanTransactionSchema = new mongoose.Schema<ILoanTransactionDoc, ILoanTransactionModel>(
  {
    userId: {
      type: String,
      required: false,
    },
    order: {
      type: Number,
      required: true,
      default: 0,
    },
    date: {
      type: Date,
      required: true,
    },
    effectiveDate: {
      type: Date,
      required: true,
    },
    productId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: MODEL_NAMES.loanProducts,
      required: true,
    },
    chargeId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: MODEL_NAMES.loanCharges,
      required: false,
    },
    amount: {
      type: Number,
      required: true,
    },
    description: {
      type: String,
      trim: true,
    },
    reference: {
      type: String,
      trim: true,
    },
    referenceRaw: {
      type: String,
      trim: true,
      required: false,
    },
    customerId: {
      type: String,
      trim: true,
    },
    bankId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: MODEL_NAMES.banks,
      required: false,
    },
    transactionType: {
      type: String,
      enum: Object.values(ELoanTransactionTypes),
      required: true,
    },
    balance: {
      type: Number,
    },
    floatedBalance: {
      type: Number,
    },
    loanPaymentId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'loan_payments',
    },
    isSent: {
      type: Boolean,
      required: false,
    },
  },
  { timestamps: true },
);

LoanTransactionSchema.statics.findByIdAndDeleteWithUserId = async function(id, userId) {
  const foundDoc = await this.findById(id).lean();
  if (!foundDoc) {
    return this.findByIdAndDelete(id);
  }
  const deletedRecord = new DeletedRecord({
    DBCollection: 'loan_transactions',
    recordId: id,
    record: foundDoc,
  });
  await deletedRecord.save();

  await createLog({
    action: ELogActionType.DELETE,
    logType: ELogType.LOAN_TRANSACTION,
    userId: userId,
    recordCollection: 'loan_transactions',
    recordId: id,
    details: {},
  });

  return this.findByIdAndDelete(id);
};


LoanTransactionSchema.pre(/^(save)/, async function() {
  const myDoc: any = this;
  await createLog({
    action: ELogActionType.CREATE,
    logType: ELogType.LOAN_TRANSACTION,
    userId: myDoc.userId,
    recordCollection: 'loan_transactions',
    recordId: myDoc._id,
    details: {},
  });
});

LoanTransactionSchema.pre(/^(updateOne|updateMany|findOneAndUpdate)/, async function() {
  const myDoc: any = this;
  const update = myDoc._update;
  const conditions = myDoc._conditions;
  if (update
    && update.hasOwnProperty('userId')
    && conditions
    && conditions.hasOwnProperty('_id')
  ) {
    const oldDoc = await myDoc.model.findById(conditions._id);
    let changes = {};
    if (oldDoc) {
      changes = getLogChanges(oldDoc, update);
    }
    await createLog({
      action: ELogActionType.EDIT,
      logType: ELogType.LOAN_TRANSACTION,
      userId: update.userId,
      recordCollection: 'loan_transactions',
      recordId: conditions._id,
      details: {
        changes,
      },
    });
  }
});

export const LoanTransaction: ILoanTransactionModel = mongoose.model<ILoanTransactionDoc, ILoanTransactionModel>(MODEL_NAMES.loanTransactions, LoanTransactionSchema);
