const GraphQLClient = require("graphql-request").GraphQLClient;
const CalculateAllocations = require("../cdk/cdk-calculate-allocations").default;
const InstanceMgr = require("../utils/instanceMgr").default;
const CreateFortellisLogEvent = require("./fortellis-logger");
const queries = require("../graphql-client/queries");
const {
  MakeFortellisCall,
  FortellisActions,
  getTransactionType,
  defaultFortellisTTL,
  FortellisCacheEnums,
  FortellisApiError
} = require("./fortellis-helpers");
const _ = require("lodash");
const moment = require("moment-timezone");

const replaceSpecialRegex = /[^a-zA-Z0-9 ]+/g;

// Helper function to handle FortellisApiError logging
function handleFortellisApiError(socket, error, functionName, additionalDetails = {}) {
  if (error instanceof FortellisApiError) {
    CreateFortellisLogEvent(socket, "ERROR", `${functionName} failed: ${error.message}`, {
      reqId: error.reqId,
      url: error.url,
      apiName: error.apiName,
      errorData: error.errorData,
      errorStatus: error.errorStatus,
      errorStatusText: error.errorStatusText,
      functionName,
      ...additionalDetails
    });
    CreateFortellisLogEvent(
      socket,
      "DEBUG",
      `${functionName} failed. , ${JSON.stringify({
        reqId: error.reqId,
        url: error.url,
        apiName: error.apiName,
        errorData: error.errorData,
        errorStatus: error.errorStatus,
        errorStatusText: error.errorStatusText,
        functionName,
        ...additionalDetails
      })}`
    );
  } else {
    CreateFortellisLogEvent(socket, "ERROR", `Error in ${functionName} - ${error.message}`, {
      error: error.message,
      stack: error.stack,
      functionName,
      ...additionalDetails
    });
  }
}

async function FortellisJobExport({ socket, redisHelpers, txEnvelope, jobid }) {
  const {
    // setSessionData,
    // getSessionData,
    // addUserSocketMapping,
    // removeUserSocketMapping,
    // refreshUserSocketTTL,
    // getUserSocketMappingByBodyshop,
    setSessionTransactionData
    // getSessionTransactionData,
    // clearSessionTransactionData
  } = redisHelpers;
  try {
    CreateFortellisLogEvent(socket, "DEBUG", `Received Job export request for id ${jobid}`);
    await setSessionTransactionData(
      socket.id,
      getTransactionType(jobid),
      FortellisCacheEnums.txEnvelope,
      txEnvelope,
      defaultFortellisTTL
    );

    const JobData = await QueryJobData({ socket, jobid });

    await setSessionTransactionData(
      socket.id,
      getTransactionType(jobid),
      FortellisCacheEnums.JobData,
      JobData,
      defaultFortellisTTL
    );

    CreateFortellisLogEvent(socket, "DEBUG", `{1} Begin Calculate DMS Vehicle ID using VIN: ${JobData.v_vin}`);
    const DMSVid = (await CalculateDmsVid({ socket, JobData, redisHelpers }))[0];
    await setSessionTransactionData(
      socket.id,
      getTransactionType(jobid),
      FortellisCacheEnums.DMSVid,
      DMSVid,
      defaultFortellisTTL
    );

    let DMSVehCustomer;
    if (!DMSVid.newId) {
      CreateFortellisLogEvent(socket, "DEBUG", `{2.1} Querying the Vehicle using the DMSVid: ${DMSVid.vehiclesVehId}`);
      const DMSVeh = await QueryDmsVehicleById({ socket, redisHelpers, JobData, DMSVid });
      await setSessionTransactionData(
        socket.id,
        getTransactionType(jobid),
        FortellisCacheEnums.DMSVeh,
        DMSVeh,
        defaultFortellisTTL
      );

      const DMSVehCustomerFromVehicle =
        DMSVeh?.owners && DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT");

      if (DMSVehCustomerFromVehicle?.id && DMSVehCustomerFromVehicle.id.value) {
        CreateFortellisLogEvent(
          socket,
          "DEBUG",
          `{2.2} Querying the Customer using the ID from DMSVeh: ${DMSVehCustomerFromVehicle.id.value}`
        );
        DMSVehCustomer = await QueryDmsCustomerById({
          socket,
          redisHelpers,
          JobData,
          CustomerId: DMSVehCustomerFromVehicle.id.value
        });
        await setSessionTransactionData(
          socket.id,
          getTransactionType(jobid),
          FortellisCacheEnums.DMSVehCustomer,
          DMSVehCustomer,
          defaultFortellisTTL
        );
      }
    }
    CreateFortellisLogEvent(socket, "DEBUG", `{2.3} Querying the Customer using the name.`);

    const DMSCustList = await QueryDmsCustomerByName({ socket, redisHelpers, JobData });
    await setSessionTransactionData(
      socket.id,
      getTransactionType(jobid),
      FortellisCacheEnums.DMSCustList,
      DMSCustList,
      defaultFortellisTTL
    );

    socket.emit("fortellis-select-customer", [
      ...(DMSVehCustomer ? [{ ...DMSVehCustomer, vinOwner: true }] : []),
      ...DMSCustList
    ]);
  } catch (error) {
    CreateFortellisLogEvent(socket, "ERROR", `Error in FortellisJobExport - ${error} `, {
      error: error.message,
      stack: error.stack
    });
  }
}

