import { Response } from "express";
import log from "../config/log";
import CONSTANTS from "../config/constants";
import CustomRequest from "../types/requestType";
import staffDB from "../models/staff.model";
import { isUserPartner } from "../utils/isUserPartner";
import tenantDB from "../models/tenant.model";
import occupancyDB from "../models/occupancy.model";
import moment from "moment";
import moveOutDB from "../models/moveOut.model";
import houseInspectionDB from "../models/houseInspection.model";
import fsPromises from "fs/promises";
import fs from "fs";
import duesDB from "../models/dues.model";
import getDueDescription from "../utils/getDueDescription";
import generateLedgerReferenceId from "../utils/generateLedgerReferenceId";
import moveOutDuesDB from "../models/moveOutDues.model";
import ledgerDB from "../models/ledger.model";

const houseInspection: any = {};

houseInspection.AddMoveInInspection = async (
  req: CustomRequest,
  res: Response,
) => {
  const C = "House Inspection Controller";
  const F = "AddMoveInInspection";

  const files = req.files as Express.Multer.File[];
  const removeTmpImages = async () => {
    for (const file of files) {
      try {
        await fsPromises.unlink(file.path);
      } catch (err) { }
    }
  };

  try {
    let { propId, roomId, tenantId, moveInRemarks, inspectionData } =
      req.body;

    log.info(
      `[${C}], [${F}], Prop Id [${propId}], Room Id [${roomId}], Tenant Id [${tenantId}], Move In Remarks [${moveInRemarks}], Inspection Data [${inspectionData}], Is File Uploaded ${files ? `[Yes], ${files.length} Files` : "[No]"}`,
    );

    inspectionData = JSON.parse(inspectionData || []);

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

    if (userType === CONSTANTS.USER_TYPE.TENANT) {
      clientId = req.clientId;
      tenantId = req.id;
    } else if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}], Partner` : `Client Id [${clientId}], 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}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Requesting....`)
    }

    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 occupancies = await occupancyDB.getByClientIdAndTenantId({
      clientId,
      tenantId,
    });
    if (!occupancies) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Occupancy Found`,
      );
      return res.status(400).json({
        msg: "No Active Occupancy Found",
        isSuccess: false,
      });
    }

    //Will use first occupancy if multiple bed occupied
    const occupancy = occupancies[0];

    log.info(`Occupancy [${JSON.stringify(occupancy)}]`);

    if (userType === CONSTANTS.USER_TYPE.TENANT) {
      propId = occupancy.propId;
      roomId = occupancy.roomId;
    }

    log.info(
      `[${C}], [${F}], Prop Id [${propId}], Room Id [${roomId}], Tenant Id [${tenantId}], Move In Remarks [${moveInRemarks}], Inspection Data [${inspectionData}]`,
    );

    const houseInspectionExists =
      await houseInspectionDB.getInspectionRecordByGlobalId({
        clientId: clientId,
        globalId: occupancy.globalId,
      });
    if (houseInspectionExists) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Occupancy Id [${occupancy.id}], House Inspection Already Exists`,
      );
      return res.status(400).json({
        msg: "Move-In inspection already completed",
        isSuccess: false,
      });
    }

    const inspectionId = await houseInspectionDB.addMoveInInspection({
      globalId: occupancy.globalId,
      isEvicted: 0,
      tenantId: tenantId,
      clientId: clientId,
      propId: propId,
      roomId: roomId,
      status: CONSTANTS.HOUSE_INSPECTION_STATUS.APPROVED,
      moveInInspectionDate: moment().format("YYYY-MM-DD"),
      moveOutInspectionDate: null,
      moveInRemarks: moveInRemarks || "",
      moveOutRemarks: null,
      damageCharges: null,
    });

    if (inspectionData && inspectionData.length > 0) {
      for (let data of inspectionData) {
        await houseInspectionDB.addInspectionRecord({
          tenantId,
          clientId,
          tenantInspectionId: inspectionId,
          itemType: data?.itemType,
          type: data?.type,
          moveInStatus: Number(data?.status),
          moveOutStatus: null,
          moveInCount: data?.moveInCount,
          moveOutCount: null,
        });
      }
    }

    if (files) {
      let i = 1; //for image indexing

      for (let file of files) {
        const folderName = `Inspection_Images`;
        const folderPath = `uploads/documents/${clientId}/${tenantId}/${folderName}`;
        const fileExtension = file.mimetype.split("/")[1];
        const filename = `Image${i}_${inspectionId}.${fileExtension}`;

        if (!fs.existsSync(folderPath)) {
          await fsPromises.mkdir(folderPath, { recursive: true });
        }
  
        const oldPath = `uploads/tmp/${file.filename}`;
        const newPath = `${folderPath}/${filename}`;
        let url = `${process.env.UPLOAD_PATH}/documents/${clientId}/${tenantId}/${folderName}/${filename}`;

        await fsPromises.copyFile(oldPath, newPath);

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

        const docId = await houseInspectionDB.addImage({
          tenantId,
          clientId,
          tenantInspectionId: inspectionId,
          value: url,
          isMovedOut: 0,
        });

        i += 1;
      }
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Occupancy Id [${occupancy.id}], Tenant Id [${tenantId}], House Inspection Added Successfully`,
    );

    return res.status(200).json({
      msg: "House inspection added successfully",
      isSuccess: true,
    });
  } catch (error) {
    log.error(`[${C}], [${F}], Error: ${error}`);
    return res.status(500).json({
      msg: "Internal Server Error",
      isSuccess: false,
    });
  }
};

