import { Response } from "express";
import clientDB from "../models/client.model";
import log from "../config/log";
import CONSTANTS from "../config/constants";
import propertyDB from "../models/property.model";
import CustomRequest from "../types/requestType";
import extraChargeDB from "../models/extraCharges.model";
import getExtraChargeName from "../utils/getExtraChargeName";
import propertiesTypes from "../schemas/property.schema";
import staffDB from "../models/staff.model";
import { isUserPartner } from "../utils/isUserPartner";
import { logActivity, logUtilityBillActivity } from "../utils/logActivity";
import getDueDescription from "../utils/getDueDescription";
import getDueName from "../utils/getDueName";
import fsPromises from "fs/promises";
import fs from "fs";
import { isPrivilegedStaff } from "../utils/isPrivilegedStaff";
import occupancyDB from "../models/occupancy.model";
import roomDB from "../models/room.model";
import generateLedgerReferenceId from "../utils/generateLedgerReferenceId";
import moveOutDB from "../models/moveOut.model";
import moveOutDuesDB from "../models/moveOutDues.model";
import moment from "moment";
import ledgerDB from "../models/ledger.model";
import duesDB from "../models/dues.model";
import adjustExcessPayments, { adjustExcessPaymentsForMovedOutX } from "../utils/adjustExcessPayment";
import electricityDB from "../models/electricity.model";
import flatDB from "../models/flat.model";

const extraCharge: any = {};