async function FortellisSelectedCustomer({ socket, redisHelpers, selectedCustomerId, jobid }) {
  const {
    // setSessionData,
    // getSessionData,
    // addUserSocketMapping,
    // removeUserSocketMapping,
    // refreshUserSocketTTL,
    // getUserSocketMappingByBodyshop,
    setSessionTransactionData,
    getSessionTransactionData
    //clearSessionTransactionData
  } = redisHelpers;

  await setSessionTransactionData(
    socket.id,
    getTransactionType(jobid),
    FortellisCacheEnums.selectedCustomerId,
    selectedCustomerId,
    defaultFortellisTTL
  );
  const JobData = await getSessionTransactionData(socket.id, getTransactionType(jobid), FortellisCacheEnums.JobData);
  const txEnvelope = await getSessionTransactionData(
    socket.id,
    getTransactionType(jobid),
    FortellisCacheEnums.txEnvelope
  );
  if (!JobData || !txEnvelope) {
    const friendlyMessage =
      "Fortellis export context was lost after reconnect. Click Post again to restart the Fortellis flow.";
    CreateFortellisLogEvent(socket, "WARN", friendlyMessage, {
      jobid,
      hasJobData: !!JobData,
      hasTxEnvelope: !!txEnvelope
    });
    socket.emit("export-failed", {
      title: "Fortellis",
      severity: "warning",
      errorCode: "FORTELLIS_CONTEXT_MISSING",
      friendlyMessage
    });
    return;
  }

  try {
    const DMSVid = await redisHelpers.getSessionTransactionData(
      socket.id,
      getTransactionType(JobData.id),
      FortellisCacheEnums.DMSVid
    );
    if (!DMSVid) {
      const friendlyMessage =
        "Fortellis vehicle context is missing after reconnect. Click Post again to restart the Fortellis flow.";
      CreateFortellisLogEvent(socket, "WARN", friendlyMessage, {
        jobid,
        hasDMSVid: !!DMSVid
      });
      socket.emit("export-failed", {
        title: "Fortellis",
        severity: "warning",
        errorCode: "FORTELLIS_CONTEXT_MISSING",
        friendlyMessage
      });
      return;
    }

    let DMSCust;
    if (selectedCustomerId) {
      CreateFortellisLogEvent(socket, "DEBUG", `{3.1} Querying the Customer using Customer ID: ${selectedCustomerId}`);

      //Get cust list from Redis. Return the item
      const DMSCustList =
        (await getSessionTransactionData(socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSCustList)) || [];
      const existingCustomerInDMSCustList = DMSCustList.find((c) => c.customerId === selectedCustomerId);
      DMSCust = existingCustomerInDMSCustList || {
        customerId: selectedCustomerId //This is the fall back in case it is the generic customer.
      };
      await setSessionTransactionData(
        socket.id,
        getTransactionType(jobid),
        FortellisCacheEnums.DMSCust,
        DMSCust,
        defaultFortellisTTL
      );
    } else {
      CreateFortellisLogEvent(socket, "DEBUG", `{3.2} Creating new customer.`);
      const DMSCustomerInsertResponse = await InsertDmsCustomer({ socket, redisHelpers, JobData });
      DMSCust = { customerId: DMSCustomerInsertResponse.data };
      await setSessionTransactionData(
        socket.id,
        getTransactionType(jobid),
        FortellisCacheEnums.DMSCust,
        DMSCust,
        defaultFortellisTTL
      );
    }

    let DMSVeh;
    if (DMSVid.newId) {
      CreateFortellisLogEvent(socket, "DEBUG", `{4.1} Inserting new vehicle with ID: ID ${DMSVid.vehiclesVehId}`);
      DMSVeh = await InsertDmsVehicle({ socket, redisHelpers, JobData, txEnvelope, DMSVid, DMSCust });
    } else {
      DMSVeh = await getSessionTransactionData(socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSVeh);
      CreateFortellisLogEvent(socket, "DEBUG", `{4.3} Updating Existing Vehicle to associate to owner.`);

      //Check to see if the vehicle needs to be updated - i.e. the owner is not the selected customer.
      if (!DMSVeh?.owners.find((o) => o.id.value === DMSCust.customerId && o.id.assigningPartyId === "CURRENT")) {
        DMSVeh = await UpdateDmsVehicle({
          socket,
          redisHelpers,
          JobData,
          DMSVeh,
          DMSCust,
          selectedCustomerId: selectedCustomerId || DMSCust.customerId,
          txEnvelope
        });
        await setSessionTransactionData(
          socket.id,
          getTransactionType(jobid),
          FortellisCacheEnums.DMSVeh,
          DMSVeh,
          defaultFortellisTTL
        );
      }
    }
    // const DMSVehHistory = await InsertServiceVehicleHistory({ socket, redisHelpers, JobData });
    // await setSessionTransactionData(socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSVehHistory, DMSVehHistory, defaultFortellisTTL);

    CreateFortellisLogEvent(socket, "DEBUG", `{5} Creating Transaction header with Dms Start WIP`);
    const DMSTransHeader = await InsertDmsStartWip({ socket, redisHelpers, JobData });
    await setSessionTransactionData(
      socket.id,
      getTransactionType(jobid),
      FortellisCacheEnums.DMSTransHeader,
      DMSTransHeader,
      defaultFortellisTTL
    );

    CreateFortellisLogEvent(socket, "DEBUG", `{5.1} Creating Transaction with ID ${DMSTransHeader.transID}`);

    if (DMSTransHeader.rtnCode === "0") {
      try {
        CreateFortellisLogEvent(
          socket,
          "DEBUG",
          `{6} Attempting to post Transaction with ID ${DMSTransHeader.transID}`
        );

        const DmsBatchTxnPost = await PostDmsBatchWip({ socket, redisHelpers, JobData }); // 2 in 1 call that includes a post and the transactions.
        await setSessionTransactionData(
          socket.id,
          getTransactionType(jobid),
          FortellisCacheEnums.DmsBatchTxnPost,
          DmsBatchTxnPost,
          defaultFortellisTTL
        );

        if (DmsBatchTxnPost.rtnCode === "0") {
          //TODO: Validate this is a string and not #
          //something
          CreateFortellisLogEvent(socket, "DEBUG", `{6} Successfully posted transaction to DMS.`);

          await MarkJobExported({ socket, jobid: JobData.id, JobData, redisHelpers });

          try {
            CreateFortellisLogEvent(socket, "DEBUG", `{7} Updating Service Vehicle History.`);
            const DMSVehHistory = await InsertServiceVehicleHistory({ socket, redisHelpers, JobData });
            await setSessionTransactionData(
              socket.id,
              getTransactionType(jobid),
              FortellisCacheEnums.DMSVehHistory,
              DMSVehHistory,
              defaultFortellisTTL
            );
          } catch (error) {
            CreateFortellisLogEvent(socket, "ERROR", `{7.1} Error posting vehicle service history. ${error.message}`);
          }

          socket.emit("export-success", JobData.id);
        } else {
          //There was something wrong. Throw an error to trigger clean up.
          const batchPostError = new Error(DmsBatchTxnPost.sendline || "Error posting DMS Batch Transaction");
          batchPostError.errorData = { DMSTransHeader, DmsBatchTxnPost };
          throw batchPostError;
        }
      } catch (error) {
        //Clean up the transaction and insert a faild error code
        // //Get the error code
        CreateFortellisLogEvent(socket, "DEBUG", `{6.1} Getting errors for Transaction ID ${DMSTransHeader.transID}`);

        let dmsErrors = [];
        try {
          const DmsError = await QueryDmsErrWip({ socket, redisHelpers, JobData });
          dmsErrors = Array.isArray(DmsError?.errMsg) ? DmsError.errMsg.filter((e) => e !== null && e !== "") : [];

          dmsErrors.forEach((e) => {
            CreateFortellisLogEvent(socket, "ERROR", `Error encountered in posting transaction => ${e} `);
          });
        } catch (queryError) {
          CreateFortellisLogEvent(
            socket,
            "ERROR",
            `{6.1} Unable to read ErrWIP for Transaction ID ${DMSTransHeader.transID}: ${queryError.message}`
          );
        }

        //Delete the transaction, even if querying ErrWIP fails.
        CreateFortellisLogEvent(socket, "DEBUG", `{6.2} Deleting Transaction ID ${DMSTransHeader.transID}`);
        try {
          await DeleteDmsWip({ socket, redisHelpers, JobData });
        } catch (cleanupError) {
          CreateFortellisLogEvent(
            socket,
            "ERROR",
            `{6.2} Failed cleanup for Transaction ID ${DMSTransHeader.transID}: ${cleanupError.message}`
          );
        }

        if (!error.errorData || typeof error.errorData !== "object") {
          error.errorData = {};
        }
        error.errorData.issues = dmsErrors.length ? dmsErrors : [error.message];
        throw error;
      }
    }
  } catch (error) {
    CreateFortellisLogEvent(socket, "ERROR", `Error in FortellisSelectedCustomer - ${error} `, {
      error: error.message,
      stack: error.stack,
      data: error.errorData
    });
    socket.emit("export-failed", {
      title: "Fortellis",
      severity: "error",
      error: error.message,
      friendlyMessage: "Fortellis export failed. Please click Post again to retry."
    });
    await InsertFailedExportLog({
      socket,
      JobData,
      error: error.errorData?.issues || [JSON.stringify(error.errorData)]
    });
  } finally {
    //Ensure we always insert logEvents
    //GQL to insert logevents.
    //CdkBase.createLogEvent(socket, "DEBUG", `Capturing log events to database.`);
  }
}

