const GraphQLClient = require("graphql-request").GraphQLClient;
const soap = require("soap");
const queries = require("../graphql-client/queries");
const CdkWsdl = require("./cdk-wsdl").default;
const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
const CalcualteAllocations = require("./cdk-calculate-allocations").default;
const InstanceMgr = require("../utils/instanceMgr").default;
const WsLogger = require("../web-sockets/createLogEvent");

const moment = require("moment-timezone");

const replaceSpecialRegex = /[^a-zA-Z0-9 .,\n #]+/g;

const defaultHandler = async (socket, { txEnvelope, jobid }) => {
  ////Store the following information into the redis store for this transaction.
  socket.logEvents = [];
  socket.recordid = jobid;
  socket.txEnvelope = txEnvelope;
  ////

  try {
    WsLogger.createLogEvent(socket, "DEBUG", `Received Job export request for id ${jobid}`);

    const JobData = await QueryJobData(socket, jobid);
    socket.JobData = JobData;
    const DealerId = JobData.bodyshop.cdk_dealerid;
    WsLogger.createLogEvent(socket, "DEBUG", `Dealer ID detected: ${JSON.stringify(DealerId)}`);

    WsLogger.createLogEvent(socket, "DEBUG", `{1} Begin Calculate DMS Vehicle ID using VIN: ${JobData.v_vin}`);
    socket.DMSVid = await CalculateDmsVid(socket, JobData);

    if (socket.DMSVid.newId === "N") {
      WsLogger.createLogEvent(
        socket,
        "DEBUG",
        `{2.1} Querying the Vehicle using the DMSVid: ${socket.DMSVid.vehiclesVehId}`
      );
      socket.DMSVeh = await QueryDmsVehicleById(socket, JobData, socket.DMSVid);

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

      if (DMSVehCustomer?.id && DMSVehCustomer.id.value) {
        WsLogger.createLogEvent(
          socket,
          "DEBUG",
          `{2.2} Querying the Customer using the ID from DMSVeh: ${DMSVehCustomer.id.value}`
        );
        socket.DMSVehCustomer = await QueryDmsCustomerById(socket, JobData, DMSVehCustomer.id.value);
      }
    }

    WsLogger.createLogEvent(socket, "DEBUG", `{2.3} Querying the Customer using the name.`);

    socket.DMSCustList = await QueryDmsCustomerByName(socket, JobData);

    socket.emit("cdk-select-customer", [
      ...(socket.DMSVehCustomer ? [{ ...socket.DMSVehCustomer, vinOwner: true }] : []),
      ...socket.DMSCustList
    ]);
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error encountered in CdkJobExport. ${error}`);
  }
};
exports.default = defaultHandler;

async function CdkSelectedCustomer(socket, selectedCustomerId) {
  try {
    socket.selectedCustomerId = selectedCustomerId;
    if (selectedCustomerId) {
      WsLogger.createLogEvent(socket, "DEBUG", `{3.1} Querying the Customer using Customer ID: ${selectedCustomerId}`);
      socket.DMSCust = await QueryDmsCustomerById(socket, socket.JobData, selectedCustomerId);
    } else {
      WsLogger.createLogEvent(socket, "DEBUG", `{3.2} Generating a new customer ID.`);
      const newCustomerId = await GenerateDmsCustomerNumber(socket);
      WsLogger.createLogEvent(socket, "DEBUG", `{3.3} Inserting new customer with ID: ${newCustomerId}`);
      socket.DMSCust = await InsertDmsCustomer(socket, newCustomerId);
    }

    if (socket.DMSVid.newId === "Y") {
      WsLogger.createLogEvent(
        socket,
        "DEBUG",
        `{4.1} Inserting new vehicle with ID: ID ${socket.DMSVid.vehiclesVehId}`
      );
      socket.DMSVeh = await InsertDmsVehicle(socket);
    } else {
      WsLogger.createLogEvent(
        socket,
        "DEBUG",
        `{4.2} Querying Existing Vehicle using ID ${socket.DMSVid.vehiclesVehId}`
      );
      socket.DMSVeh = await QueryDmsVehicleById(socket, socket.JobData, socket.DMSVid);
      WsLogger.createLogEvent(socket, "DEBUG", `{4.3} Updating Existing Vehicle to associate to owner.`);
      socket.DMSVeh = await UpdateDmsVehicle(socket);
    }

    WsLogger.createLogEvent(socket, "DEBUG", `{5} Creating Transaction header with Dms Start WIP`);
    socket.DMSTransHeader = await InsertDmsStartWip(socket);
    WsLogger.createLogEvent(socket, "DEBUG", `{5.1} Creating Transaction with ID ${socket.DMSTransHeader.transID}`);

    socket.DMSBatchTxn = await InsertDmsBatchWip(socket);
    WsLogger.createLogEvent(
      socket,
      "DEBUG",
      `{6} Attempting to post Transaction with ID ${socket.DMSTransHeader.transID}`
    );
    socket.DmsBatchTxnPost = await PostDmsBatchWip(socket);
    if (socket.DmsBatchTxnPost.code === "success") {
      //something
      WsLogger.createLogEvent(socket, "DEBUG", `{6} Successfully posted sransaction to DMS.`);

      await MarkJobExported(socket, socket.JobData.id);

      WsLogger.createLogEvent(socket, "DEBUG", `{5} Updating Service Vehicle History.`);
      socket.DMSVehHistory = await InsertServiceVehicleHistory(socket);
      socket.emit("export-success", socket.JobData.id);
    } else {
      //Get the error code
      WsLogger.createLogEvent(
        socket,
        "DEBUG",
        `{6.1} Getting errors for Transaction ID ${socket.DMSTransHeader.transID}`
      );
      socket.DmsError = await QueryDmsErrWip(socket);
      //Delete the transaction
      WsLogger.createLogEvent(socket, "DEBUG", `{6.2} Deleting Transaction ID ${socket.DMSTransHeader.transID}`);
      socket.DmsBatchTxnPost = await DeleteDmsWip(socket);

      socket.DmsError.errMsg
        .split("|")
        .map(
          (e) =>
            e !== null &&
            e !== "" &&
            WsLogger.createLogEvent(socket, "ERROR", `Error(s) encountered in posting transaction. ${e}`)
        );
    }
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error encountered in CdkSelectedCustomer. ${error}`);
    await InsertFailedExportLog(socket, error);
  } finally {
    //Ensure we always insert logEvents
    //GQL to insert logevents.

    WsLogger.createLogEvent(socket, "DEBUG", `Capturing log events to database.`);
  }
}

