import { Response } from "express";
import log from "../config/log";
import CustomRequest from "../types/requestType";
import CONSTANTS from "../config/constants";
import { isUserFinanceAdmin, isUserPartner } from "../utils/isUserPartner";
import clientDB from "../models/client.model";
import staffDB from "../models/staff.model";
import tenantDB from "../models/tenant.model";
import tenantGuardianDB from "../models/tenantGuardians.model";
import tenantInstitutionDetailsDB from "../models/tenantInstitutionDetails.model";
import bookingsDB from "../models/bookings.model";
import moment from "moment";
import axios from "axios";
import fs from "fs";
import fsPromises from "fs/promises";
import jwt from "jsonwebtoken";
// @ts-ignore
import pdf from "pdf-creator-node";
import propertyDB from "../models/property.model";
import occupancyDB from "../models/occupancy.model";
import leadDB from "../models/lead.model";

const bookings: any = {};

bookings.Add = async (req: CustomRequest, res: Response) => {
  const C = "Bookings Controller";
  const F = "Add";

  try {
    const {
      encryptedId,
      name,
      mobile,
      email,
      gender,
      dob,
      aadhaar,
      idNumber, //student or employee id
      address,
      moveInDate,
      occupation, //student or employed
      tenure,
      institutionName,
      designation, //course name
      domain, //stream
      intakeDate,
      fatherName,
      fatherMobile,
      fatherEmail,
      motherName,
      motherMobile,
      motherEmail,
      localGuardianName,
      localGuardianEmail,
      localGuardianMobile,
      localGuardianRelation,
    } = req.body;

    log.info(
      `[${C}], [${F}], Client gId [${encryptedId}], Name [${name}], Mobile [${mobile}], Email [${email}], Gender [${gender}], Date of Birth [${dob}], Aadhaar [${aadhaar}], Id Number [${idNumber}], Address [${address}], Father Name [${fatherName}], Father Mobile [${fatherMobile}], Father Email [${fatherEmail}], Mother Name [${motherName}], Mother Mobile [${motherMobile}], Mother Email [${motherEmail}], Local Guardian Name [${localGuardianName}], Local Guardian Email [${localGuardianEmail}], Local Guardian Mobile [${localGuardianMobile}], Local Guardian Relation [${localGuardianRelation}], Move In Date [${moveInDate}], Occupation Type [${occupation}], Tenure [${tenure}], Institution Name [${institutionName}], Designation [${designation}], Domain [${domain}], Intake Date [${intakeDate}]`,
    );

    const client = await clientDB.getByGId({
      gId: encryptedId,
    });
    if (!client) {
      log.error(`[${C}], [${F}], Client gId [${encryptedId}] not found`);
      return res.status(404).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const clientId = client.id;

    let tenant = await tenantDB.getByMobile({
      mobile: mobile,
    });

    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Not Found, Creating New Tenant`);
      tenant = await tenantDB.createByBookingForm({
        mobile,
        name,
        email,
        gender,
        occupation,
        dob: moment(dob).format('YYYY-MM-DD'),
        aadharNumber: aadhaar,
        address,
      });
    } else {
      log.info(`[${C}], [${F}], Tenant Found, Updating Tenant`);
      tenant = await tenantDB.updateByBookingForm({
        mobile,
        name,
        email,
        gender,
        occupation,
        dob: moment(dob).format('YYYY-MM-DD'),
        aadharNumber: aadhaar,
        address,
      });
    }

    tenant = await tenantDB.getByMobile({
        mobile: mobile,
    });

    const tenantGuardian = await tenantGuardianDB.getByTenantId({ tenantId: tenant.id });
    if (!tenantGuardian) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenant.id}], Guardian Details Not Found, Creating Record`);
      await tenantGuardianDB.create({
        tenantId: tenant.id,
        fatherName: fatherName || null,
        fatherMobile: fatherMobile || null,
        fatherEmail: fatherEmail || null,
        fatherOccupation: null,
        fatherAnnualIncome: null,
        motherName: motherName || null,
        motherMobile: motherMobile || null,
        motherEmail: motherEmail || null,
        localGuardianName: localGuardianName || null,
        localGuardianMobile: localGuardianMobile || null,
        localGuardianEmail: localGuardianEmail || null,
        localGuardianRelation: localGuardianRelation || null,
      });
    } else {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenant.id}], Guardian Details Found, Updating Record`);
      await tenantGuardianDB.update({
        tenantId: tenant.id,
        fatherName: fatherName || null,
        fatherMobile: fatherMobile || null,
        fatherEmail: fatherEmail || null,
        fatherOccupation: null,
        fatherAnnualIncome: null,
        motherName: motherName || null,
        motherMobile: motherMobile || null,
        motherEmail: motherEmail || null,
        localGuardianName: localGuardianName || null,
        localGuardianMobile: localGuardianMobile || null,
        localGuardianEmail: localGuardianEmail || null,
        localGuardianRelation: localGuardianRelation || null,
      });
    }

    let tenantInstituionDetails = await tenantInstitutionDetailsDB.getByTenantIdAndClientId({
      tenantId: tenant.id,
      clientId,
    });

    if (!tenantInstituionDetails) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenant.id}], Instituion Details Not Found, Creating Record`);
      await tenantInstitutionDetailsDB.create({
        tenantId: tenant.id,
        clientId,
        institutionName: institutionName || null,
        institutionId: idNumber || null,
        occupation: occupation || null,
        tenure: tenure || null,
        designation: designation || null,
        domain: domain || null,
        intakeDate: intakeDate || null,
      });
    } else {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenant.id}], Instituion Details Found, Updating Record`);
      await tenantInstitutionDetailsDB.update({
        tenantId: tenant.id,
        clientId,
        institutionName: institutionName || null,
        institutionId: idNumber || null,
        occupation: occupation || null,
        tenure: tenure || null,
        designation: designation || null,
        domain: domain || null,
        intakeDate: intakeDate || null,
      });
    }

    // let booking = await bookingsDB.getByClientIdAndTenantId({
    //   clientId,
    //   tenantId: tenant.id,
    // });

    let booking = await bookingsDB.getByTenantAndClientIdAndStatus({
      clientId,
      tenantId: tenant.id,
      status: CONSTANTS.BOOKING_STATUS.FORM_SUBMITTED,
    });

    let applicationNumber = `KIP${clientId}${tenant.id}${moment().format("HHmmss")}`;

    if (!booking) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenant.id}], Pending Booking Not Found, Creating Record`);
      await bookingsDB.create({
        clientId,
        tenantId: tenant.id,
        propId: null,
        roomId: null,
        moveInDate: moveInDate || null,
        status: CONSTANTS.BOOKING_STATUS.FORM_SUBMITTED,
        stayType: CONSTANTS.STAY_TYPE.NORMAL,
        applicationNumber,
      });
    } else {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenant.id}], Previous Pending Booking Found, Updating Record`);
      await bookingsDB.update({
        id: booking.id,
        propId: null,
        roomId: null,
        moveInDate: moveInDate || null,
        status: CONSTANTS.BOOKING_STATUS.FORM_SUBMITTED,
        stayType: CONSTANTS.STAY_TYPE.NORMAL,
        applicationNumber,
      });
    }

    log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenant.id}], Booking Added Successfully`);

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