exports.FortellisSelectedCustomer = FortellisSelectedCustomer;

async function QueryJobData({ socket, jobid }) {
  const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
  const result = await client
    .setHeaders({ Authorization: `Bearer ${socket.handshake?.auth?.token}` })
    .request(queries.QUERY_JOBS_FOR_CDK_EXPORT, { id: jobid });
  return result.jobs_by_pk;
}

async function CalculateDmsVid({ socket, JobData, redisHelpers }) {
  try {
    const result = await MakeFortellisCall({
      ...FortellisActions.GetVehicleId,
      requestPathParams: JobData.v_vin,
      headers: {},
      redisHelpers,
      socket,
      jobid: JobData.id,
      body: {}
    });
    return Array.isArray(result) ? result.filter((v) => v.vehiclesVehId !== null && v.vehiclesVehId !== "") : [];
  } catch (error) {
    handleFortellisApiError(socket, error, "CalculateDmsVid", {
      vin: JobData.v_vin,
      jobId: JobData.id
    });
    throw error;
  }
}

async function QueryDmsVehicleById({ socket, redisHelpers, JobData, DMSVid }) {
  try {
    const result = await MakeFortellisCall({
      ...FortellisActions.GetVehicleById,
      requestPathParams: DMSVid.vehiclesVehId,
      headers: {},
      redisHelpers,
      socket,
      jobid: JobData.id,
      body: {}
    });
    return result;
  } catch (error) {
    handleFortellisApiError(socket, error, "QueryDmsVehicleById", {
      vehicleId: DMSVid.vehiclesVehId,
      jobId: JobData.id
    });
    throw error;
  }
}

async function QueryDmsCustomerById({ socket, redisHelpers, JobData, CustomerId }) {
  try {
    const result = await MakeFortellisCall({
      ...FortellisActions.ReadCustomer,
      requestPathParams: CustomerId,
      headers: {},
      redisHelpers,
      socket,
      jobid: JobData.id,
      body: {}
    });
    return result.data;
  } catch (error) {
    handleFortellisApiError(socket, error, "QueryDmsCustomerById", {
      customerId: CustomerId,
      jobId: JobData.id
    });
    throw error;
  }
}

async function QueryDmsCustomerByName({ socket, redisHelpers, JobData }) {
  const ownerName =
    JobData.ownr_co_nm && JobData.ownr_co_nm.trim() !== ""
      ? //? [["firstName", JobData.ownr_co_nm.replace(replaceSpecialRegex, "").toUpperCase()]] // Commented out until we receive direction.
        [["phone", JobData.ownr_ph1?.replace(/[^0-9]/g, "")]]
      : [
          ["firstName", JobData.ownr_fn?.replace(/[^a-zA-Z0-9]/g, "").toUpperCase()],
          ["lastName", JobData.ownr_ln?.replace(/[^a-zA-Z0-9]/g, "").toUpperCase()]
        ];
  try {
    const result = await MakeFortellisCall({
      ...FortellisActions.QueryCustomerByName,
      requestSearchParams: ownerName,
      headers: {},
      redisHelpers,
      socket,
      jobid: JobData.id,
      body: {}
    });
    return result.data;
  } catch (error) {
    handleFortellisApiError(socket, error, "QueryDmsCustomerByName", {
      searchParams: ownerName,
      jobId: JobData.id
    });
    throw error;
  }
}