exports.CdkSelectedCustomer = CdkSelectedCustomer;

async function QueryJobData(socket, jobid) {
  WsLogger.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`);
  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.QUERY_JOBS_FOR_CDK_EXPORT, { id: jobid });

  //WsLogger.createLogEvent(socket, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`);
  return result.jobs_by_pk;
}

async function CalculateDmsVid(socket, JobData) {
  try {
    const soapClientVehicleInsertUpdate = await soap.createClientAsync(CdkWsdl.VehicleInsertUpdate);
    const soapResponseVehicleInsertUpdate = await soapClientVehicleInsertUpdate.getVehIdsAsync({
      arg0: CDK_CREDENTIALS,
      arg1: { id: JobData.bodyshop.cdk_dealerid },
      arg2: { VIN: JobData.v_vin }
    });

    const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate;
    WsLogger.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.getVehIdsAsync request.`);

    WsLogger.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.getVehIdsAsync response.`);

    WsLogger.createLogEvent(
      socket,
      "SILLY",
      `soapClientVehicleInsertUpdate.getVehIdsAsync Result ${JSON.stringify(result, null, 2)}`
    );
    CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);

    //if (result && result.return && result.return.length > 1) {
    return result.return.find((r) => r.vehiclesVehId);
    //}

    //return result && result.return && result.return[0];
  } catch (error) {
    WsLogger.createXmlEvent(socket, error.request, `soapClientVehicleInsertUpdate.getVehIdsAsync request.`, true);

    WsLogger.createXmlEvent(
      socket,
      error.response?.data,
      `soapClientVehicleInsertUpdate.getVehIdsAsync response.`,
      true
    );
    WsLogger.createLogEvent(socket, "ERROR", `{1} Error in CalculateDmsVid - ${error}`);
    throw new Error(error);
  }
}