houseInspection.AddMoveOutInspection = async (
  req: CustomRequest,
  res: Response,
) => {
  const C = "House Inspection Controller";
  const F = "AddMoveOutInspection";

  const files = req.files as Express.Multer.File[];
  const removeTmpImages = async () => {
    for (const file of files) {
      try {
        await fsPromises.unlink(file.path);
      } catch (err) { }
    }
  };

  try {
    let { tenantId, moveOutRemarks, damageCharges, inspectionData, isEvicted } =
      req.body;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Move Out Remarks [${moveOutRemarks}], Damage Charges [${damageCharges}], Is Evicted [${isEvicted}], Inspection Data [${inspectionData}], Is File Uploaded ${files ? `[Yes], ${files.length} Files` : "[No]"}`,
    );

    inspectionData = JSON.parse(inspectionData || []);

    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 [${clientId}], 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}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Requesting....`)
    }

    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,
      });
    }

    let occupancies = null;

    if (Number(isEvicted) === 1) {
      occupancies = await moveOutDB.getByClientIdAndTenantId({
        clientId,
        tenantId,
      });
    } else {
      occupancies = await occupancyDB.getByClientIdAndTenantId({
        clientId,
        tenantId,
      });
    }

    if (!occupancies) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Occupancy Found`,
      );
      return res.status(400).json({
        msg: "No Active Occupancy Found",
        isSuccess: false,
      });
    }

    //Will use first occupancy if multiple bed occupied
    const occupancy = occupancies[0];

    const houseInspectionExists = await houseInspectionDB.getInspectionRecordByGlobalId({
      globalId: occupancy.globalId,
      clientId: clientId,
    });

    if (!houseInspectionExists) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Occupancy Id [${occupancy.id}], House Inspection Not Found`,
      );
      return res.status(400).json({
        msg: "Move-In inspection not found",
        isSuccess: false,
      });
    }

    await houseInspectionDB.addMoveOutInspection({
      id: houseInspectionExists.id,
      moveOutInspectionDate: moment().format("YYYY-MM-DD"),
      moveOutRemarks: moveOutRemarks || null,
      damageCharges: damageCharges || 0,
      moveOutInspectionDoneBy: req.id,
      moveOutInspectionDoneByUserType: userType,
    });

    if (inspectionData && inspectionData.length > 0) {
      for (let data of inspectionData) {
        const inspectionRecord = await houseInspectionDB.getInspectionRecordByClientIdAndTenantInspectionIdAndItemType({
          clientId,
          tenantId,
          tenantInspectionId: houseInspectionExists.id,
          itemType: data.itemType,
        });

        if (!inspectionRecord) {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Occupancy Id [${occupancy.id}], Item Type [${data.itemType}], Inspection Record Not Found`,
          );
          continue;
        }
        await houseInspectionDB.updateInspectionRecord({
          id: inspectionRecord.id,
          moveOutStatus: data.moveOutStatus,
          moveOutCount: data.moveOutCount,
        });
      }
    }

    if (files) {
      let i = 1; //for image indexing

      for (let file of files) {
        const folderName = `Inspection_Images`;
        const folderPath = `uploads/documents/${clientId}/${tenantId}/${folderName}`;
        const fileExtension = file.mimetype.split("/")[1];
        const filename = `Image${i}_${houseInspectionExists.id}.${fileExtension}`;

        if (!fs.existsSync(folderPath)) {
          await fsPromises.mkdir(folderPath, { recursive: true });
        }
  
        const oldPath = `uploads/tmp/${file.filename}`;
        const newPath = `${folderPath}/${filename}`;
        let url = `${process.env.UPLOAD_PATH}/documents/${clientId}/${tenantId}/${folderName}/${filename}`;

        await fsPromises.copyFile(oldPath, newPath);

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

        const docId = await houseInspectionDB.addImage({
          tenantId,
          clientId,
          tenantInspectionId: houseInspectionExists.id,
          value: url,
          isMovedOut: 1,
        });
        
        i += 1;
      }
    }

    const referenceId = await generateLedgerReferenceId({ clientId });
    
    if (damageCharges && Number(damageCharges) > 0) {
      if (Number(isEvicted) === 1) {
        const dueId = await moveOutDuesDB.add({
          moveOutId: occupancy.id,
          tenantId,
          roomId: occupancy.roomId,
          propId: occupancy.propId,
          clientId: clientId,
          amount: damageCharges,
          balance: damageCharges,
          ledgerReferenceId: referenceId,
          dueDate: moment().format("YYYY-MM-DD"),
          type: CONSTANTS.DUES_TYPES.DAMAGE,
          rentStartDate: null,
          rentEndDate: null,
          description: `${getDueDescription(CONSTANTS.DUES_TYPES.DAMAGE)}, added during move-out inspection`,
          title: "Damage Charges added during move-out inspection",
        });
      } else {
        await duesDB.addWithStartEndDateX({
          tenantId: tenantId,
          amount: damageCharges,
          occupancyId: occupancy?.id,
          roomId: occupancy?.roomId,
          propId: occupancy?.propId,
          clientId,
          rentStartDate: null,
          rentEndDate: null,
          type: CONSTANTS.DUES_TYPES.DAMAGE,
          dueDate: moment().format("YYYY-MM-DD"),
          balance: damageCharges,
          ledgerReferenceId: referenceId,
          description: `${getDueDescription(CONSTANTS.DUES_TYPES.DAMAGE)}, added during move-out inspection`,
          title: "Damage Charges added during move-out inspection",
        });
      }

      await ledgerDB.add({
        tenantId: tenantId,
        roomId: occupancy?.roomId,
        propId: occupancy?.propId,
        clientId,
        amount: damageCharges,
        balance: damageCharges,
        referenceId: referenceId,
        transactionId: null,
        type: CONSTANTS.DUES_TYPES.DAMAGE,
        rentStartDate: null,
        rentEndDate: null,
        dueDate: moment().format("YYYY-MM-DD"),
        description: `${getDueDescription(CONSTANTS.DUES_TYPES.DAMAGE)}, added during move-out inspection`,
        title: "Damage Charges added during move-out inspection",
      });
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Occupancy Id [${occupancy.id}], Tenant Id [${tenantId}], House Move Out Inspection Added Successfully`,
    );

    return res.status(200).json({
      msg: "House move-out inspection done",
      isSuccess: true,
    });
  } catch (error) {
    log.error(`[${C}], [${F}], Error: ${error}`);
    return res.status(500).json({
      msg: "Internal Server Error",
      isSuccess: false,
    });
  }
};