async function InsertDmsCustomer({ socket, redisHelpers, JobData }) {
  try {
    const isBusiness = JobData.ownr_co_nm && JobData.ownr_co_nm.replace(replaceSpecialRegex, "").trim() !== "";
    const result = await MakeFortellisCall({
      ...FortellisActions.CreateCustomer,
      headers: {},
      redisHelpers,
      socket,
      jobid: JobData.id,
      body: {
        customerType: isBusiness ? "BUSINESS" : "INDIVIDUAL",
        ...(isBusiness
          ? {
              companyName: JobData.ownr_co_nm && JobData.ownr_co_nm.replace(replaceSpecialRegex, "").toUpperCase(),
              secondaryCustomerName: {
                //lastName: JobData.ownr_co_nm && JobData.ownr_co_nm.replace(replaceSpecialRegex, "").toUpperCase()
              }
            }
          : {
              customerName: {
                //"suffix": "Mr.",
                firstName: JobData.ownr_fn && JobData.ownr_fn.replace(/[^a-zA-Z0-9]/g, "").toUpperCase(),
                //"middleName": "",
                lastName: JobData.ownr_ln && JobData.ownr_ln.replace(/[^a-zA-Z0-9]/g, "").toUpperCase()
                //"title": "",
                //"nickName": ""
              }
            }),
        postalAddress: {
          addressLine1: JobData.ownr_addr1?.replace(replaceSpecialRegex, "").trim().toUpperCase(),
          addressLine2: JobData.ownr_addr2?.replace(replaceSpecialRegex, "").trim().toUpperCase(),
          city: JobData.ownr_city?.replace(replaceSpecialRegex, "").trim().toUpperCase(),
          postalCode: InstanceMgr({
            imex: JobData.ownr_zip && JobData.ownr_zip.toUpperCase().replace(/\W/g, "").replace(/(...)/, "$1 "),
            rome: JobData.ownr_zip
          }),
          state: JobData.ownr_st?.replace(replaceSpecialRegex, "").trim().toUpperCase(),
          country: JobData.ownr_ctry?.replace(replaceSpecialRegex, "").trim().toUpperCase()
          //"territory": ""
        },
        // "birthDate": {
        //   "day": "15",
        //   "month": "07",
        //   "year": "1979"
        // },
        //"gender": "M",
        //"language": "English",
        contactMethods: {
          phones: [
            {
              //"uuid": "",
              number: JobData.ownr_ph1?.replace(/[^0-9]/g, ""),
              type: "HOME"
              // "doNotCallIndicator": true,
              // "doNotCallIndicatorDate": `null,
              // "doNotCallRegistrySource": "",
              // "isOnDoNotCallRegistry": false,
              // "isPrimary": false,
              // "isPreferred": false,
              // "isTextMessageAllowed": false,
              // "textMessageCarrier": "",
              // "optInDate": null,
              // "optInRequestedDate": null,
              // "preferredDay": "",
              // "preferredTime": ""
            }
            // {
            //   "uuid": "",
            //   "number": "6666666666",
            //   "type": "MOBILE",
            //   "doNotCallIndicator": true,
            //   "doNotCallIndicatorDate": null,
            //   "doNotCallRegistrySource": "",
            //   "isOnDoNotCallRegistry": false,
            //   "isPrimary": true,
            //   "isPreferred": true,
            //   "isTextMessageAllowed": false,
            //   "textMessageCarrier": "",
            //   "optInDate": null,
            //   "optInRequestedDate": null,
            //   "preferredDay": "",
            //   "preferredTime": ""
            // }
          ],
          emailAddresses: [
            ...(!_.isEmpty(JobData.ownr_ea)
              ? [
                  {
                    //"uuid": "",
                    address: JobData.ownr_ea.toUpperCase(),
                    type: "PERSONAL"
                    // "doNotEmailSource": "",
                    // "doNotEmail": false,
                    // "isPreferred": true,
                    // "transactionEmailNotificationOptIn": false,
                    // "optInRequestDate": null,
                    // "optInDate": null
                  }
                ]
              : [])
            // {
            //   "uuid": "",
            //   "address": "jilldoe@test.com",
            //   "type": "WORK",
            //   "doNotEmailSource": "",
            //   "doNotEmail": false,
            //   "isPreferred": false,
            //   "transactionEmailNotificationOptIn": false,
            //   "optInRequestDate": null,
            //   "optInDate": null
            // }
          ]
        }
        // // "doNotContact": false,
        // // "optOutDate": "",
        // // "optOutTime": "",
        // // "optOutFlag": false,
        // // "isDeleteDataFlag": false,
        // // "deleteDataDate": "",
        // // "deleteDataTime": "",
        // // "blockMailFlag": true,
        // // "dateAdded": "",
        // // "employer": "employer",
        // "insurance": {
        //   "policy": {
        //     "effectiveDate": "2022-01-01",
        //     "expirationDate": "2023-01-01",
        //     "number": "12345",
        //     "verifiedBy": "Agent",
        //     "verifiedDate": "2023-01-01",
        //     "insPolicyCollisionDed": "",
        //     "insPolicyComprehensiveDed": "",
        //     "insPolicyFireTheftDed": ""
        //   },
        //   "insuranceAgency": {
        //     "agencyName": "InsAgency",
        //     "agentName": "agent",
        //     "phoneNumber": "9999999999",
        //     "postalAddress": {
        //       "addressLine1": "999 Main St",
        //       "addressLine2": "Suite 999",
        //       "city": "Austin",
        //       "state": "TX",
        //       "postalCode": "78750",
        //       "county": "Travis",
        //       "country": "USA"
        //     }
        //   },
        //   "insuranceCompany": {
        //     "name": "InsCompany",
        //     "phoneNumber": "8888888888",
        //     "postalAddress": {
        //       "addressLine1": "888 Main St",
        //       "addressLine2": "Suite 888",
        //       "city": "Austin",
        //       "state": "TX",
        //       "postalCode": "78750",
        //       "county": "Travis",
        //       "country": "USA"
        //     }
        //   }
        // },
        // "specialInstructions": [
        //   {
        //     "lineNuber": "1",
        //     "specialInstruction": "specialInstruction1"
        //   },
        //   {
        //     "lineNuber": "2",
        //     "specialInstruction": "specialInstruction2"
        //   }
        // ],
        // "groupCode": "PT",
        // "priceCode": "5",
        // "roPriceCode": "5",
        // "taxCode": "3145",
        // "dealerLoyaltyIndicator": "PN612345",
        // "delCdeServiceNames": "99",
        // "deleteCode": "9999",
        // "fleetFlag": "1",
        // "dealerFields": [
        //   {
        //     "lineNumber": null,
        //     "dealerField": "Custom dealer field value 1"
        //   },
        //   {
        //     "lineNumber": null,
        //     "dealerField": "Custom dealer field value 2"
        //   },
        //   {
        //     "lineNumber": null,
        //     "dealerField": "Custom dealer field value 3"
        //   }
        // ]
      }
    });
    return result;
  } catch (error) {
    handleFortellisApiError(socket, error, "InsertDmsCustomer", {
      customerData: {
        firstName: JobData.ownr_fn,
        lastName: JobData.ownr_ln,
        companyName: JobData.ownr_co_nm
      },
      jobId: JobData.id
    });
    throw error;
  }
}