async function QueryDmsVehicleById(socket, JobData, DMSVid) {
  try {
    const soapClientVehicleInsertUpdate = await soap.createClientAsync(CdkWsdl.VehicleInsertUpdate);

    const soapResponseVehicleInsertUpdate = await soapClientVehicleInsertUpdate.readAsync({
      arg0: CDK_CREDENTIALS,
      arg1: { id: JobData.bodyshop.cdk_dealerid },
      arg2: {
        fileType: "VEHICLES",
        vehiclesVehicleId: DMSVid.vehiclesVehId
      }
    });

    const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate;

    WsLogger.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.readAsync request.`);

    WsLogger.createLogEvent(
      socket,
      "SILLY",
      `soapClientVehicleInsertUpdate.readAsync Result ${JSON.stringify(result, null, 2)}`
    );
    WsLogger.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.readAsync response.`);
    CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);
    const VehicleFromDMS = result?.return && result.return.vehicle;
    return VehicleFromDMS;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in QueryDmsVehicleById - ${error}`);
    throw new Error(error);
  }
}

async function QueryDmsCustomerById(socket, JobData, CustomerId) {
  try {
    const soapClientCustomerInsertUpdate = await soap.createClientAsync(CdkWsdl.CustomerInsertUpdate);
    const soapResponseCustomerInsertUpdate = await soapClientCustomerInsertUpdate.readAsync({
      arg0: CDK_CREDENTIALS,
      arg1: { dealerId: JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards.
      arg2: {
        // userId: CustomerId,
      },
      arg3: CustomerId
    });

    const [result, rawResponse, , rawRequest] = soapResponseCustomerInsertUpdate;

    WsLogger.createXmlEvent(socket, rawRequest, `soapClientCustomerInsertUpdate.readAsync request.`);

    WsLogger.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.readAsync response.`);
    WsLogger.createLogEvent(
      socket,
      "SILLY",
      `soapClientCustomerInsertUpdate.readAsync Result ${JSON.stringify(result, null, 2)}`
    );
    CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
    const CustomersFromDms = result?.return && result.return.customerParty;
    return CustomersFromDms;
  } catch (error) {
    WsLogger.createXmlEvent(socket, error.request, `soapClientCustomerInsertUpdate.readAsync request.`, true);

    WsLogger.createXmlEvent(socket, error.response?.data, `soapClientCustomerInsertUpdate.readAsync response.`, true);

    WsLogger.createLogEvent(socket, "ERROR", `Error in QueryDmsCustomerById - ${error}`);
    throw new Error(error);
  }
}