bookings.AddWithoutBed = async (req: CustomRequest, res: Response) => {
  const C = "Bookings Controller";
  const F = "AddWithoutBed";

  try {
    const {
      name,
      mobile,
      email,
      gender,
    } = req.body;

    const userType = req.userType;

    log.info(
      `[${C}], [${F}], Name [${name}], Mobile [${mobile}], Email [${email}], Gender [${gender}]`,
    );

    const { 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}]`
        }, Platform [${req.platform}], ${
          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}], Platform [${req.platform}], Staff Requesting....`,
      );
    }

    let tenant = await tenantDB.getByMobile({
      mobile: mobile,
    });

    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Not Found, Creating New Tenant`);
      tenant = await tenantDB.createByBookingForm({
        mobile,
        name,
        email,
        gender,
        occupation: null,
        dob: null,
        aadharNumber: null,
        address: null,
      });
    } else {
      log.info(`[${C}], [${F}], Tenant Found, Updating Tenant`);
      tenant = await tenantDB.updateByBookingForm({
        mobile,
        name,
        email,
        gender,
        occupation: null,
        dob: null,
        aadharNumber: null,
        address: null,
      });
    }

    tenant = await tenantDB.getByMobile({
      mobile: mobile,
    });

    let applicationNumber = `KIP${clientId}${tenant.id}${moment().format("HHmmss")}`;

    let booking = await bookingsDB.getByTenantAndClientIdAndStatus({
      clientId,
      tenantId: tenant.id,
      status: CONSTANTS.BOOKING_STATUS.FORM_SUBMITTED,
    });

    // await bookingsDB.create({
    //   clientId,
    //   tenantId: tenant.id,
    //   propId: null,
    //   roomId: null,
    //   moveInDate: null,
    //   status: CONSTANTS.BOOKING_STATUS.FORM_SUBMITTED,
    //   stayType: CONSTANTS.STAY_TYPE.NORMAL,
    //   applicationNumber,
    // });

    if (!booking) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenant.id}], Booking Not Found, Creating Record`);
      await bookingsDB.create({
        clientId,
        tenantId: tenant.id,
        propId: null,
        roomId: null,
        moveInDate: null,
        status: CONSTANTS.BOOKING_STATUS.FORM_SUBMITTED,
        stayType: CONSTANTS.STAY_TYPE.NORMAL,
        applicationNumber,
      });
    } else {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenant.id}], Previous Booking Found, Updating Record`);
      await bookingsDB.update({
        id: booking.id,
        propId: null,
        roomId: null,
        moveInDate: null,
        status: CONSTANTS.BOOKING_STATUS.FORM_SUBMITTED,
        stayType: CONSTANTS.STAY_TYPE.NORMAL,
        applicationNumber,
      });
    }

    log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenant.id}], Booking Added Successfully`);

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