async function InsertDmsVehicle({ socket, redisHelpers, JobData, txEnvelope, DMSVid, DMSCust }) {
  try {
    const result = await MakeFortellisCall({
      ...FortellisActions.InsertVehicle,
      requestSearchParams: {},
      headers: {},
      redisHelpers,
      socket,
      jobid: JobData.id,
      body: {
        dealer: {
          //company: JobData.bodyshop.cdk_configuration.srcco || "77",
          // "dealNumber": "",
          // "dealerAssignedNumber": "82268",
          // "dealerDefined1": "2WDSP",
          // "dealerDefined2": "33732.71",
          // "dealerDefined3": "",
          // "dealerDefined4": "G0901",
          // "dealerDefined5": "",
          // "dealerDefined6": "",
          // "dealerDefined7": "",
          // "dealerDefined8": "",
          // "dealerNumber": "",
          ...(txEnvelope.inservicedate && {
            inServiceDate:
              txEnvelope.dms_unsold === true
                ? ""
                : moment(txEnvelope.inservicedate)
                    //.tz(JobData.bodyshop.timezone)
                    .startOf("day")
                    .toISOString()
          }),
          //"lastServiceDate": "2011-11-23",
          vehicleId: DMSVid.vehiclesVehId
          // "vehicleLocation": "",
          // "vehicleSoldDate": "2021-04-06",
          // "wholesaleVehicleInd": false
        },
        // "manufacturer": {
        //   "name": "",
        //   "plant": "",
        //   "productionNumber": "PZPKM6",
        //   "vehicleProductionDate": "2020-04-06"
        // },
        // "invoice": {
        //   "entryDate": "2012-01-19",
        //   "freight": {
        //     "freightInCharge": 995.95,
        //     "freightOutCharge": 95.95,
        //     "freightTaxCharge": 5.95
        //   },
        //   "vehicleAcquisitionDate": "2012-01-18",
        //   "vehicleOrderDate": "2012-01-12",
        //   "vehicleOrderNumber": "",
        //   "vehicleOrderPriority": "",
        //   "vehicleOrderType": "TRE RETAIL - STOCK"
        // },
        vehicle: {
          // "axleCode": "GU6/3.42 REAR AXLE RATIO",
          // "axleCount": 2,
          // "bodyStyle": "PU",
          // "brakeSystem": "",
          // "cabType": "",
          // "certifiedPreownedInd": false,
          // "certifiedPreownedNumber": "",
          // "chassis": "",
          // "color": "",
          // "dealerBodyStyle": "",
          deliveryDate:
            txEnvelope.dms_unsold === true
              ? ""
              : moment()
                  // .tz(JobData.bodyshop.timezone)
                  .format("YYYY-MM-DD"),
          // "deliveryMileage": 4,
          // "doorsQuantity": 4,
          // "engineNumber": "",
          // "engineType": "LMG/VORTEC 5.3L VVT V8 SFI FLEXFUEL",
          // "exteriorColor": "",
          // "fleetVehicleId": "",
          // "frontTireCode": "",
          // "gmRPOCode": "",
          // "ignitionKeyNumber": "",
          // "interiorColor": "EBONY",
          licensePlateNo:
            JobData.plate_no === null
              ? null
              : String(JobData.plate_no).replace(/([^\w]|_)/g, "").length === 0
                ? null
                : String(JobData.plate_no)
                    .replace(/([^\w]|_)/g, "")
                    .toUpperCase(),
          make: txEnvelope.dms_make,
          // "model": "CC10753",
          modelAbrev: txEnvelope.dms_model,
          // "modelDescription": "SILVERADO 1500 2WD EXT CAB LT",
          // "modelType": "T",
          modelYear:
            JobData.v_model_yr &&
            (JobData.v_model_yr < 100
              ? JobData.v_model_yr >= (moment().year() + 1) % 100
                ? 1900 + parseInt(JobData.v_model_yr, 10)
                : 2000 + parseInt(JobData.v_model_yr, 10)
              : JobData.v_model_yr),
          // "numberOfEngineCylinders": 4,
          odometerStatus: txEnvelope.kmout,
          // "options": [
          //   {
          //     "optionCategory": "SS",
          //     "optionCode": "A95",
          //     "optionCost": 875.6,
          //     "optionDescription": "FRONT BUCKET SEATS INCLUDING: PWR SEAT ADJUST DRIVER 6-WAY",
          //     "optionPrices": [
          //       {
          //         "optionPricingType": "RETAIL",
          //         "price": 995
          //       },
          //       {
          //         "optionPricingType": "INVOICE",
          //         "price": 875.6
          //       }
          //     ]
          //   },
          //   {
          //     "optionCategory": "E",
          //     "optionCode": "LMG",
          //     "optionCost": 0,
          //     "optionDescription": "VORTEC 5.3L V8 SFI ENGINE W/ACTIVE FUEL MANAGEMENT",
          //     "optionPrices": [
          //       {
          //         "optionPricingType": "RETAIL",
          //         "price": 0
          //       },
          //       {
          //         "optionPricingType": "INVOICE",
          //         "price": 0
          //       }
          //     ]
          //   }
          // ],
          // "rearTireCode": "",
          // "restraintSystem": "",
          saleClassValue: "MISC",
          // "sourceCodeValue": "",
          // "sourceDescription": "",
          // "standardEquipment": "",
          // "stickerNumber": "",
          // "transmissionType": "A",
          // "transmissionNo": "MYC/ELECTRONIC 6-SPEED AUTOMATIC W/OD",
          // "trimCode": "",
          // "vehicleNote": "",
          // "vehiclePrices": [
          //   {
          //     "price": 35894,
          //     "vehiclePricingType": "SELLINGPRICE"
          //   },
          //   {
          //     "price": 33749.87,
          //     "vehiclePricingType": "INVOICE"
          //   },
          //   {
          //     "price": 36472,
          //     "vehiclePricingType": "RETAIL"
          //   },
          //   {
          //     "price": 28276.66,
          //     "vehiclePricingType": "BASEINVOICE"
          //   },
          //   {
          //     "price": 30405,
          //     "vehiclePricingType": "BASERETAIL"
          //   },
          //   {
          //     "price": 33749.87,
          //     "vehiclePricingType": "COMMISSIONPRICE"
          //   },
          //   {
          //     "price": 32702.9,
          //     "vehiclePricingType": "DRAFTAMOUNT"
          //   }
          // ],
          // "vehicleStatus": "G",
          // "vehicleStock": "82268",
          // "vehicleWeight": "6800",
          vin: JobData.v_vin.toUpperCase()
          // "warrantyExpDate": "2015-01-12",
          // "wheelbase": ""
        },
        owners: [
          {
            id: {
              assigningPartyId: "CURRENT",
              value: DMSCust.customerId
            }
          }
        ]
        //"inventoryAccount": "237"
      }
    });
    return result.data;
  } catch (error) {
    handleFortellisApiError(socket, error, "InsertDmsVehicle", {
      vin: JobData.v_vin,
      vehicleId: DMSVid.vehiclesVehId,
      customerId: DMSCust.customerId,
      jobId: JobData.id
    });
    throw error;
  }
}