async function QueryDmsCustomerByName(socket, JobData) {
  const ownerName = (
    JobData.ownr_co_nm && JobData.ownr_co_nm.trim() !== ""
      ? JobData.ownr_co_nm
      : `${JobData.ownr_ln},${JobData.ownr_fn}`
  ).replace(replaceSpecialRegex, "");

  WsLogger.createLogEvent(socket, "DEBUG", `Begin Query DMS Customer by Name using: ${ownerName}`);

  try {
    const soapClientCustomerSearch = await soap.createClientAsync(CdkWsdl.CustomerSearch);
    const soapResponseCustomerSearch = await soapClientCustomerSearch.executeSearchAsync({
      arg0: CDK_CREDENTIALS,
      arg1: { dealerId: JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards.
      arg2: {
        verb: "EXACT",
        key: ownerName
      }
    });

    const [result, rawResponse, , rawRequest] = soapResponseCustomerSearch;

    WsLogger.createXmlEvent(socket, rawRequest, `soapClientCustomerSearch.executeSearchBulkAsync request.`);

    WsLogger.createXmlEvent(socket, rawResponse, `soapClientCustomerSearch.executeSearchBulkAsync response.`);

    WsLogger.createLogEvent(
      socket,
      "SILLY",
      `soapClientCustomerSearch.executeSearchBulkAsync Result ${JSON.stringify(result, null, 2)}`
    );
    CheckCdkResponseForError(socket, soapResponseCustomerSearch);
    const CustomersFromDms = result?.return || [];
    return CustomersFromDms;
  } catch (error) {
    WsLogger.createXmlEvent(socket, error.request, `soapClientCustomerSearch.executeSearchBulkAsync request.`, true);

    WsLogger.createXmlEvent(
      socket,
      error.response?.data,
      `soapClientCustomerSearch.executeSearchBulkAsync response.`,
      true
    );

    WsLogger.createLogEvent(socket, "ERROR", `Error in QueryDmsCustomerByName - ${error}`);
    throw new Error(error);
  }
}

async function GenerateDmsCustomerNumber(socket) {
  try {
    const soapClientCustomerInsertUpdate = await soap.createClientAsync(CdkWsdl.CustomerInsertUpdate);
    const soapResponseCustomerInsertUpdate = await soapClientCustomerInsertUpdate.getCustomerNumberAsync(
      {
        arg0: CDK_CREDENTIALS,
        //TODO: Verify why this does not follow the other standards.
        arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
        arg2: { userId: null }
      },

      {}
    );

    const [result, rawResponse, , rawRequest] = soapResponseCustomerInsertUpdate;

    WsLogger.createXmlEvent(socket, rawRequest, `soapClientCustomerInsertUpdate.getCustomerNumberAsync request.`);

    WsLogger.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.getCustomerNumberAsync response.`);

    WsLogger.createLogEvent(
      socket,
      "SILLY",
      `soapClientCustomerInsertUpdate.getCustomerNumberAsync Result ${JSON.stringify(result, null, 2)}`
    );
    CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
    const customerNumber = result?.return && result.return.customerNumber;
    return customerNumber;
  } catch (error) {
    WsLogger.createXmlEvent(
      socket,
      error.request,
      `soapClientCustomerInsertUpdate.getCustomerNumberAsync request.`,
      true
    );

    WsLogger.createXmlEvent(
      socket,
      error.response?.data,
      `soapClientCustomerInsertUpdate.getCustomerNumberAsync response.`,
      true
    );
    WsLogger.createLogEvent(socket, "ERROR", `Error in GenerateDmsCustomerNumber - ${error}`);
    throw new Error(error);
  }
}

async function InsertDmsCustomer(socket, newCustomerNumber) {
  try {
    const soapClientCustomerInsertUpdate = await soap.createClientAsync(CdkWsdl.CustomerInsertUpdate);
    const soapResponseCustomerInsertUpdate = await soapClientCustomerInsertUpdate.insertAsync(
      {
        arg0: CDK_CREDENTIALS,
        arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
        arg2: { userId: null },
        arg3: {
          //Copied the required fields from the other integration.
          //TODO: Verify whether we need to bring more information in.
          id: { value: newCustomerNumber },
          address: {
            addressLine: socket.JobData.ownr_addr1 && socket.JobData.ownr_addr1.replace(replaceSpecialRegex, ""),
            city: socket.JobData.ownr_city && socket.JobData.ownr_city.replace(replaceSpecialRegex, ""),
            country: socket.JobData.ownr_ctry && socket.JobData.ownr_ctry.replace(replaceSpecialRegex, ""),
            postalCode: InstanceMgr({
              imex:
                socket.JobData.ownr_zip &&
                socket.JobData.ownr_zip //TODO Need to remove for US Based customers.
                  .toUpperCase()
                  .replace(/\W/g, "")
                  .replace(/(...)/, "$1 "),
              rome: socket.JobData.ownr_zip
            }),
            stateOrProvince: socket.JobData.ownr_st && socket.JobData.ownr_st.replace(replaceSpecialRegex, "")
          },
          contactInfo: {
            mainTelephoneNumber: {
              main: true,
              value: socket.JobData.ownr_ph1 && socket.JobData.ownr_ph1.replace(replaceSpecialRegex, "")
            },
            email: {
              desc: socket.JobData.ownr_ea ? "Other" : "CustomerDeclined",
              value: socket.JobData.ownr_ea ? socket.JobData.ownr_ea : null
            }
          },
          demographics: null,
          name1: {
            companyName:
              socket.JobData.ownr_co_nm && socket.JobData.ownr_co_nm.replace(replaceSpecialRegex, "").toUpperCase(),

            firstName: socket.JobData.ownr_fn && socket.JobData.ownr_fn.replace(replaceSpecialRegex, "").toUpperCase(),
            fullname: null,
            lastName: socket.JobData.ownr_ln && socket.JobData.ownr_ln.replace(replaceSpecialRegex, "").toUpperCase(),
            middleName: null,
            nameType:
              socket.JobData.ownr_co_nm && String(socket.JobData.ownr_co_nm).trim() !== "" ? "Business" : "Person",
            suffix: null,
            title: null
          }
        }
      },

      {}
    );

    const [result, rawResponse, , rawRequest] = soapResponseCustomerInsertUpdate;
    WsLogger.createXmlEvent(socket, rawRequest, `soapClientCustomerInsertUpdate.insertAsync request.`);

    WsLogger.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.insertAsync response.`);
    WsLogger.createLogEvent(
      socket,
      "SILLY",
      `soapClientCustomerInsertUpdate.insertAsync Result ${JSON.stringify(result, null, 2)}`
    );
    CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
    const customer = result?.return && result.return.customerParty;
    return customer;
  } catch (error) {
    WsLogger.createXmlEvent(socket, error.request, `soapClientCustomerInsertUpdate.insertAsync request.`, true);

    WsLogger.createXmlEvent(socket, error.response?.data, `soapClientCustomerInsertUpdate.insertAsync response.`, true);
    WsLogger.createLogEvent(socket, "ERROR", `Error in InsertDmsCustomer - ${error}`);
    throw new Error(error);
  }
}