extraCharge.AddExtraCharge = async (req: CustomRequest, res: Response) => {
  const C = "Extra Charge Controller";
  const F = "AddExtraCharge";

  try {
    let { type, repetitionType, amount, propertyList } = req.body;

    const userType = req.userType;

    let clientId = req.id;

    let propNames = [];

    if (propertyList.length === 0) {
      log.info(
        `[${C}], [${F}], Type [${type}], Repetition Type [${repetitionType}], Amount [${amount}], Property List [${JSON.stringify(propertyList)}], Property List Length [${propertyList.length}], Staff Id [${req.id}], Empty Property List`
      );
      return res
        .status(400)
        .json({ msg: "Please select atleast one property", isSuccess: false });
    }

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Type [${type}], Repetition Type [${repetitionType}], Amount [${amount}], Property List [${JSON.stringify(propertyList)}] Property List Length [${propertyList.length}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Type [${type}], Repetition Type [${repetitionType}], Property List [${JSON.stringify(propertyList)}] Amount [${amount}], Property List Length [${propertyList.length}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Type [${type}], Repetition Type [${repetitionType}], Amount [${amount}], Property List [${JSON.stringify(propertyList)}], Property List Length [${propertyList.length}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Type [${type}], Repetition Type [${repetitionType}], Amount [${amount}], Property List [${JSON.stringify(propertyList)}], Property List Length [${propertyList.length}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    for (const propId of propertyList) {
      try {
        const isExists = await extraChargeDB.getByPropAndType({
          clientId,
          propId,
          type,
        });

        if (isExists) {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Property Id [${propId}], Already Linked with the same extra charge type.`
          );

          propertyList = propertyList.filter(
            (propertyId: number) => propertyId !== propId
          );

          continue;
        }
      } catch (error: any) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Type [${type}], Repetition Type [${repetitionType}], Amount [${amount}], Error while checking linked properties: ${
            error?.message || error
          }`
        );

        return res.status(400).json({
          msg: "Unable to link property at this moment. Please try again later.",
          isSuccess: false,
        });
      }
    }

    if (propertyList.length === 0) {
      log.info(
        `[${C}], [${F}], Type [${type}], Repetition Type [${repetitionType}], Amount [${amount}], Property List Length [${propertyList.length}], Staff Id [${req.id}], Property already linked with the same extra charge type`
      );
      return res.status(400).json({
        msg: "Property already linked with the same extra charge type",
        isSuccess: true,
      });
    }

    const chargeName = getExtraChargeName(type);

    const extraChargeId = await extraChargeDB.create({
      type,
      repetitionType,
      amount,
      name: chargeName,
      clientId,
    });

    for (const propId of propertyList) {
      try {
        const propertyDetail = await propertyDB.getById({ id: propId });

        propNames.push(propertyDetail.name);

        if (!propertyDetail) {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Type [${type}], Repetition Type [${repetitionType}], Amount [${amount}], Property Id [${propId}], Property Not Found`
          );
          continue;
        }

        await extraChargeDB.addProperty({
          extraChargeId,
          propId: propId,
          clientId: propertyDetail?.clientId,
        });

        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Type [${type}], Repetition Type [${repetitionType}], Amount [${amount}], Property Id [${propId}], Extra charge Id [${extraChargeId}], Prop Id [${propertyDetail?.id}], Property Added`
        );
      } catch (error: any) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Type [${type}], Repetition Type [${repetitionType}], Amount [${amount}], Error while linking properties: ${
            error?.message || error
          }`
        );

        return res.status(400).json({
          msg: "Unable to link property at this moment. Please try again later.",
          isSuccess: false,
        });
      }
    }

    await logActivity(
      Number(req.userType),
      Number(req.id),
      Number(req.parentClientId!),
      Number(req.platform),
      0,
      CONSTANTS.ACTIVITY_TYPES.ADD_EXTRA_CHARGES,
      amount,
      getDueName(type),
      propNames,
      null,
      null
    );

    return res.status(200).json({
      msg: "Extra charge added successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

extraCharge.EditExtraCharge = async (req: CustomRequest, res: Response) => {
  const C = "Extra Charge Controller";
  const F = "EditExtraCharge";

  try {
    const { repetitionType, amount, id } = req.body;

    const userType = req.userType;

    let clientId = req.id;

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Repetition Type [${repetitionType}], Amount [${amount}], Extra Charge Id [${id}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Repetition Type [${repetitionType}], Amount [${amount}], Extra Charge Id [${id}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Repetition Type [${repetitionType}], Amount [${amount}], Extra Charge Id [${id}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Repetition Type [${repetitionType}], Amount [${amount}], Extra Charge Id [${id}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const isExists = await extraChargeDB.getById({ id });

    if (!isExists) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Repetition Type [${repetitionType}], Amount [${amount}], Extra Charge Id [${id}], No Extra Charge Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    await extraChargeDB.update({
      repetitionType,
      amount,
      clientId,
      id,
    });

    return res.status(200).json({
      msg: "Extra charge updated successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

extraCharge.ListForClient = async (req: CustomRequest, res: Response) => {
  const C = "Extra Charge Controller";
  const F = "ListForClient";

  try {
    const { pageNum, propId } = req.query;

    const userType = req.userType;

    let clientId = req.id;

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Page Num [${pageNum}], Prop Id [${propId}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Page Num [${pageNum}], Prop Id [${propId}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Page Num [${pageNum}], Prop Id [${propId}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Page Num [${pageNum}], Prop Id [${propId}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let list = [];

    const limit = 10;
    if (Number(propId)) {
      list = await extraChargeDB.getByClientIdAndPropId({
        clientId,
        propId,
        pageNum,
        limit,
      });
    } else {
      list = await extraChargeDB.getByClientId({
        clientId,
        pageNum,
        limit,
      });
    }

    if (!list) list = [];

    for (const index in list) {
      const propList = await extraChargeDB.getProperties({
        extraChargeId: list[index]?.id,
      });
      if (!propList) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Page Num [${pageNum}], Prop Id [${propId}], Extra Charge Id [${list[index]?.id}], No Property Found`
        );
      }

      list[index].propertyList = propList || [];
    }

    for (const index in list) {
      if(list[index].type === CONSTANTS.EXTRA_CHARGE_TYPES.ELECTRICITY){
        let extracharge = await extraChargeDB.getById({ 
          id: list[index]?.id
        })
        if(extracharge.amount == 0){
          list[index].electricityChargesType = CONSTANTS.CHARGE_TYPE.AMOUNT;
        }
        else{
          list[index].electricityChargesType = CONSTANTS.CHARGE_TYPE.UNIT;
        }
      }
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Page Num [${pageNum}], Prop Id [${propId}], Extra Charges List Sent Successfully`
    );

    return res.status(200).json({
      msg: "Extra charges list has been sent successfully",
      data: list,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

extraCharge.ToggleProperty = async (req: CustomRequest, res: Response) => {
  const C = "Extra Charge Controller";
  const F = "ToggleProperty";

  try {
    const { toggleValue, propId, extraChargeId } = req.body;

    const userType = req.userType;

    let clientId = req.id;

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Toggle Value [${toggleValue}], Property Id [${propId}], extraChargeId [${extraChargeId}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Toggle Value [${toggleValue}], Property Id [${propId}], extraChargeId [${extraChargeId}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Toggle Value [${toggleValue}], Property Id [${propId}], extraChargeId [${extraChargeId}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Toggle Value [${toggleValue}], Property Id [${propId}], extraChargeId [${extraChargeId}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const property = await propertyDB.getById({ id: propId });
    if (!property) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Property Id [${propId}],  Toggle Value [${toggleValue}], Property Id [${propId}], extraChargeId [${extraChargeId}], No Property Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const extraCharge = await extraChargeDB.getById({ id: extraChargeId });

    if (!extraCharge) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Property Id [${propId}],  Toggle Value [${toggleValue}], Property Id [${propId}], extraChargeId [${extraChargeId}], No Extra Charge Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (toggleValue === CONSTANTS.TOGGLES.LINK) {
      const isExists = await extraChargeDB.getByPropAndType({
        clientId,
        propId,
        type: extraCharge.type,
      });

      if (isExists) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Property Id [${propId}],  Toggle Value [${toggleValue}], Property Id [${propId}], extraChargeId [${extraChargeId}], Already Linked to this property.`
        );

        return res.status(400).json({
          msg: `Property already linked`,
          isSuccess: false,
        });
      }

      await extraChargeDB.addProperty({ clientId, propId, extraChargeId });

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Property Id [${propId}],  Toggle Value [${toggleValue}], Property Id [${propId}], extraChargeId [${extraChargeId}], Property linked Successfully`
      );
    } else {
      await extraChargeDB.removeProperty({ clientId, propId, extraChargeId });
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Property Id [${propId}],  Toggle Value [${toggleValue}], Property Id [${propId}], extraChargeId [${extraChargeId}], Property unlinked Successfully`
      );
    }

    return res.status(200).json({
      msg: `Property ${
        toggleValue === CONSTANTS.TOGGLES.LINK ? "linked" : "unlinked"
      } successfully`,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

extraCharge.TogglePropertyX = async (req: CustomRequest, res: Response) => {
  const C = "Extra Charge Controller";
  const F = "TogglePropertyX";

  try {
    const { linkedProps, unlinkedProps, extraChargeId } = req.body;

    const userType = req.userType;

    log.info(
      `[${C}], [${F}], Linked Props [${JSON.stringify(linkedProps)}], Unlinked Props [${JSON.stringify(unlinkedProps)}], extraChargeId [${extraChargeId}]`
    );

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`}, ${isPartner ? "Partner Requesting" : "Client Requesting"
        }...`
      );
    } else {
      const staff = await staffDB.getById({
        id: req.id,
      });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);

        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Requesting....`
      );
    }

    const extraCharge = await extraChargeDB.getById({ id: extraChargeId });

    if (!extraCharge) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Extra Charge Id [${extraChargeId}], No Extra Charge Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (linkedProps && Array.isArray(linkedProps) && linkedProps.length > 0) {
      for (let propId of linkedProps) {
        const property = await propertyDB.getById({ id: propId });
        if (!property) {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Property Id [${propId}], Property Id [${propId}], extraChargeId [${extraChargeId}], No Property Found, Skipping`
          );

          continue;
        }

        const isExists = await extraChargeDB.getByPropAndType({
          clientId,
          propId,
          type: extraCharge.type,
        });

        if (!isExists) {
          await extraChargeDB.addProperty({ clientId, propId, extraChargeId });
        } else {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Property Id [${propId}], extraChargeId [${extraChargeId}], Already Linked to this property, Skipping`
          );
        }
      }
    }

    if (unlinkedProps && Array.isArray(unlinkedProps) && unlinkedProps.length > 0) {
      for (let propId of unlinkedProps) {
        await extraChargeDB.removeProperty({ clientId, propId, extraChargeId });
      }
    }

    log.info(`[${C}], [${F}], Client Id [${clientId}], Extra Charge Id [${extraChargeId}], Properties Updated Successfully`);

    return res.status(200).json({
      msg: `Linked properties updated successfully`,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

extraCharge.LinkedProperties = async (req: CustomRequest, res: Response) => {
  const C = "Extra Charge Controller";
  const F = "LinkedProperties";

  try {
    const { extraChargeId } = req.params;

    const userType = req.userType;

    let clientId = req.id;

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Extra Charge Id [${extraChargeId}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Extra Charge Id [${extraChargeId}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Extra Charge Id [${extraChargeId}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Extra Charge Id [${extraChargeId}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let linkedProperties = await extraChargeDB.getLinkedProperties({
      extraChargeId,
      clientId,
    });
    const allProperties = await propertyDB.getActivePropIdsByClientId({
      clientId,
      status: CONSTANTS.PROPERTY_STATUS.ACTIVE,
    });
    const combinedPropList = allProperties.map((property: propertiesTypes) => {
      let isExists = false;
      if (linkedProperties) {
        isExists = linkedProperties.find(
          (prop: propertiesTypes) => prop.id === property.id
        );
      }
      return {
        id: property.id,
        name: property.name,
        isLinked: isExists ? 1 : 0,
      };
    });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Extra Charge Id [${extraChargeId}], Linked Properties sent successfully`
    );

    return res.status(200).json({
      msg: `Linked Properties sent successfully`,
      data: combinedPropList,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

extraCharge.ListForWeb = async (req: CustomRequest, res: Response) => {
  const C = "Extra Charge Controller";
  const F = "ListForWeb";

  try {
    const { propId } = req.query;

    const userType = req.userType;

    let clientId = req.id;
    const limit = 50;
    const pageNum = 1;

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Page Num [${pageNum}], Prop Id [${propId}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Page Num [${pageNum}], Prop Id [${propId}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Page Num [${pageNum}], Prop Id [${propId}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Page Num [${pageNum}], Prop Id [${propId}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let list = [];

    if (Number(propId)) {
      list = await extraChargeDB.getByClientIdAndPropId({
        clientId,
        propId,
        pageNum,
        limit,
      });
    } else {
      list = await extraChargeDB.getByClientId({
        clientId,
        pageNum,
        limit,
      });
    }

    if (!list) list = [];

    for (const index in list) {
      const propList = await extraChargeDB.getProperties({
        extraChargeId: list[index]?.id,
      });
      if (!propList) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Page Num [${pageNum}], Prop Id [${propId}], Extra Charge Id [${list[index]?.id}], No Property Found`
        );
      }

      list[index].propertyList = propList || [];
    }

    for (const index in list) {
      if(list[index].type === CONSTANTS.EXTRA_CHARGE_TYPES.ELECTRICITY){
        let extracharge = await extraChargeDB.getById({ 
          id: list[index]?.id
        })
        if(extracharge.amount == 0){
          list[index].electricityChargesType = CONSTANTS.CHARGE_TYPE.AMOUNT;
        }
        else{
          list[index].electricityChargesType = CONSTANTS.CHARGE_TYPE.UNIT;
        }
      }
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Page Num [${pageNum}], Prop Id [${propId}], Extra Charges List Sent Successfully`
    );

    return res.status(200).json({
      msg: "Extra charges list has been sent successfully",
      data: list,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

extraCharge.Delete = async (req: CustomRequest, res: Response) => {
  const C = "Extra Charge Controller";
  const F = "Delete";

  try {
    const { id } = req.body;

    const userType = req.userType;

    let {isPartner, clientId} = await isUserPartner (Number(userType), Number(req.id));

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(`[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`}, Extra Charge Id [${id}], ${isPartner ? `Partner` : `Client`} Requesting....`)
    } else {
      const staff = await staffDB.getById({
        id: req.id,
      });

      if (!staff) {
        log.info(`[${C}], [${F}], Client Id [${clientId}], Staff Id [${req.id}], No Staff Found`);

        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }

      if (
        staff.role !== CONSTANTS.STAFF_ROLES.ADMIN && 
        staff.role !== CONSTANTS.STAFF_ROLES.WARDEN && 
        staff.role !== CONSTANTS.STAFF_ROLES.FINANCE_ADMIN && 
        staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE
      ) {
        log.info(`[${C}], [${F}], User Type [${userType}], User Id [${req.id}], Staff Not Authorized`);
  
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }

      log.info(`
        [${C}], [${F}], Client Id [${clientId}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Requesting....`
      );
    }

    const extraCharge = await extraChargeDB.getById({ id });
    if (!extraCharge) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Extra Charge Id [${id}], No Extra Charge Found`
      );
      return res.status(400).json({ 
        msg: CONSTANTS.MSG.INVALID_REQUEST, 
        isSuccess: false 
      });
    }

    await extraChargeDB.deleteFromProperties({ extraChargeId: id });
    await extraChargeDB.delete({ id });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Extra Charge Id [${id}], Extra Charge Deleted Successfully`
    );
    return res.status(200).json({
      msg: "Extra charge has been deleted successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

extraCharge.AddUtilityBill = async (req: CustomRequest, res: Response) => {
  const C = "Extra Charge Controller";
  const F = "AddUtilityBill";

  const file = req.file as Express.Multer.File;
  const removeTmpImages = async () => {
    try {
      await fsPromises.unlink(file.path);
    } catch (err) {
      log.info(
        `[${C}], [${F}], Error in Removing Temp Image, Error [${JSON.stringify(
          err
        )}]`
      );
    }
  };

  try {
    let { propId, amount = 0, roomFlatId, startDate, endDate, utilityType  } = req.body;

    propId = Number(propId);
    amount = Number(amount); 
    
    log.info(
      `[${C}], [${F}], Prop Id [${propId}], Current Bill [${amount}], Room/Flat Id [${roomFlatId}], Start Date [${startDate}], End Date [${endDate}], Utility Type [${utilityType}], Is File Uploaded ${file ? `[Yes], File ${JSON.stringify(file)}` : "[No]"}, Client Requested....`
    );
    
    const userType = req.userType;
    let recordedBy = "";

    let {isPartner, clientId} = await isUserPartner (
      Number(userType), 
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(`[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`}, ${isPartner ? `Partner` : `Client`} Requesting....`)
    } else {
      const staff = await staffDB.getById({id: req.id});
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Not Found`);
  
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }

      recordedBy = staff.name;

      let isStaffAllowed = await isPrivilegedStaff(staff.role, 1);

      if (!isStaffAllowed) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Not Authorized`);

        if (file) {
          await fsPromises.unlink(file.path);
        }
  
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Requesting....`
      );
    }

    const client = await clientDB.getById({id: clientId});
    if (!client) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Client Not Found`);

      if (file) {
        await fsPromises.unlink(file.path);
      }

      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    if (userType === CONSTANTS.USER_TYPE.CLIENT) {
      recordedBy = client.name;
    }

    const property = await propertyDB.getById({ id: propId });
    if (!property) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], No Property Found`
      );

      if (file) {
        await fsPromises.unlink(file.path);
      }

      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let tenants;
    roomFlatId = String(roomFlatId);
    let roomFlatIds = roomFlatId.split(",");

    tenants = await occupancyDB.getTenantsMoveInAndMoveOutDate({
      clientId,
      propId,
      roomIds: roomFlatIds,
      startDate,
      endDate,
    });

    let noOfTenant = tenants.length;

    if (noOfTenant === 0) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomFlatId}], Start Date [${startDate}], End Date [${endDate}], No of Tenants [${noOfTenant}], No Tenants In The Rooms`
      );

      if (file) {
        await fsPromises.unlink(file.path);
      }

      return res.status(400).json({
        msg: "Cannot add utility bill for vacant rooms",
        isSuccess: false,
      });
    }

    let roomFlatName = "";

    let singleRoomFlatId = roomFlatIds[0];
    const room = await roomDB.getById({id: singleRoomFlatId});
    roomFlatName = room?.roomNum || "";
    if (property.type == CONSTANTS.PROPERTY_TYPE.FLAT) {
        let room = await roomDB.getById({id:roomFlatIds[0]});
        singleRoomFlatId = room?.flatId;
        const flat = await flatDB.getById({id: singleRoomFlatId});
        roomFlatName = flat?.name || "";
    }

    const billId = await extraChargeDB.addUtilityBill({ //Function To be created
      amount: amount,
      roomFlatId: Number(singleRoomFlatId),
      clientId,
      propId,
      startDate,
      endDate,
      noOfTenant,
      type: utilityType,
    });


    if (tenants.length > 0) {
      const tenantDetails = tenants.map((tenant: any) => {
        const effectiveMoveIn = new Date(tenant.effectiveMoveIn);
        const effectiveMoveOut = new Date(tenant.effectiveMoveOut);

        const daysStayed =
          (effectiveMoveOut.getTime() - effectiveMoveIn.getTime()) /
            (1000 * 60 * 60 * 24) +
          1;

        return {
          tenantId: tenant.tenantId,
          daysStayed,
        };
      });
      const totalDays = tenantDetails.reduce(
        (sum: any, tenant: any) => sum + tenant.daysStayed,
        0
      );

      for (const tenant of tenantDetails) {
        let billShare = Math.ceil(
          parseFloat(((tenant.daysStayed / totalDays) * amount).toFixed(2))
        );

        let occupancyDetail = await occupancyDB.getByTenantIdAndClientId({
          tenantId: Number(tenant.tenantId),
          clientId
        });

        let dueDescription = getDueDescription(
          utilityType,
        );

        const referenceId = await generateLedgerReferenceId({ clientId });

        if (false == occupancyDetail) {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant [${tenant.tenantId}], Tenant Stayed [${tenant.daysStayed}], Tenant Share [${billShare}], Occupancy Status [Moved Out], Tenant Electricity Due`
          );

          let tenantInMoveOut = await moveOutDB.getMovedOutTenants({
            tenantId: Number(tenant.tenantId)
          });
          if (tenantInMoveOut){
            let prevBalance = await ledgerDB.getPreviousBalance({
              tenantId: tenant.tenantId,
              clientId,
            });
            //Added Due to moveOut Due pallav
            await moveOutDuesDB.addWithStartEndDate({
              tenantId: Number(tenant.tenantId),
              amount: billShare,
              moveOutId: tenantInMoveOut.id,
              roomId: tenantInMoveOut.roomId,
              propId,
              clientId,
              rentStartDate: startDate,
              rentEndDate: endDate,
              dueDate: moment().format("YYYY-MM-DD HH:mm:ss"),
              type: Number(utilityType),
              balance: billShare,
              ledgerReferenceId: referenceId,
            });
            await ledgerDB.add({
              tenantId: Number(tenant.tenantId),
              roomId: occupancyDetail.roomId,
              propId,
              clientId,
              amount: billShare,
              balance: billShare,
              referenceId,
              transactionId: null,
              type: Number(utilityType),
              rentStartDate: startDate,
              rentEndDate: endDate,
              dueDate: moment().format("YYYY-MM-DD HH:mm:ss"),
              description: `${dueDescription} for ${moment(startDate).format("DD MMM YY")} to ${moment(endDate).format("DD MMM YY")}`,
            });

            if (prevBalance && prevBalance < 0) {
              await adjustExcessPaymentsForMovedOutX({
                tenantId: tenant.tenantId,
                clientId: clientId,
                amountPaid: Math.abs(Number(prevBalance)),
                ledgerReferenceId: referenceId,
                markedFromSecurity: 1,
                recordedBy: recordedBy,
              });
            }
          } else{
            log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant [${tenant.tenantId}], Tenant not found in either Occupancy or MoveOut tables`)
          }
        } else {
          let prevBalance = await ledgerDB.getPreviousBalance({
            tenantId: tenant.tenantId,
            clientId,
          });

          await duesDB.addWithStartEndDate({
            tenantId: Number(tenant.tenantId),
            amount: billShare,
            occupancyId: occupancyDetail.id,
            roomId: occupancyDetail.roomId,
            propId,
            clientId,
            rentStartDate: startDate,
            rentEndDate: endDate,
            dueDate: moment().format("YYYY-MM-DD HH:mm:ss"),
            type: Number(utilityType),
            balance: billShare,
            ledgerReferenceId: referenceId,
          });

          await ledgerDB.add({
            tenantId: Number(tenant.tenantId),
            roomId: occupancyDetail.roomId,
            propId,
            clientId,
            amount: billShare,
            balance: billShare,
            referenceId,
            transactionId: null,
            type: Number(utilityType),
            rentStartDate: startDate,
            rentEndDate: endDate,
            dueDate: moment().format("YYYY-MM-DD HH:mm:ss"),
            description: `${dueDescription} for ${moment(startDate).format("DD MMM YY")} to ${moment(endDate).format("DD MMM YY")}`,
          });

          if (prevBalance && prevBalance < 0) {
            await adjustExcessPayments({
              tenantId: tenant.tenantId,
              clientId: clientId,
              amountPaid: Math.abs(Number(prevBalance)),
              ledgerReferenceId: referenceId,
            });
          }

          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant [${tenant.tenantId}], Tenant Stayed [${tenant.daysStayed}], Tenant Share [${billShare}], Occupancy Status [Occupied]`
          );
        }

        const dueName = await getDueName(utilityType);
        await logActivity(
          req.userType!,
          Number(req.id!),
          Number(req.parentClientId!),
          Number(req.platform),
          tenant.tenantId,
          CONSTANTS.ACTIVITY_TYPES.ADD_DUES,
          billShare,
          dueName,
          null,
          null,
          null
        );
      }

      await logUtilityBillActivity(
        req.userType!,
        Number(req.id),
        Number(req.parentClientId!),
        req.platform!,
        CONSTANTS.ACTIVITY_TYPES.UTILITY_BILL_ADDED,
        utilityType,
        noOfTenant,
        amount,
        propId,
        roomFlatName,
      )

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Current Total Bill [${amount}], Room/Flat Id [${roomFlatId}], Prop Id [${propId}], Bill generated for tenants No of Tenants [${noOfTenant}]`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Current Total Bill [${amount}], Room/Flat Id [${roomFlatId}], Prop Id [${propId}], No Tenant found in room`
      );
    }

    if (file) {
      const folderName = `UtilityBill_${billId}`;
      const folderPath = `uploads/documents/client_${clientId}/${folderName}`;
      const fileExtension = file.mimetype.split("/")[1];
      const filename = `uploadedDoc.${fileExtension}`;

      if (!fs.existsSync(folderPath)) {
        await fsPromises.mkdir(folderPath, { recursive: true });
      }

      const oldPath = `uploads/tmp/${file.filename}`;
      const newPath = `${folderPath}/${filename}`;
      const url = `${process.env.UPLOAD_PATH}/documents/client_${clientId}/${folderName}/${filename}`;

      await fsPromises.copyFile(oldPath, newPath);

      await extraChargeDB.updateUploadedDoc({
        id: billId,
        docs: url,
      });

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Utility Bill Id [${billId}], Old Path [${oldPath}], New Path [${newPath}], Url [${url}], Document Saved in DB.`
      );
    }

    log.info(
      `[${C}], [${F}], Prop Id [${propId}], Current Bill [${amount}], Room/Flat Id [${roomFlatId}], Start Date [${startDate}], End Date [${endDate}], Utility Type [${utilityType}], Is File Uploaded ${file ? `[Yes], File ${JSON.stringify(file)}` : "[No]"}, Utility Bill Added Successfully`
    );

    return res.status(200).json({
      msg: "Bill amount added & tenant dues created successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    if (file) {
      await removeTmpImages();
    }
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

extraCharge.ListBillForClient = async (req: CustomRequest, res: Response) => {
  const C = "Extra Charge Controller";
  const F = "ListBillForClient";

  try {

    const { propId, propType, utilityType } = req.query;

    log.info(`[${C}], [${F}], Prop Id [${propId}], Prop Type [${propType}], Utility Type [${utilityType}]`);

    const userType = req.userType;

    let {isPartner, clientId} = await isUserPartner (
      Number(userType), 
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(`[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`}, ${isPartner ? `Partner` : `Client`} Requesting....`)
    } else {
      const staff = await staffDB.getById({id: req.id});
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Not Found`);
  
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }

      let isStaffAllowed = await isPrivilegedStaff(staff.role, 1);
      if (!isStaffAllowed) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Not Authorized`);
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Requesting....`
      );
    }

    let data = [];
    if (CONSTANTS.PROPERTY_TYPE.PG == Number(propType)) {
      let allFloors = await electricityDB.getAllFloors({ propId });
      let allRooms = await electricityDB.getAllRooms({ propId });
      let previousBill = Number(utilityType) === CONSTANTS.DUES_TYPES.ELECTRICITY 
        ? await electricityDB.getPreviousBillForRoom({ propId }) 
        : await extraChargeDB.getPreviousBillForRoom({ propId, type: utilityType });
      let roomTenantCount = await occupancyDB.getTenantCountByPropId({clientId, propId});

      const tenantCountMap: any = {};
      if (Array.isArray(roomTenantCount)) {
        roomTenantCount.forEach((row: any) => {
          tenantCountMap[row.roomId] = row.tenantCount;
        });
      }

      let stats = allFloors.map((floor: any) => {
        let roomsOnFloor = allRooms.filter(
          (room: any) => room.floor === floor.floor
        );
        let rooms = [];
        let floorId = 1111;
        if (floor.floor != "G") {
          floorId = Number(floor.floor);
        }
        let floorObj: any = {
          name: `Floor ${floor.floor}`,
          id: floorId,
          roomCount: floor.roomCount,
        };
        for (let item of roomsOnFloor) {
          let prevBill = previousBill.filter(
            (curr: any) => item.roomNum === curr.roomNumber
          );
          rooms.push({
            roomNum: item.roomNum,
            id: item.id,
            prevBill: prevBill,
            amenities: item.amenities,
            tenantCount: tenantCountMap[item.id] || 0 
          });
          floorObj.rooms = rooms;
        }

        return floorObj;
      });
      data = stats;
    } else {
      let allFlats = await electricityDB.getAllFlats({ propId });
      let allRooms = await electricityDB.getAllRoomsForFlat({ propId });
      let previousBill = CONSTANTS.DUES_TYPES.ELECTRICITY 
        ? await electricityDB.getPreviousBill({ propId }) 
        : await extraChargeDB.getPreviousBill({ propId, type: utilityType });
      
      let roomTenantCount = await occupancyDB.getTenantCountByPropId({clientId, propId});

      const tenantCountMap: any = {};
      if (Array.isArray(roomTenantCount)) {
        roomTenantCount.forEach((row: any) => {
          tenantCountMap[row.roomId] = row.tenantCount;
        });
      }

      let stats = allFlats.map((flat: any) => {
        let roomsOnFlat = allRooms.filter(
          (room: any) => room.flatId === flat.id
        );

        let rooms = roomsOnFlat.map((room: any) => {
          // let prevBill = previousBill.filter(
          //   (bill: any) => bill.roomNumber === room.roomNum
          // );
          return {
            roomNum: room.roomNum,
            id: room.id,
            // prevBill: prevBill,
            amenities: room.amenities,
            tenantCount: tenantCountMap[room.id] || 0 
          };
        });

        return {
          id: flat.id,
          name: flat.name,
          roomCount: rooms.length,
          rooms: rooms,
          prevBill: previousBill.filter((bill: any) => bill.id === flat.id)
        };
      });

      data = stats;
    }

    //For Electricity
    if (Number(utilityType) === CONSTANTS.DUES_TYPES.ELECTRICITY) {
      let electricityChargesType;
      const { pricePerUnit } = await extraChargeDB.getElectricityPrice({
        clientId,
        propId,
        type: CONSTANTS.EXTRA_CHARGE_TYPES.ELECTRICITY,
      });
      if(pricePerUnit == 0){
        electricityChargesType = CONSTANTS.CHARGE_TYPE.AMOUNT;
      }
      else{
        electricityChargesType = CONSTANTS.CHARGE_TYPE.UNIT;
      }
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Property Type [${propType}], Utility Type [${utilityType}], Electricity Charges Type [${electricityChargesType}], List has been sent successfully`
      );

      return res.status(200).json({
        msg: "List has been sent successfully",
        data: data || {},
        electricityChargesType: electricityChargesType || 0,
        pricePerUnit,
        isSuccess: true,
      });
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Property Type [${propType}], Utility Type [${utilityType}] List has been sent successfully`
    );

    return res.status(200).json({
      msg: "List has been sent successfully",
      data: data || {},
      isSuccess: true,
    });

  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
}

export default extraCharge;