async function UpdateDmsVehicle({ socket, redisHelpers, JobData, DMSVeh, DMSCust, selectedCustomerId, txEnvelope }) {
  try {
    let ids = [];

    //if it's a generic customer, don't update the vehicle owners.

    if (selectedCustomerId === JobData.bodyshop.cdk_configuration.generic_customer_number) {
      ids = DMSVeh?.owners && DMSVeh.owners;
    } else {
      const existingOwnerinVeh = DMSVeh?.owners && DMSVeh.owners.find((o) => o.id.value === DMSCust.customerId);

      if (existingOwnerinVeh) {
        ids = DMSVeh.owners.map((o) => {
          return {
            id: {
              assigningPartyId: o.id.value === DMSCust.customerId ? "CURRENT" : "PREVIOUS",
              value: o.id.value
            }
          };
        });
      } else {
        const oldOwner = DMSVeh?.owners && DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT");

        ids = [
          {
            id: {
              assigningPartyId: "CURRENT",
              value: DMSCust.customerId
            }
          },
          ...(oldOwner
            ? [
                {
                  id: {
                    assigningPartyId: "PREVIOUS",
                    value: oldOwner.id.value
                  }
                }
              ]
            : [])
        ];
      }
    }

    const DMSVehToSend = _.cloneDeep(DMSVeh);
    //Remove unsupported fields on the post API.
    delete DMSVehToSend.dealer.lastActivityDate;
    delete DMSVehToSend.manufacturer;
    delete DMSVehToSend.invoice;
    delete DMSVehToSend.inventoryAccount;

    !DMSVehToSend.vehicle.engineNumber && delete DMSVehToSend.vehicle.engineNumber;
    !DMSVehToSend.vehicle.saleClassValue && (DMSVehToSend.vehicle.saleClassValue = "MISC");
    !DMSVehToSend.vehicle.exteriorColor && delete DMSVehToSend.vehicle.exteriorColor;

    const result = await MakeFortellisCall({
      ...FortellisActions.UpdateVehicle,
      requestSearchParams: {},
      headers: {},
      redisHelpers,
      socket,
      jobid: JobData.id,
      body: {
        ...DMSVehToSend,
        dealer: {
          ...DMSVehToSend.dealer, //TODO: Check why company is blank on a queried record.

          ...((txEnvelope.inservicedate || DMSVehToSend.dealer.inServiceDate) && {
            inServiceDate:
              txEnvelope.dms_unsold === true
                ? ""
                : moment(DMSVehToSend.dealer.inServiceDate || txEnvelope.inservicedate)
                    // .tz(JobData.bodyshop.timezone)
                    .toISOString()
          })
        },
        vehicle: {
          ...DMSVehToSend.vehicle,
          ...(txEnvelope.dms_model_override
            ? {
                make: txEnvelope.dms_make,
                modelAbrev: txEnvelope.dms_model
              }
            : {}),
          deliveryDate:
            txEnvelope.dms_unsold === true
              ? ""
              : moment(DMSVehToSend.vehicle.deliveryDate)
                  //.tz(JobData.bodyshop.timezone)
                  .toISOString()
        },
        owners: ids
      }
    });
    return result;
  } catch (error) {
    handleFortellisApiError(socket, error, "UpdateDmsVehicle", {
      vin: JobData.v_vin,
      vehicleId: DMSVeh?.dealer?.vehicleId,
      customerId: DMSCust?.customerId,
      selectedCustomerId,
      jobId: JobData.id
    });
    throw error;
  }
}