async function InsertDmsVehicle(socket) {
  try {
    const soapClientVehicleInsertUpdate = await soap.createClientAsync(CdkWsdl.VehicleInsertUpdate);

    const soapResponseVehicleInsertUpdate = await soapClientVehicleInsertUpdate.insertAsync({
      arg0: CDK_CREDENTIALS,
      arg1: { id: socket.JobData.bodyshop.cdk_dealerid },
      arg2: {
        dealer: {
          dealerNumber: socket.JobData.bodyshop.cdk_dealerid,
          ...(socket.txEnvelope.inservicedate && {
            inServiceDate:
              socket.txEnvelope.dms_unsold === true
                ? ""
                : moment(socket.txEnvelope.inservicedate)
                    //.tz(socket.JobData.bodyshop.timezone)
                    .startOf("day")
                    .toISOString()
          }),
          vehicleId: socket.DMSVid.vehiclesVehId
        },
        manufacturer: {},
        vehicle: {
          deliveryDate:
            socket.txEnvelope.dms_unsold === true
              ? ""
              : moment()
                  // .tz(socket.JobData.bodyshop.timezone)
                  .format("YYYYMMDD"),
          licensePlateNo:
            socket.JobData.plate_no === null
              ? null
              : String(socket.JobData.plate_no).replace(/([^\w]|_)/g, "").length === 0
                ? null
                : String(socket.JobData.plate_no)
                    .replace(/([^\w]|_)/g, "")
                    .toUpperCase(),
          make: socket.txEnvelope.dms_make,
          modelAbrev: socket.txEnvelope.dms_model,
          modelYear: socket.JobData.v_model_yr,
          odometerStatus: socket.txEnvelope.kmout,
          saleClassValue: "MISC",
          VIN: socket.JobData.v_vin
        },
        owners: {
          id: {
            assigningPartyId: "CURRENT",
            value: socket.DMSCust.id.value
          }
        }
      },
      arg3: "VEHICLES"
    });

    const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate;

    WsLogger.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.insertAsync request.`);

    WsLogger.createLogEvent(
      socket,
      "SILLY",
      `soapClientVehicleInsertUpdate.insertAsync Result ${JSON.stringify(result, null, 2)}`
    );
    WsLogger.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.insertAsync response.`);
    CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);
    const VehicleFromDMS = result?.return && result.return.vehicle;
    return VehicleFromDMS;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in InsertDmsVehicle - ${error}`);
    throw new Error(error);
  }
}

async function UpdateDmsVehicle(socket) {
  try {
    const soapClientVehicleInsertUpdate = await soap.createClientAsync(CdkWsdl.VehicleInsertUpdate);

    let ids = [];

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

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

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

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

    const soapResponseVehicleInsertUpdate = await soapClientVehicleInsertUpdate.updateAsync({
      arg0: CDK_CREDENTIALS,
      arg1: { id: socket.JobData.bodyshop.cdk_dealerid },
      arg2: {
        ...socket.DMSVeh,
        dealer: {
          ...socket.DMSVeh.dealer,
          ...((socket.txEnvelope.inservicedate || socket.DMSVeh.dealer.inServiceDate) && {
            inServiceDate:
              socket.txEnvelope.dms_unsold === true
                ? ""
                : moment(socket.DMSVeh.dealer.inServiceDate || socket.txEnvelope.inservicedate)
                    // .tz(socket.JobData.bodyshop.timezone)
                    .toISOString()
          })
        },
        vehicle: {
          ...socket.DMSVeh.vehicle,
          ...(socket.txEnvelope.dms_model_override
            ? {
                make: socket.txEnvelope.dms_make,
                modelAbrev: socket.txEnvelope.dms_model
              }
            : {}),
          deliveryDate:
            socket.txEnvelope.dms_unsold === true
              ? ""
              : moment(socket.DMSVeh.vehicle.deliveryDate)
                  //.tz(socket.JobData.bodyshop.timezone)
                  .toISOString()
        },
        owners: ids
      },
      arg3: "VEHICLES"
    });
    const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate;

    WsLogger.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.updateAsync request.`);

    WsLogger.createLogEvent(
      socket,
      "DEBUG",
      `soapClientVehicleInsertUpdate.updateAsync Result ${JSON.stringify(result, null, 2)}`
    );
    WsLogger.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.updateAsync response.`);
    CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);
    const VehicleFromDMS = result?.return && result.return.vehicle;
    return VehicleFromDMS;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in UpdateDmsVehicle - ${error}`);
    throw new Error(error);
  }
}