houseInspection.ListInspectionItems = async (
  req: CustomRequest,
  res: Response,
) => {
  const C = "House Inspection Controller";
  const F = "ListInspectionItems";

  try {
    const { propId } = req.query;

    log.info(`[${C}], [${F}], Prop Id [${propId}]`)

    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 [${clientId}], 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}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Requesting....`);
    }

    const inspectionItemList = await houseInspectionDB.getInspectionItemList();
    const propertyLinkedInspectionItems = await houseInspectionDB.getInspectionItemListByPropId({ clientId, propId }); 

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Inspection Items List Sent Successfully`,
    );

    // log.info(`[${C}], [${F}], Inspection Item List [${JSON.stringify(inspectionItemList)}]`);
    // log.info(`[${C}], [${F}], Property Linked Inspection Items [${JSON.stringify(propertyLinkedInspectionItems)}]`);

    return res.status(200).json({
      msg: "Inspection items list sent successfully",
      isSuccess: true,
      inspectionItemList: inspectionItemList || [],
      propertyLinkedInspectionItems: propertyLinkedInspectionItems || [],
    });
  } catch (error) {
    log.error(`[${C}], [${F}], Error: ${error}`);
    return res.status(500).json({
      msg: "Internal Server Error",
      isSuccess: false,
    });
  }
};