async function InsertServiceVehicleHistory({ socket, redisHelpers, JobData }) {
  try {
    const txEnvelope = await redisHelpers.getSessionTransactionData(
      socket.id,
      getTransactionType(JobData.id),
      FortellisCacheEnums.txEnvelope
    );
    const DMSVid = await redisHelpers.getSessionTransactionData(
      socket.id,
      getTransactionType(JobData.id),
      FortellisCacheEnums.DMSVid
    );

    const result = await MakeFortellisCall({
      ...FortellisActions.ServiceHistoryInsert,
      headers: {},
      redisHelpers,
      socket,
      jobid: JobData.id,
      body: {
        header: {
          vehId: DMSVid.vehiclesVehId,
          roNumber: JobData.ro_number.match(/\d+/g)[0],
          mileage: txEnvelope.kmout,
          openDate: moment(JobData.actual_in).tz(JobData.bodyshop.timezone).format("YYYY-MM-DD"),
          openTime: moment(JobData.actual_in).tz(JobData.bodyshop.timezone).format("HH:mm:ss"),
          closeDate: moment(JobData.invoice_date).tz(JobData.bodyshop.timezone).format("YYYY-MM-DD"),
          closeTime: moment(JobData.invoice_date).tz(JobData.bodyshop.timezone).format("HH:mm:ss"),
          comments: txEnvelope.story?.slice(0, 40).toUpperCase(), // has to be between 0 and 40.
          cashierId: JobData.bodyshop.cdk_configuration.cashierid,
          referenceNumber: JobData.ro_number.match(/\d+/g)[0]
        }
      }
    });
    return result;
  } catch (error) {
    handleFortellisApiError(socket, error, "InsertServiceVehicleHistory", {
      roNumber: JobData.ro_number,
      jobId: JobData.id
    });
    throw error;
  }
}

async function InsertDmsStartWip({ socket, redisHelpers, JobData }) {
  try {
    const txEnvelope = await redisHelpers.getSessionTransactionData(
      socket.id,
      getTransactionType(JobData.id),
      FortellisCacheEnums.txEnvelope
    );

    const result = await MakeFortellisCall({
      ...FortellisActions.StartWip,
      headers: {},
      redisHelpers,
      socket,
      jobid: JobData.id,
      body: {
        acctgDate: moment().tz(JobData.bodyshop.timezone).format("YYYY-MM-DD"),
        desc: txEnvelope.story && txEnvelope.story.replace(replaceSpecialRegex, "").toUpperCase(),
        docType: "10",
        m13Flag: "0",
        refer: JobData.ro_number,
        // "rtnCode": "",
        // "sendline": "",
        // "groupName": "",
        srcCo: JobData.bodyshop.cdk_configuration.srcco,
        srcJrnl: txEnvelope.journal,
        transID: "",
        userID: JobData.bodyshop.cdk_configuration.cashierid,
        userName: "IMEX"

        //Cert Values Below
        // userID: "partprgm",
        // userName: "PROGRAM, PARTNER"

        // acctgDate: "2025-07-07",
        // desc: "DOCUMENT DESC. OPTIONAL REQUIREMENT",
        // docType: "3",
        // m13Flag: "0",
        // refer: "707MISC01",
        // rtnCode: "",
        // sendline: "",
        // groupName: "",
        // srcCo: "77",
        // srcJrnl: "80",
        // transID: "",
        // userID: "partprgm",
        // userName: "PROGRAM, PARTNER"
      }
    });
    return result;
  } catch (error) {
    handleFortellisApiError(socket, error, "InsertDmsStartWip", {
      roNumber: JobData.ro_number,
      jobId: JobData.id
    });
    throw error;
  }
}

async function GenerateTransWips({ socket, redisHelpers, JobData }) {
  //3rd prop sets fortellis to true to maintain logging.
  const allocations = await CalculateAllocations(socket, JobData.id, true);

  const wips = [];
  const DMSTransHeader = await redisHelpers.getSessionTransactionData(
    socket.id,
    getTransactionType(JobData.id),
    FortellisCacheEnums.DMSTransHeader
  );

  allocations.forEach((alloc) => {
    //Add the sale item from each allocation.
    if (alloc.sale.getAmount() > 0 && !alloc.tax) {
      const item = {
        acct: alloc.profitCenter.dms_acctnumber,
        cntl:
          alloc.profitCenter.dms_control_override &&
          alloc.profitCenter.dms_control_override !== null &&
          alloc.profitCenter.dms_control_override !== undefined &&
          alloc.profitCenter.dms_control_override?.trim() !== ""
            ? alloc.profitCenter.dms_control_override
            : JobData.ro_number,
        cntl2: null,
        credtMemoNo: null,
        postAmt: alloc.sale.multiply(-1).getAmount(),
        postDesc: null,
        prod: null,
        statCnt: 1,
        transID: DMSTransHeader.transID,
        trgtCoID: JobData.bodyshop.cdk_configuration.srcco
      };
      wips.push(item);
    }

    //Add the cost Item.
    if (alloc.cost.getAmount() > 0 && !alloc.tax) {
      const item = {
        acct: alloc.costCenter.dms_acctnumber,
        cntl:
          alloc.costCenter.dms_control_override &&
          alloc.costCenter.dms_control_override !== null &&
          alloc.costCenter.dms_control_override !== undefined &&
          alloc.costCenter.dms_control_override?.trim() !== ""
            ? alloc.costCenter.dms_control_override
            : JobData.ro_number,
        cntl2: null,
        credtMemoNo: null,
        postAmt: alloc.cost.getAmount(),
        postDesc: null,
        prod: null,
        statCnt: 1,
        transID: DMSTransHeader.transID,
        trgtCoID: JobData.bodyshop.cdk_configuration.srcco
      };
      wips.push(item);

      const itemWip = {
        acct: alloc.costCenter.dms_wip_acctnumber,
        cntl:
          alloc.costCenter.dms_control_override &&
          alloc.costCenter.dms_control_override !== null &&
          alloc.costCenter.dms_control_override !== undefined &&
          alloc.costCenter.dms_control_override?.trim() !== ""
            ? alloc.costCenter.dms_control_override
            : JobData.ro_number,
        cntl2: null,
        credtMemoNo: null,
        postAmt: alloc.cost.multiply(-1).getAmount(),
        postDesc: null,
        prod: null,
        statCnt: 1,
        transID: DMSTransHeader.transID,
        trgtCoID: JobData.bodyshop.cdk_configuration.srcco
      };
      wips.push(itemWip);
      //Add to the WIP account.
    }

    if (alloc.tax) {
      if (alloc.sale.getAmount() > 0) {
        const item2 = {
          acct: alloc.profitCenter.dms_acctnumber,
          cntl:
            alloc.profitCenter.dms_control_override &&
            alloc.profitCenter.dms_control_override !== null &&
            alloc.profitCenter.dms_control_override !== undefined &&
            alloc.profitCenter.dms_control_override?.trim() !== ""
              ? alloc.profitCenter.dms_control_override
              : JobData.ro_number,
          cntl2: null,
          credtMemoNo: null,
          postAmt: alloc.sale.multiply(-1).getAmount(),
          postDesc: null,
          prod: null,
          statCnt: 1,
          transID: DMSTransHeader.transID,
          trgtCoID: JobData.bodyshop.cdk_configuration.srcco
        };
        wips.push(item2);
      }
    }
  });
  const txEnvelope = await redisHelpers.getSessionTransactionData(
    socket.id,
    getTransactionType(JobData.id),
    FortellisCacheEnums.txEnvelope
  );

  txEnvelope.payers.forEach((payer) => {
    const item = {
      acct: payer.dms_acctnumber,
      cntl: payer.controlnumber,
      cntl2: null,
      credtMemoNo: null,
      postAmt: Math.round(payer.amount * 100),
      postDesc: null,
      prod: null,
      statCnt: 1,
      transID: DMSTransHeader.transID,
      trgtCoID: JobData.bodyshop.cdk_configuration.srcco
    };

    wips.push(item);
  });
  await redisHelpers.setSessionTransactionData(
    socket.id,
    getTransactionType(JobData.id),
    FortellisCacheEnums.transWips,
    wips,
    defaultFortellisTTL
  );

  return wips;
}