async function InsertServiceVehicleHistory(socket) {
  try {
    const soapClientServiceHistoryInsert = await soap.createClientAsync(CdkWsdl.ServiceHistoryInsert);

    const soapResponseServiceHistoryInsert = await soapClientServiceHistoryInsert.serviceHistoryHeaderInsertAsync({
      authToken: CDK_CREDENTIALS,
      dealerId: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
      serviceHistoryHeader: {
        vehID: socket.DMSVid.vehiclesVehId,
        roNumber: socket.JobData.ro_number.match(/\d+/g),
        mileage: socket.txEnvelope.kmout,
        openDate: moment(socket.JobData.actual_in).tz(socket.JobData.bodyshop.timezone).format("YYYY-MM-DD"),
        openTime: moment(socket.JobData.actual_in).tz(socket.JobData.bodyshop.timezone).format("HH:mm:ss"),
        closeDate: moment(socket.JobData.invoice_date).tz(socket.JobData.bodyshop.timezone).format("YYYY-MM-DD"),
        closeTime: moment(socket.JobData.invoice_date).tz(socket.JobData.bodyshop.timezone).format("HH:mm:ss"),
        comments: socket.txEnvelope.story,
        cashierID: socket.JobData.bodyshop.cdk_configuration.cashierid
      }
    });

    const [result, rawResponse, , rawRequest] = soapResponseServiceHistoryInsert;

    WsLogger.createXmlEvent(socket, rawRequest, `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert request.`);

    WsLogger.createLogEvent(
      socket,
      "SILLY",
      `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert Result ${JSON.stringify(result, null, 2)}`
    );
    WsLogger.createXmlEvent(socket, rawResponse, `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert response.`);
    CheckCdkResponseForError(socket, soapResponseServiceHistoryInsert);
    return result?.return;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in InsertServiceVehicleHistory - ${error}`);
    throw new Error(error);
  }
}

async function InsertDmsStartWip(socket) {
  try {
    const soapClientAccountingGLInsertUpdate = await soap.createClientAsync(CdkWsdl.AccountingGLInsertUpdate);

    const soapResponseAccountingGLInsertUpdate = await soapClientAccountingGLInsertUpdate.doStartWIPAsync({
      arg0: CDK_CREDENTIALS,
      arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
      arg2: {
        acctgDate: moment().tz(socket.JobData.bodyshop.timezone).format("YYYY-MM-DD"),
        //socket.JobData.invoice_date
        desc: socket.txEnvelope.story && socket.txEnvelope.story.replace(replaceSpecialRegex, ""),
        docType: 10, // Need to check what this usually would be? Apparently it is almost always 10 or 7.
        //1 Cash Receipt , 2 Check, 3 Journal Voucher, 4 Parts invoice, 5 Payable Invoice, 6 Recurring Entry, 7 Repair
        // Order Invoice, 8 Vehicle Purchase Invoice, 9 Vehicle Sale Invoice, 10 Other, 11 Payroll, 12 Finance Charge,
        // 13 FMLR Invoice, 14 Parts Credit Memo, 15 Manufacturer Document, 16 FMLR Credit Memo
        m13Flag: 0,
        refer: socket.JobData.ro_number,
        srcCo: socket.JobData.bodyshop.cdk_configuration.srcco,
        srcJrnl: socket.txEnvelope.journal,
        userID: socket.JobData.bodyshop.cdk_configuration.cashierid //Where is this coming from?
        //userName: "IMEX",
      }
    });

    const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate;

    WsLogger.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doStartWIPAsync request.`);

    WsLogger.createLogEvent(
      socket,
      "SILLY",
      `soapClientAccountingGLInsertUpdate.doStartWIPAsync Result ${JSON.stringify(result, null, 2)}`
    );
    WsLogger.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doStartWIPAsync response.`);
    CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
    const TransactionHeader = result?.return;
    return TransactionHeader;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in InsertDmsStartWip - ${error}`);
    throw new Error(error);
  }
}

