import mongoose from 'mongoose';

import { createLog, ICreateLogParams } from '../db/user-logs.db';
import { ELogType, IUserLogDoc, IUserLogWithLookup, UserLog } from '../models/UserLog.model';
import { DeletedRecord } from '../models/DeletedRecords.model';

import { BanksService } from './banks.service';
import { BorrowersDB } from './borrowers.db';
import { LoanChargesService } from './loan-charges.service';

export interface IUserLogFilter {
  userId: string;
  borrowerId: string;
  periodStart: Date;
  periodEnd: Date;
}

const createLogs = true;

export class UserLogsService {

  constructor(
    private readonly banksService: BanksService,
    private readonly borrowerDB: BorrowersDB,
    private readonly loanChargesService: LoanChargesService,
  ) {
  }

  async createUserLog(params: ICreateLogParams) {
    if (createLogs) {
      await createLog(params);
    }
  }

  async filterByBorrower(logs: IUserLogWithLookup[], borrowerId: string): Promise<IUserLogWithLookup[]> {
    const products = await this.loanChargesService.getBorrowersLoanProducts(borrowerId);
    const productIds = products.map((product) => <string>product._id.toString());
    return logs.filter((r) => {
      if (r.logType === ELogType.LOAN_TRANSACTION && r.record) {
        return productIds.includes(r.record.productId.toString());
      }
      if (r.details && r.details.hasOwnProperty('borrowerId')) {
        if (r.details.borrowerId && r.details.borrowerId === borrowerId) {
          return true;
        }
      }
      return false;
    });
  }

  async replaceBorrowers(logs: IUserLogWithLookup[]) {
    const convertToMap = (arr: any[]): Map<string, string> => {
      return arr.reduce((acc, item) => {
        acc.set(item._id.toString(), item.name);
        return acc;
      }, new Map<string, string>());
    };
    const borrowers = await this.borrowerDB.getAllBorrowers();
    const borrowersMap = convertToMap(borrowers);

    const products = await this.loanChargesService.getAllLoanProducts();
    const productsMap = convertToMap(products);

    const banks = await this.banksService.getBanks();
    const banksMap = convertToMap(banks);

    const replacedFields = {
      borrowerId: borrowersMap,
      productId: productsMap,
      bankId: banksMap,
    };

    return logs.map((r) => {
      Object.keys(replacedFields).forEach((key) => {
        if (r.details && r.details.hasOwnProperty(key) && r.details[key]) {
          r.details[key] = `${replacedFields[key].get(r.details[key].toString())} (${r.details[key].toString()})`;
        }
        if (r.record && r.record.hasOwnProperty(key) && r.record[key]) {
          r.record[key] = `${replacedFields[key].get(r.record[key].toString())} (${r.record[key].toString()})`;
        }
      });
    });
  }

  async performDynamicLookup(record: IUserLogDoc): Promise<IUserLogWithLookup> {
    try {
      const { _id, recordCollection } = record;
      if (!recordCollection) {
        return record;
      }

      const pipeline = [
        {
          $match: {
            _id,
          },
        },
        {
          $lookup: {
            from: recordCollection,
            localField: 'recordId',
            foreignField: '_id',
            as: 'record',
          },
        },
        {
          $unwind: {
            path: '$record',
            preserveNullAndEmptyArrays: true,
          },
        },
      ];

      const result = await UserLog.aggregate(pipeline);
      if (result.length > 0 && result[0]) {
        const foundRecord = result[0].record;
        if (!foundRecord) {
          const deletedRecord = await DeletedRecord
            .findOne({ DBCollection: recordCollection, recordId: record.recordId })
            .lean();
          if (deletedRecord) {
            result[0].record = deletedRecord.record;
          }
        }
        return result[0];
      }
      return record;
    } catch (error) {
      console.error('Error performing dynamic lookup:', error);
      throw error;
    }
  }

  async getLogs(filter: IUserLogFilter) {
    const logs = await UserLog.aggregate<IUserLogDoc>([
      {
        $match: {
          $and: [
            { 'timestamp': { $gte: filter.periodStart } },
            { 'timestamp': { $lte: filter.periodEnd } },
            filter.userId ? { 'userId': new mongoose.Types.ObjectId(filter.userId) } : {},
          ],
        },
      },
      {
        $sort: {
          'timestamp': 1,
        },
      },
    ]);
    const logsWithRecord = await Promise.all(logs.map(async (log) => await this.performDynamicLookup(log)));
    const filteredByBorrowers = !filter.borrowerId
      ? logsWithRecord.slice()
      : await this.filterByBorrower(logsWithRecord, filter.borrowerId);
    await this.replaceBorrowers(filteredByBorrowers);
    return filteredByBorrowers;
  }

  async getDocumentLogs(documentId: string) {
    const logs = await UserLog.aggregate<IUserLogDoc>([
      {
        $match: {
          $and: [
            { 'recordCollection': 'loan_transactions' },
            { 'recordId': new mongoose.Types.ObjectId(documentId) },
          ],
        },
      },
      {
        $sort: {
          'timestamp': 1,
        },
      },
    ]);
    return await Promise.all(logs.map(async (log) => await this.performDynamicLookup(log)));
  }
}