async function PostDmsBatchWip({ socket, redisHelpers, JobData }) {
  let DMSTransHeader;
  try {
    DMSTransHeader = await redisHelpers.getSessionTransactionData(
      socket.id,
      getTransactionType(JobData.id),
      FortellisCacheEnums.DMSTransHeader
    );

    const result = await MakeFortellisCall({
      ...FortellisActions.PostBatchWip,
      headers: {},
      redisHelpers,
      socket,
      jobid: JobData.id,
      body: {
        opCode: "P",
        transID: DMSTransHeader.transID,
        transWipReqList: await GenerateTransWips({ socket, redisHelpers, JobData })
      }
    });
    return result;
  } catch (error) {
    handleFortellisApiError(socket, error, "PostDmsBatchWip", {
      transId: DMSTransHeader?.transID,
      jobId: JobData.id
    });
    throw error;
  }
}

async function QueryDmsErrWip({ socket, redisHelpers, JobData }) {
  let DMSTransHeader;
  try {
    DMSTransHeader = await redisHelpers.getSessionTransactionData(
      socket.id,
      getTransactionType(JobData.id),
      FortellisCacheEnums.DMSTransHeader
    );

    const result = await MakeFortellisCall({
      ...FortellisActions.QueryErrorWip,
      headers: {},
      redisHelpers,
      socket,
      jobid: JobData.id,
      requestPathParams: DMSTransHeader.transID,
      body: {}
    });
    return result;
  } catch (error) {
    handleFortellisApiError(socket, error, "QueryDmsErrWip", {
      transId: DMSTransHeader?.transID,
      jobId: JobData.id
    });
    throw error;
  }
}

async function DeleteDmsWip({ socket, redisHelpers, JobData }) {
  let DMSTransHeader;
  try {
    DMSTransHeader = await redisHelpers.getSessionTransactionData(
      socket.id,
      getTransactionType(JobData.id),
      FortellisCacheEnums.DMSTransHeader
    );

    const result = await MakeFortellisCall({
      ...FortellisActions.DeleteTranWip,
      headers: {},
      redisHelpers,
      socket,
      jobid: JobData.id,
      body: {
        opCode: "D",
        transID: DMSTransHeader.transID
      }
    });
    return result;
  } catch (error) {
    handleFortellisApiError(socket, error, "DeleteDmsWip", {
      transId: DMSTransHeader?.transID,
      jobId: JobData.id
    });
    throw error;
  }
}

async function MarkJobExported({ socket, jobid, JobData, redisHelpers }) {
  CreateFortellisLogEvent(socket, "DEBUG", `Marking job as exported for id ${jobid}`);

  const transwips = await redisHelpers.getSessionTransactionData(
    socket.id,
    getTransactionType(JobData.id),
    FortellisCacheEnums.transWips
  );

  const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
  const currentToken =
    (socket?.data && socket.data.authToken) || (socket?.handshake?.auth && socket.handshake.auth.token);

  const result = await client
    .setHeaders({ Authorization: `Bearer ${currentToken}` })
    .request(queries.MARK_JOB_EXPORTED, {
      jobId: jobid,
      job: {
        status: JobData.bodyshop.md_ro_statuses.default_exported || "Exported*",
        date_exported: new Date()
      },
      log: {
        bodyshopid: JobData.bodyshop.id,
        jobid: jobid,
        successful: true,
        useremail: socket.user.email,
        metadata: transwips
      },
      bill: {
        exported: true,
        exported_at: new Date()
      }
    });

  return result;
}

async function InsertFailedExportLog({ socket, JobData, error }) {
  try {
    const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
    const currentToken =
      (socket?.data && socket.data.authToken) || (socket?.handshake?.auth && socket.handshake.auth.token);

    const result = await client
      .setHeaders({ Authorization: `Bearer ${currentToken}` })
      .request(queries.INSERT_EXPORT_LOG, {
        logs: [
          {
            bodyshopid: JobData.bodyshop.id,
            jobid: JobData.id,
            successful: false,
            message: JSON.stringify(error),
            useremail: socket.user.email
          }
        ]
      });

    return result;
  } catch (error2) {
    CreateFortellisLogEvent(socket, "ERROR", `Error in InsertFailedExportLog - ${error}`, {
      message: error2.message,
      stack: error2.stack
    });
  }
}

exports.getTransactionType = getTransactionType;
exports.FortellisJobExport = FortellisJobExport;