async function InsertDmsBatchWip(socket) {
  try {
    const soapClientAccountingGLInsertUpdate = await soap.createClientAsync(CdkWsdl.AccountingGLInsertUpdate);

    const soapResponseAccountingGLInsertUpdate = await soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync({
      arg0: CDK_CREDENTIALS,
      arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
      arg2: {
        transWIPs: await GenerateTransWips(socket)
      }
    });

    const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate;

    WsLogger.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync request.`);

    WsLogger.createLogEvent(
      socket,
      "SILLY",
      `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync Result ${JSON.stringify(result, null, 2)}`
    );
    WsLogger.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync response.`);
    CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
    const BatchWipResult = result?.return;
    return BatchWipResult;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in InsertDmsBatchWip - ${error}`);
    throw new Error(error);
  }
}

async function GenerateTransWips(socket) {
  const allocations = await CalcualteAllocations(socket, socket.JobData.id);
  const wips = [];
  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
            : socket.JobData.ro_number,
        cntl2: null,
        credtMemoNo: null,
        postAmt: alloc.sale.multiply(-1).getAmount(),
        postDesc: null,
        prod: null,
        statCnt: 1,
        transID: socket.DMSTransHeader.transID,
        trgtCoID: socket.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
            : socket.JobData.ro_number,
        cntl2: null,
        credtMemoNo: null,
        postAmt: alloc.cost.getAmount(),
        postDesc: null,
        prod: null,
        statCnt: 1,
        transID: socket.DMSTransHeader.transID,
        trgtCoID: socket.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
            : socket.JobData.ro_number,
        cntl2: null,
        credtMemoNo: null,
        postAmt: alloc.cost.multiply(-1).getAmount(),
        postDesc: null,
        prod: null,
        statCnt: 1,
        transID: socket.DMSTransHeader.transID,
        trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco
      };
      wips.push(itemWip);
      //Add to the WIP account.
    }

    if (alloc.tax) {
      // if (alloc.cost.getAmount() > 0) {
      //   const item = {
      //     acct: alloc.costCenter.dms_acctnumber,
      //     cntl: socket.JobData.ro_number,
      //     cntl2: null,
      //     credtMemoNo: null,
      //     postAmt: alloc.cost.getAmount(),
      //     postDesc: null,
      //     prod: null,
      //     statCnt: 1,
      //     transID: socket.DMSTransHeader.transID,
      //     trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco,
      //   };

      //   wips.push(item);
      // }

      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
              : socket.JobData.ro_number,
          cntl2: null,
          credtMemoNo: null,
          postAmt: alloc.sale.multiply(-1).getAmount(),
          postDesc: null,
          prod: null,
          statCnt: 1,
          transID: socket.DMSTransHeader.transID,
          trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco
        };
        wips.push(item2);
      }
    }
  });

  socket.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: socket.DMSTransHeader.transID,
      trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco
    };

    wips.push(item);
  });
  socket.transWips = wips;
  return wips;
}

async function PostDmsBatchWip(socket) {
  try {
    const soapClientAccountingGLInsertUpdate = await soap.createClientAsync(CdkWsdl.AccountingGLInsertUpdate);

    const soapResponseAccountingGLInsertUpdate = await soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync({
      arg0: CDK_CREDENTIALS,
      arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
      arg2: {
        postWIP: { opCode: "P", transID: socket.DMSTransHeader.transID }
      }
    });

    const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate;

    WsLogger.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync request.`);

    WsLogger.createLogEvent(
      socket,
      "SILLY",
      `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync Result ${JSON.stringify(result, null, 2)}`
    );
    WsLogger.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.`);
    // CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
    const PostResult = result?.return;
    return PostResult;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in PostDmsBatchWip - ${error}`);
    throw new Error(error);
  }
}