bookings.AddConfirmedTenant = async (req: CustomRequest, res: Response) => {
  const C = "Bookings Controller";
  const F = "AddConfirmedTenant";

  try {
    const { propId, roomId, bedId, moveInDate, name, mobile, email, gender, rent, stayType=CONSTANTS.STAY_TYPE.NORMAL, moveOutDate, rentalType=CONSTANTS.RENTAL_TYPES.MONTHLY, sendNotiToTenant=1 } = req.body;

    log.info(`[${C}], [${F}], Property ID [${propId}], Room ID [${roomId}], Bed Id [${bedId}], Move In Date [${moveInDate}], Move Out Date [${moveOutDate}], Name [${name}], Mobile [${mobile}], Email [${email}], Gender [${gender}], Rent [${rent}], Stay Type [${stayType}], Rental Type [${rentalType}], Send Notice To Tenant [${sendNotiToTenant}], Adding Confirmed Tenant`);

    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}], Partner` : `Client Id [${req.id}], Client`} Requested....`,
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff not found, Id [${req.id}]`);
        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 Requested....`,
      );
    }
    
    let tenant = await tenantDB.getByMobile({ mobile: mobile });
    if (!tenant) {
      log.info(`[${C}], [${F}], Mobile [${mobile}], Tenant Not Found, Creating Record`);
      await tenantDB.createByBookingForm({
        name,
        mobile,
        email,
        gender: gender,
        occupation: null,
        dob: null,
        aadharNumber: null,
        address: null,
      });

      tenant = await tenantDB.getByMobile({ mobile: mobile });
    }

    let occupancy = await occupancyDB.getByClientIdAndTenantId({
      clientId,
      tenantId: tenant.id,
    });

    if (occupancy) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenant.id}], Occupancy [${JSON.stringify(occupancy)}], Tenant Already Occupied On Another Bed`
      );
      
      return res.status(400).json({
        msg: "Tenant is already occupied on another bed",
        isSuccess: false,
      });
    }

    let applicationNumber = `KIP${clientId}${tenant.id}${moment().format("HHmmss")}`;
    
    // Create booking
    const bookingId = await bookingsDB.create({
      clientId: clientId,
      tenantId: tenant.id,
      propId,
      roomId,
      moveInDate,
      status: CONSTANTS.BOOKING_STATUS.CONFIRMED,
      stayType: stayType,
      applicationNumber,
    });

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      await bookingsDB.updateBookedBy({
        bookedBy: Number(req.id),
        id: bookingId,
      });
    }
    
    const property = await propertyDB.getAllInfoById({ id: propId });
    if (!property) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], No Property Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    } 

    log.info(`[${C}], [${F}], Move In Date [${moveInDate}], Stay Type [${stayType}], Adding Basic Details`)
    const addBasicPayload = {
      bedId,
      propId,
      roomId,
      fullName: name,
      mobile,
      gender,
      occupation: "null",
      allBedsOccupied: false,
      alternateMobile: "null",
      entireFlatOccupied: false,
      isRentalBondAllowed: false,
      bondAllowed: 1,
    };

    const addBasicResponse = await axios.post(
      `${process.env.API_BASE}/tenant/add/basic`,
      addBasicPayload,
      {
        headers: {
          // authorization: "Bearer" + " " + req.headers.authorization,
          authorization: req.headers.authorization,
          "Content-Type": "application/json",
        },
      },
    );

    log.info(`[${C}], [${F}], Client Id [${clientId}], Add Basic Response [${JSON.stringify(addBasicResponse?.data)}]`);

    if (stayType === CONSTANTS.STAY_TYPE.SHORT) {
      if(addBasicResponse?.data?.isSuccess) {
        const addAgreementByClientXPayload = {
          dailyRent: rent,
          moveInDate: moment(moveInDate).format("YYYY-MM-DD"),
          moveOutDate: moveOutDate ? moment(moveOutDate).format("YYYY-MM-DD") : null,
          tenantId: tenant.id,
          propId,
          roomId,
          entireFlatOccupied: false,
          notifyTenant: 0,
          securityDeposit: 0,
          sendNotiToTenant,
          addFromBooking: 1,
        };
  
        const addAgreementByClientResponse = await axios.post(
          `${process.env.API_BASE}/tenant/add/agreement/short/stay/new`,
          addAgreementByClientXPayload,
          {
            headers: {
              // authorization: "Bearer" + " " + req.headers.authorization,
              authorization: req.headers.authorization,
              "Content-Type": "application/json",
            },
          },
        );
  
        log.info(`[${C}], [${F}], Client Id [${clientId}], Add Agreement Response [${JSON.stringify(addAgreementByClientResponse?.data)}]`);
  
        if (addAgreementByClientResponse?.data?.isSuccess) {
          log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenant.id}], Stay Type [${stayType}], Agreement Details Added Successfully`)
        }
      } 
    } else {
      if(addBasicResponse?.data?.isSuccess) {
        const addAgreementByClientXPayload = {
          monthlyRent: rent,
          securityDeposit: property.security,
          agreementStartDate: moment(moveInDate).format("YYYY-MM-DD"),
          rentalCycle: property.rentalCycle,
          rentalType: rentalType,
          agreementPeriod: property.agreementPeriod,
          noticePeriod: property.noticePeriod,
          lockInPeriod: property.lockInPeriod,
          moveInDate: moment(moveInDate).format("YYYY-MM-DD"),
          tenantId: tenant.id,
          electricityReading: 0,
          propId,
          roomId,
          entireFlatOccupied: false,
          bookingAmount: 0,
          bookingAdjustType: 0,
          sendNotiToTenant,
          addFromBooking: 1,
        };
  
        const addAgreementByClientResponse = await axios.post(
          `${process.env.API_BASE}/tenant/add/agreement/new`,
          addAgreementByClientXPayload,
          {
            headers: {
              // authorization: "Bearer" + " " + req.headers.authorization,
              authorization: req.headers.authorization,
              "Content-Type": "application/json",
            },
          },
        );
  
        log.info(`[${C}], [${F}], Client Id [${clientId}], Add Agreement Response [${JSON.stringify(addAgreementByClientResponse?.data)}]`);
  
        if (addAgreementByClientResponse?.data?.isSuccess) {
          log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenant.id}], Stay Type [${stayType}], Agreement Details Added Successfully`)
        }
      } 
    }
    
    log.info(`[${C}], [${F}], Client Id [${req.clientId}], Tenant Id [${tenant.id}], Confirmed Tenant Added Successfully`);
    
    return res.status(200).json({
      msg: "Tenant booked successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.error(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

bookings.InitialValues = async (req: CustomRequest, res: Response) => {
  const C = "Bookings Controller";
  const F = "InitialValues";

  try {
    const { encryptedId } = req.query;

    log.info(
      `[${C}], [${F}], Encrypted ID [${encryptedId}]`
    );

    if (!encryptedId || String(encryptedId).trim() === "" || String(encryptedId).toLowerCase().trim() === "null" || String(encryptedId).toLowerCase().trim() === "undefined") {
      log.info(`[${C}], [${F}], Invalid Encrypted ID [${encryptedId}]`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const client = await clientDB.getByGId({
      gId: encryptedId,
    });
    if (!client) {
      log.error(`[${C}], [${F}], Client gId [${encryptedId}] not found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const logo = client?.logo
      ? process.env.RS_LOGO_URI + client.id + "/" + client.logo
      : String(process.env.RS_DEFAULT_LOGO_URI);

    const token = jwt.sign(
      {
        id: null,
        type: CONSTANTS.USER_TYPE.TENANT,
        clientId: client?.id,
        isEvicted: false,
      },
      process.env.TOKEN_SECRET!,
      { expiresIn: process.env.TOKEN_EXPIRY }
    );
    
    let data = {
      clientName: client.name,
      clientMobile: client.mobile,
      logo: logo,
      id: client.id,
      gId: client.gId,
      token: token,
    };

    log.info(`[${C}], [${F}], Client Id [${client.id}], Encrypted ID [${encryptedId}], Initial Values Sent Successfully`);

    return res.status(200).json({
      msg: "Initial Values fetched successfully",
      isSuccess: true,
      data,
    });

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

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

  try {
    const { pageNum, propId, s, t, startDate, endDate, } = req.query;

    let propFilter = null;
    if (propId && Number(propId) !== 0 && typeof propId === 'string') {
      propFilter = propId.split(',').map(v => v.trim()).filter(Boolean);
    }

    const userType = req.userType;
    const limit = 10;

    log.info(`[${C}], [${F}], Start Date [${startDate}], End Date [${endDate}], Page Number [${pageNum}], Property Ids [${propId}], Search Val [${s}], Search Type [${t}]`);

    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}], Partner` : `Client Id [${req.id}], Client`} Requested....`,
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff not found, Id [${req.id}]`);
        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 Requested....`,
      );
    }

    const client = await clientDB.getById({
      id: clientId,
    });
    if (!client) {
      log.info(`[${C}], [${F}], Client not found, Id [${clientId}]`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const bookings = await bookingsDB.getByClientIdAndFilters({
      clientId,
      propIds: propFilter || [],
      searchVal: s || null,
      searchType: t || null,
      startDate,
      endDate,
      pageNum: Number(pageNum) || null,
      limit,
    });

    const summary = await bookingsDB.getSummaryByClientId({
      clientId,
    });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Prop Ids [${propId}], Page Num [${pageNum}], Limit [${limit}], Booking Sent Successfully`
    );

    return res.status(200).json({
      msg: "Bookings fetched successfully",
      isSuccess: true,
      data: bookings || [],
      summary,
      encryptedId: client.gId || null,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

bookings.AssignTenant = async (req: CustomRequest, res: Response) => {
  const C = "Bookings Controller";
  const F = "AssignTenant";

  try {
    const { tenantId, propId, roomId, moveInDate, leadId=null, } = req.body;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move-In Date [${moveInDate}], Lead Id [${leadId}]`
    );
    
    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}], Partner` : `Client Id [${req.id}], Client`} Requested....`,
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff not found, Id [${req.id}]`);
        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 Requested....`,
      );
    }

    const isFutureDate = moment(moveInDate).isAfter(moment(), "day");

    await bookingsDB.assignTenantIdByClientId({
      tenantId,
      clientId,
      propId,
      roomId,
      moveInDate,
      status: isFutureDate ? CONSTANTS.BOOKING_STATUS.CONFIRMED : CONSTANTS.BOOKING_STATUS.MOVED_IN,
    });

    if (Number(leadId) && Number(leadId) > 0) {
      const lead = await leadDB.getById({id: leadId});
      if (lead) {
        await leadDB.editLead({
          id: leadId,
          visitDateTime: lead?.visitDateTime,
          remarks: lead?.remarks,
          status: CONSTANTS.LEAD_STATUS.CONVERTED,
        });
      }
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Booking Assigned Successfully`
    );

    return res.status(200).json({
      msg: "Booking assigned 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,
    });
  }
}

bookings.DownloadTenantProfile = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "DownloadTenantProfile";


  try {
    const { tenantId } = req.query;

    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}], Partner` : `Client Id [${req.id}], Client`} Requested....`,
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff not found, Id [${req.id}]`);
        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 Requested....`,
      );
    }

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


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

    const tenantBooking = await bookingsDB.getByClientIdAndTenantId({
      tenantId,
      clientId,
    });
    if (!tenantBooking) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Tenant Booking Found`);
      return res.status(400).json({ 
        msg: CONSTANTS.MSG.INVALID_REQUEST, 
        isSuccess: false 
      });
    }

    const tenantGuardians = await tenantGuardianDB.getByTenantId({ tenantId });
    const tenantInstituionDetails = await tenantInstitutionDetailsDB.getByTenantIdAndClientId({
      tenantId,
      clientId,
    });

    let templatePath = process.env.TENANT_BOOKING_APPLICATION_FORM!;
    let { data: html } = await axios.get(templatePath);

    if (!html) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Template found without content`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }

    const folderName = `tenant_${tenantId}`;
    const currentYear = moment().year();
    const folderPath = `uploads/documents/${currentYear}/bookings/client_${clientId}/bookings/${folderName}`;

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

    const urlBasePath = `${process.env.UPLOAD_PATH}/documents/${currentYear}/bookings/client_${clientId}/bookings/${folderName}`;

    html = html.toString();

    let applicationStatus = "";
    switch (Number(tenantBooking.status)) {
      case CONSTANTS.BOOKING_STATUS.FORM_SUBMITTED: {
        applicationStatus = "Submitted";
        break;
      }
      case CONSTANTS.BOOKING_STATUS.PAYMENT_DONE: {
        applicationStatus = "Payment Done";
        break;
      }
      case CONSTANTS.BOOKING_STATUS.CONFIRMED: {
        applicationStatus = "Confirmed";
        break;
      }
      case CONSTANTS.BOOKING_STATUS.CANCELLED: {
        applicationStatus = "Cancelled";
        break;
      }
    }

    let tenantGender = "";
    switch (Number(tenant.gender)) {
      case CONSTANTS.GENDER.MALE: {
        tenantGender = "Male";
        break;
      }
      case CONSTANTS.GENDER.FEMALE: {
        tenantGender = "Female";
        break;
      }
      case CONSTANTS.GENDER.OTHER: {
        tenantGender = "Other";
        break;
      }
    }

    let tenantOccupation = 
      tenantInstituionDetails 
      ? Number(tenantInstituionDetails.occupation) === CONSTANTS.TENANT_OCCUPATION.STUDENT ? "Student" : "Salried"
      : "N/A";

    let academicStanding = "N/A";
    if (tenantInstituionDetails) {
      switch (Number(tenantInstituionDetails.tenure)) {
        case CONSTANTS.ACADEMIC_YEAR.FIRST: {
          academicStanding = "First Year";
          break;
        }
        case CONSTANTS.ACADEMIC_YEAR.SECOND: {
          academicStanding = "Second Year";
          break;
        }
        case CONSTANTS.ACADEMIC_YEAR.THIRD: {
          academicStanding = "Third Year";
          break;
        }
        case CONSTANTS.ACADEMIC_YEAR.FOURTH: {
          academicStanding = "Fourth Year";
          break;
        }
        case CONSTANTS.ACADEMIC_YEAR.FIFTH: {
          academicStanding = "Fifth Year";
          break;
        }
        case CONSTANTS.ACADEMIC_YEAR.SIXTH: {
          academicStanding = "Sixth Year";
          break;
        }
      }
    }

    let localGuardianRelation = "N/A";
    if (tenantGuardians && tenantGuardians?.localGuardianRelation) {
      switch (Number(tenantGuardians.localGuardianRelation)) {
        case CONSTANTS.TENANT_GUARDIAN_RELATION.UNCLE: {
          localGuardianRelation = "Uncle";
          break;
        }
        case CONSTANTS.TENANT_GUARDIAN_RELATION.AUNTY: {
          localGuardianRelation = "Aunty";
          break;
        }
        case CONSTANTS.TENANT_GUARDIAN_RELATION.BROTHER: {
          localGuardianRelation = "Brother";
          break;
        }
        case CONSTANTS.TENANT_GUARDIAN_RELATION.SISTER: {
          localGuardianRelation = "Sister";
          break;
        }
        case CONSTANTS.TENANT_GUARDIAN_RELATION.FRIEND: {
          localGuardianRelation = "Friend";
          break;
        }
      }
    }

    const logo = client?.logo
      ? process.env.RS_LOGO_URI + client.id + "/" + client.logo
      : String(process.env.RS_DEFAULT_LOGO_URI);


    html = html.replace(/{{logo}}/g, logo);
    html = html.replace(/{{applicationStatus}}/g, applicationStatus);
    html = html.replace(/{{tenantName}}/g, tenant.name);
    html = html.replace(/{{tenantEmail}}/g, tenant?.email || "-");
    html = html.replace(/{{tenantPhone}}/g, tenant?.mobile || "-");
    html = html.replace(/{{tenantGender}}/g, tenantGender);
    html = html.replace(/{{tenantDob}}/g, tenant?.dob ? moment(tenant.dob).format("DD MMM YYYY") : "-");
    html = html.replace(/{{tenantAadhar}}/g, tenant?.aadharNumber || "-");
    html = html.replace(/{{applicationNumber}}/g, tenantBooking?.applicationNumber || "-");
    html = html.replace(/{{tenantAddress}}/g, tenant?.address || "-");
    html = html.replace(/{{tenantOccupation}}/g, tenantOccupation || "-");
    html = html.replace(/{{moveInDate}}/g, tenantBooking?.moveInDate ? moment(tenantBooking.moveInDate).format("DD MMM YYYY") : "N/A");
    html = html.replace(/{{idNumber}}/g, tenantInstituionDetails ? tenantInstituionDetails?.institutionId : "N/A");
    html = html.replace(/{{institutionName}}/g, tenantInstituionDetails ? tenantInstituionDetails?.institutionName : "N/A");
    html = html.replace(/{{academicStanding}}/g, academicStanding);
    html = html.replace(/{{designation}}/g, tenantInstituionDetails ? tenantInstituionDetails?.designation : "N/A");
    html = html.replace(/{{domain}}/g, tenantInstituionDetails ? tenantInstituionDetails?.domain : "N/A");
    html = html.replace(/{{intakeDate}}/g, tenantInstituionDetails ? tenantInstituionDetails?.intake : "-");
    html = html.replace(/{{fatherName}}/g, tenantGuardians?.fatherName ? tenantGuardians?.fatherName : "-");
    html = html.replace(/{{fatherEmail}}/g, tenantGuardians?.fatherEmail ? tenantGuardians?.fatherEmail : "-");
    html = html.replace(/{{fatherPhone}}/g, tenantGuardians?.fatherMobile ? tenantGuardians?.fatherMobile : "-");
    html = html.replace(/{{motherName}}/g, tenantGuardians?.motherName ? tenantGuardians?.motherName : "-");
    html = html.replace(/{{motherEmail}}/g, tenantGuardians?.motherEmail ? tenantGuardians?.motherEmail : "-");
    html = html.replace(/{{motherPhone}}/g, tenantGuardians?.motherMobile ? tenantGuardians?.motherMobile : "-");
    html = html.replace(/{{localGuardianName}}/g, tenantGuardians?.localGuardianName ? tenantGuardians?.localGuardianName : "-");
    html = html.replace(/{{localGuardianEmail}}/g, tenantGuardians?.localGuardianEmail ? tenantGuardians?.localGuardianEmail : "-");
    html = html.replace(/{{localGuardianPhone}}/g, tenantGuardians?.localGuardianMobile ? tenantGuardians?.localGuardianMobile : "-");
    html = html.replace(/{{localGuardianRelation}}/g, localGuardianRelation || "-");
    
    const options = {
      format: "A4",
      orientation: "portrait",
      border: "10mm",
      childProcessOptions: {
        env: {
          OPENSSL_CONF: "/dev/null",
        },
      },
    };

    let fileNamePart = tenant?.name ? tenant?.name.trim()
    .toLowerCase()                    // lowercase
    .replace(/[^a-z0-9\s]/g, '')      // keep only alphabets, numbers and spaces
    .trim()
    .split(/\s+/)                    // split by spaces
    .slice(0, 2)                     // keep first 2 words
    .join('_') : 'tenant' ;
    const randomNum = Math.floor(1000 + Math.random() * 9000);
    const filename = `booking_${fileNamePart}${randomNum}.pdf`;
    let url = `${urlBasePath}/${filename}`;

    var document = {
      html: html,
      path: `${folderPath}/${filename}`,
      data: {},
      type: "",
    };

    await pdf.create(document, options);

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], URL [${url}]`
    );
    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Booked Tenant Profile document created Successfully`
    );
    return res.status(200).json({
      msg: "Tenant profile document created Successfully",
      link: url,
      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,
    });
  }
};

bookings.Analytics = async (req: CustomRequest, res: Response) => {
  const C = "Bookings Controller";
  const F = "Analytics";

  try {
    const { startDate, endDate } = req.query;

    let bookingList = null;
    let properties = null;
    let staffs = null;
    let bookingData = null;

    log.info(`[${C}], [${F}], Start Date [${startDate}], End Date [${endDate}], Requesting....`);

    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}], Partner` : `Client Id [${req.id}], Client`} Requested....`,
      );

      bookingList = await bookingsDB.getByClientIdAndDateRange({
        clientId,
        startDate,
        endDate,
      });

      properties = await propertyDB.getPropIdsByClientId({
        clientId,
        status: CONSTANTS.PROPERTY_STATUS.ACTIVE,
      });
      bookingData = await bookingsDB.getLastSixMonthsTrend({ clientId });
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff not found, Id [${req.id}]`);
        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 Requested....`,
      );

      bookingList = await bookingsDB.getByClientIdAndDateRangeForStaffId({
        clientId,
        startDate,
        endDate,
        staffId: staff.id,
      });

      properties = await propertyDB.getPropIdsByClientIdForStaff({
        clientId,
        status: CONSTANTS.PROPERTY_STATUS.ACTIVE,
        staffId: staff.id,
      });
      bookingData = await bookingsDB.getLastSixMonthsTrendForStaff({ clientId, staffId: staff.id });
    }

    staffs = await staffDB.getActiveByClientId({
      clientId,
    });

    let totalBookings = 0;
    let cancelledBookings = 0;
    let confirmedBookings = 0;
    let movedInBookings = 0;
    let nullPropBookings = 0;

    let summaryByProperty = properties && properties.length > 0 ? properties.reduce((acc: any, prop: any) => {
      acc[prop.id] = {
        ...prop,
        totalBookings: 0,
        cancelledBookings: 0,
        confirmedBookings: 0,
        movedInBookings: 0,
      };
      return acc;
    }, {}) : {};

    let summaryByStaff = staffs && staffs.length > 0 ? staffs.reduce((acc: any, staff: any) => {
      acc[staff.id] = {
        ...staff,
        totalBookings: 0,
        cancelledBookings: 0,
        confirmedBookings: 0,
        movedInBookings: 0,
      };
      return acc;
    }, {}) : {};

    if(bookingList && bookingList.length > 0){
      totalBookings = bookingList.length;
      cancelledBookings = bookingList.filter((booking: any) => booking.status === CONSTANTS.BOOKING_STATUS.CANCELLED).length;
      confirmedBookings = bookingList.filter((booking: any) => booking.status === CONSTANTS.BOOKING_STATUS.CONFIRMED).length;
      movedInBookings = bookingList.filter((booking: any) => booking.status === CONSTANTS.BOOKING_STATUS.MOVED_IN).length;
      nullPropBookings = bookingList.filter((booking: any) => booking.propId === null).length;

      for (const booking of bookingList) {
        if(booking?.propId && summaryByProperty[booking.propId]){
          summaryByProperty[booking.propId].totalBookings++;
          if (booking.status === CONSTANTS.BOOKING_STATUS.CANCELLED) {
            summaryByProperty[booking.propId].cancelledBookings++;
          }
          if (booking.status === CONSTANTS.BOOKING_STATUS.CONFIRMED) {
            summaryByProperty[booking.propId].confirmedBookings++;
          }
          if (booking.status === CONSTANTS.BOOKING_STATUS.MOVED_IN) {
            summaryByProperty[booking.propId].movedInBookings++;
          }
        }

        if(booking?.bookedBy && summaryByStaff[booking.bookedBy]){
          summaryByStaff[booking.bookedBy].totalBookings++;
          if (booking.status === CONSTANTS.BOOKING_STATUS.CANCELLED) {
            summaryByStaff[booking.bookedBy].cancelledBookings++;
          }
          if (booking.status === CONSTANTS.BOOKING_STATUS.CONFIRMED) {
            summaryByStaff[booking.bookedBy].confirmedBookings++;
          }
          if (booking.status === CONSTANTS.BOOKING_STATUS.MOVED_IN) {
            summaryByStaff[booking.bookedBy].movedInBookings++;
          }
        }
      }
    }

    // log.info(
    //   `[${C}], [${F}], Client Id [${clientId}], Start Date [${startDate}], End Date [${endDate}], Total Bookings [${totalBookings}], Cancelled Bookings [${cancelledBookings}], Confirmed Bookings [${confirmedBookings}], Null Prop Bookings [${nullPropBookings}], Summary By Property [${JSON.stringify(summaryByProperty)}]`
    // );

    //Booking Trends 
    // bookingData = await bookingsDB.getLastSixMonthsTrend({ clientId });
    const bookingTrend = [];
    for (let i = 5; i >= 0; i--) {
      const date = moment().subtract(i, "months");

      const year = date.year();
      const monthNumber = date.month() + 1; // 1-12

      const row = bookingData.find(
        (r: any) => r.year === year && r.monthNumber === monthNumber
      );

      bookingTrend.push({
        month: date.format("MMM"),
        year,
        bookings: row ? Number(row.bookings) : 0,
        movedIn: row ? Number(row.movedIn) : 0,
        cancelled: row ? Number(row.cancelled) : 0,
      });
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Start Date [${startDate}], End Date [${endDate}], Analytics fetched successfully`
    );

    return res.status(200).json({
      msg: "Analytics fetched successfully",
      totalBookings,
      cancelledBookings,
      confirmedBookings,
      nullPropBookings,
      movedInBookings,
      summaryByProperty,
      summaryByStaff,
      bookingTrend,
      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 bookings;