houseInspection.LinkUnlinkInspectionItems = async (
  req: CustomRequest,
  res: Response,
) => {
  const C = "House Inspection Controller";
  const F = "LinkUnlinkInspectionItems";

  try {
    let { propId, items } = req.body;

    log.info(`[${C}], [${F}], Prop Id [${propId}], Items [${JSON.stringify(items)}]`)

    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 [${clientId}], 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}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Requesting....`);
    }
    
    if (items && items.length > 0) {
      for (let item of items) {
        if (Number(item?.link) === 1) {
          //Link
          const isItemAlreadyLinked = await houseInspectionDB.getInspectionItemByIdAndPropId({
            clientId,
            propId,
            inspectionItemId: item?.id,
          });
          if (!isItemAlreadyLinked) {
            await houseInspectionDB.linkInspectionItem({
              clientId,
              propId,
              inspectionItemId: item?.id,
            });
          }
        } else if (Number(item?.link) === 0) {
          //Unlink
          const isItemAlreadyLinked = await houseInspectionDB.getInspectionItemByIdAndPropId({
            clientId,
            propId,
            inspectionItemId: item?.id,
          });
          if (isItemAlreadyLinked) {
            await houseInspectionDB.unlinkInspectionItem({
              clientId,
              propId,
              inspectionItemId: item?.id,
            });
          }
        }
      }
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Inspection Items List Updated Successfully`,
    );

    return res.status(200).json({
      msg: "Inspection items list updated successfully",
      isSuccess: true,
    });
  } catch (error) {
    log.error(`[${C}], [${F}], Error: ${error}`);
    return res.status(500).json({
      msg: "Internal Server Error",
      isSuccess: false,
    });
  }
};

houseInspection.ListTenantHouseInspection = async (
  req: CustomRequest,
  res: Response,
) => {
  const C = "House Inspection Controller";
  const F = "ListTenantHouseInspection";

  try {
    let { tenantId, isEvicted } = req.query;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Is Evicted [${isEvicted}]`);

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

    if (userType === CONSTANTS.USER_TYPE.TENANT) {
      log.info(`[${C}], [${F}], Client Id [${req.clientId}], Tenant Id [${req.id}], Tenant Requesting....`);
      clientId = req.clientId;
      tenantId = req.id;
    } else if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}], Partner` : `Client Id [${clientId}], 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}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Requesting....`)
    }

    let occupancies = null;

    if (Number(isEvicted) === 1) {
      occupancies = await moveOutDB.getByClientIdAndTenantId({
        clientId,
        tenantId,
      });
    } else {
      occupancies = await occupancyDB.getByClientIdAndTenantId({
        clientId,
        tenantId,
      });
    }

    if (!occupancies) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Occupancy Found`);
      return res.status(400).json({
        msg: "No occupancy found",
        isSuccess: false,
      });
    }

    const occupancy = occupancies[0];

    const inspectionItemList = await houseInspectionDB.getInspectionItemList();
    const propertyLinkedInspectionItems = await houseInspectionDB.getInspectionItemListByPropId({ 
      clientId, 
      propId: occupancy.propId,
    }); 
    
    const tenantHouseInspection = await houseInspectionDB.getHouseInspectionByClientIdAndTenantIdAndGlobalId({
      clientId,
      tenantId,
      globalId: occupancy?.globalId,
    });

    if (!tenantHouseInspection) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Occupancy Id [${occupancy?.id}], No House Inspection Found`);
      return res.status(200).json({
        msg: "No house inspection found",
        isSuccess: true,
        inspectionItemList: inspectionItemList || [],
        propertyLinkedInspectionItems: propertyLinkedInspectionItems || [],
        tenantInspectionItemData: false,
      });
    }

    // log.info(`Tenant House Inspection Data [${JSON.stringify(tenantHouseInspection)}]`);

    const tenantInspectionItems = await houseInspectionDB.getInspectionItemsByClientIdAndTenantIdAndTenantInspectionId({
      clientId,
      tenantId,
      tenantInspectionId: tenantHouseInspection?.id,
    });

    const inspectionImages = await houseInspectionDB.getInspectionImagesByInspectionId({
      tenantInspectionId: tenantHouseInspection?.id,
    });

    // log.info(`Tenant Inspection Items Data [${JSON.stringify(tenantInspectionItems)}]`);
    // log.info(`Inspection Images Data [${JSON.stringify(inspectionImages)}]`);

    let tenantInspectionItemData = {
      ...tenantHouseInspection,
      inspectionItems: tenantInspectionItems || [],
      inspectionImages: inspectionImages || [],
    };
      
    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Occupancy Id [${occupancy?.id}], House Inspection List Sent Successfully`,
    );

    return res.status(200).json({
      msg: "House inspection list sent successfully",
      isSuccess: true,
      inspectionItemList: inspectionItemList || [],
      propertyLinkedInspectionItems: propertyLinkedInspectionItems || [],
      tenantInspectionItemData,
    });
  } catch (error) {
    log.error(`[${C}], [${F}], Error: ${error}`);
    return res.status(500).json({
      msg: "Internal Server Error",
      isSuccess: false,
    });
  }
};

export default houseInspection;