async function QueryDmsErrWip(socket) {
  try {
    const soapClientAccountingGLInsertUpdate = await soap.createClientAsync(CdkWsdl.AccountingGLInsertUpdate);

    const soapResponseAccountingGLInsertUpdate = await soapClientAccountingGLInsertUpdate.doErrWIPAsync({
      arg0: CDK_CREDENTIALS,
      arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
      arg2: socket.DMSTransHeader.transID
    });

    const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate;

    WsLogger.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doErrWIPAsync request.`);

    WsLogger.createLogEvent(
      socket,
      "DEBUG",
      `soapClientAccountingGLInsertUpdate.doErrWIPAsync Result ${JSON.stringify(result, null, 2)}`
    );
    WsLogger.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doErrWIPAsync response.`);
    CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
    const PostResult = result?.return;
    return PostResult;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in QueryDmsErrWip - ${error}`);
    throw new Error(error);
  }
}

async function DeleteDmsWip(socket) {
  try {
    const soapClientAccountingGLInsertUpdate = await soap.createClientAsync(CdkWsdl.AccountingGLInsertUpdate);

    const soapResponseAccountingGLInsertUpdate = await soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync({
      arg0: CDK_CREDENTIALS,
      arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
      arg2: {
        postWIP: { opCode: "D", transID: socket.DMSTransHeader.transID }
      }
    });

    const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate;

    WsLogger.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync request.`);

    WsLogger.createLogEvent(
      socket,
      "SILLY",
      `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync Result ${JSON.stringify(result, null, 2)}`
    );
    WsLogger.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.`);
    CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
    const PostResult = result?.return;
    return PostResult;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in PostDmsBatchWip - ${error}`);
    throw new Error(error);
  }
}

async function MarkJobExported(socket, jobid) {
  WsLogger.createLogEvent(socket, "DEBUG", `Marking job as exported for id ${jobid}`);
  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: socket.JobData.bodyshop.md_ro_statuses.default_exported || "Exported*",
        date_exported: new Date()
      },
      log: {
        bodyshopid: socket.JobData.bodyshop.id,
        jobid: jobid,
        successful: true,
        useremail: socket.user.email,
        metadata: socket.transWips
      },
      bill: {
        exported: true,
        exported_at: new Date()
      }
    });

  return result;
}

async function InsertFailedExportLog(socket, 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: socket.JobData.bodyshop.id,
          jobid: socket.JobData.id,
          successful: false,
          message: JSON.stringify(error),
          useremail: socket.user.email
        }]
      });

    return result;
  } catch (error2) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in InsertFailedExportLog - ${error} - ${JSON.stringify(error2)}`);
  }
}
