const GraphQLClient = require("graphql-request").GraphQLClient;
const AxiosLib = require("axios").default;
const queries = require("../../graphql-client/queries");
const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants");
const WsLogger = require("../../web-sockets/createLogEvent");
const fs = require("fs");
const path = require("path");

//const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
const CalculateAllocations = require("../../cdk/cdk-calculate-allocations").default;
const moment = require("moment-timezone");
const Dinero = require("dinero.js");
const InstanceManager = require("../../utils/instanceMgr").default;
const axios = AxiosLib.create();

axios.interceptors.request.use((x) => {
  const socket = x.socket;

  const headers = {
    ...x.headers.common,
    ...x.headers[x.method],
    ...x.headers
  };
  const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
    x.url
  } | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
  //logRequestToFile(printable);

  WsLogger.createJsonEvent(socket, "DEBUG", `Raw Request: ${printable}`, x.data);

  return x;
});

axios.interceptors.response.use((x) => {
  const socket = x.config.socket;

  const printable = `${new Date()} | Response: ${x.status} ${x.statusText} |${JSON.stringify(x.data)}`;
  //logRequestToFile(printable);
  WsLogger.createJsonEvent(socket, "DEBUG", `Raw Response: ${printable}`, x.data);

  return x;
});

function logRequestToFile(printable) {
  try {
    const logDir = path.join(process.cwd(), "logs");
    if (!fs.existsSync(logDir)) {
      fs.mkdirSync(logDir, { recursive: true });
    }
    const logFile = path.join(logDir, "pbs-http.log");
    fs.appendFileSync(logFile, `${printable}\n`);
  } catch (err) {
    console.error("Unexpected error in logRequestToFile:", err);
  }
}

const defaultHandler = async (socket, { txEnvelope, jobid }) => {
  socket.logEvents = [];
  socket.recordid = jobid;
  socket.txEnvelope = txEnvelope;
  try {
    WsLogger.createLogEvent(socket, "INFO", `Received Job export request for id ${jobid}`);

    const JobData = await QueryJobData(socket, jobid);
    socket.JobData = JobData;
    WsLogger.createLogEvent(socket, "INFO", `Querying the DMS for the Vehicle Record.`);
    //Query for the Vehicle record to get the associated customer.
    socket.DmsVeh = await QueryVehicleFromDms(socket);
    //Todo: Need to validate the lines and methods below.
    if (socket.DmsVeh?.CustomerRef) {
      //Get the associated customer from the Vehicle Record.
      socket.DMSVehCustomer = await QueryCustomerBycodeFromDms(socket, socket.DmsVeh.CustomerRef);
    }
    socket.DMSCustList = await QueryCustomersFromDms(socket);

    socket.emit("pbs-select-customer", [
      ...(socket.DMSVehCustomer ? [{ ...socket.DMSVehCustomer, vinOwner: true }] : []),
      ...socket.DMSCustList
    ]);
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error encountered in PbsJobExport. ${error}`);
  }
};

exports.default = defaultHandler;

exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selectedCustomerId) {
  try {
    socket.selectedCustomerId = selectedCustomerId;
    if (socket.JobData.bodyshop.pbs_configuration.disablecontactvehicle !== true) {
      WsLogger.createLogEvent(socket, "INFO", `User selected customer ${selectedCustomerId || "NEW"}`);

      //Upsert the contact information as per Wafaa's Email.
      WsLogger.createLogEvent(
        socket,
        "INFO",
        `Upserting contact information to DMS for ${
          socket.JobData.ownr_fn || ""
        } ${socket.JobData.ownr_ln || ""} ${socket.JobData.ownr_co_nm || ""}`
      );

      //If this is an AR customer, don't do anything. 

      const selectedCustomer = [...(socket.DMSVehCustomer ? [{ ...socket.DMSVehCustomer, vinOwner: true }] : []),
      ...socket.DMSCustList]?.find((cust) => cust.ContactId === selectedCustomerId);

      if (selectedCustomer?.IsARCustomer) {

        WsLogger.createLogEvent(socket, "INFO", `Skipping contact and vehicle update becuase it is marked as an AR contact in PBS.`);

      }
      else {

        const ownerRef = await UpsertContactData(socket, selectedCustomerId);
        socket.ownerRef = ownerRef;
        WsLogger.createLogEvent(socket, "INFO", `Upserting vehicle information to DMS for ${socket.JobData.v_vin}`);
        const vehicleRef = await UpsertVehicleData(socket, ownerRef.ReferenceId);
        socket.vehicleRef = vehicleRef;
      }

    } else {
      WsLogger.createLogEvent(
        socket,
        "INFO",
        `Contact and Vehicle updates disabled. Querying data and skipping to accounting data insert.`
      );
      //Must query for records to insert $0 RO.
      if (!socket.ownerRef) {
        const ownerRef = (await QueryCustomerBycodeFromDms(socket, selectedCustomerId))?.[0];
        socket.ownerRef = ownerRef;
      }
      const vehicleRef = await GetVehicleData(socket, socket.ownerRef?.ReferenceId || socket.selectedCustomerId);
      socket.vehicleRef = vehicleRef;
    }
    WsLogger.createLogEvent(socket, "INFO", `Inserting account posting data...`);
    const insertResponse = await InsertAccountPostingData(socket);

    if (insertResponse.WasSuccessful) {
      if (socket.JobData.bodyshop.pbs_configuration.ro_posting) {
        await CreateRepairOrderInPBS(socket, socket.ownerRef, socket.vehicleRef);
      }
      WsLogger.createLogEvent(socket, "INFO", `Marking job as exported.`);
      await MarkJobExported(socket, socket.JobData.id);
      socket.emit("export-success", socket.JobData.id);
    } else {
      WsLogger.createLogEvent(socket, "ERROR", `Export was not successful.`);
    }
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error encountered in PbsSelectedCustomer. ${error}`);
    await InsertFailedExportLog(socket, error);
  }
};

// Was Successful
async function CheckForErrors(socket, response) {
  if (response.WasSuccessful === undefined || response.WasSuccessful === true) {
    WsLogger.createLogEvent(socket, "INFO", `Successful response from DMS. ${response.Message || ""}`);
  } else {
    WsLogger.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`);
    WsLogger.createLogEvent(socket, "DEBUG", `Error received from DMS: ${JSON.stringify(response)}`);
  }
}

exports.CheckForErrors = CheckForErrors;

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

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

async function QueryVehicleFromDms(socket) {
  try {
    if (!socket.JobData.v_vin) return null;

    const { data: VehicleGetResponse } = await axios.post(
      PBS_ENDPOINTS.VehicleGet,
      {
        SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
        // VehicleId: "00000000000000000000000000000000",
        // Year: "String",
        // Make: "String",
        // Model: "String",
        // Trim: "String",
        // ModelNumber: "String",
        // StockNumber: "String",
        VIN: socket.JobData.v_vin
        // LicenseNumber: "String",
        // Lot: "String",
        // Status: "String",
        // StatusList: ["String"],
        // OwnerRef: "00000000000000000000000000000000",
        // ModifiedSince: "0001-01-01T00:00:00.0000000Z",
        // ModifiedUntil: "0001-01-01T00:00:00.0000000Z",
        // LastSaleSince: "0001-01-01T00:00:00.0000000Z",
        // VehicleIDList: ["00000000000000000000000000000000"],
        // IncludeInactive: false,
        // IncludeBuildVehicles: false,
        // ShortVIN: "String",
      },
      { auth: PBS_CREDENTIALS, socket }
    );

    CheckForErrors(socket, VehicleGetResponse);
    return VehicleGetResponse;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in QueryVehicleFromDms - ${error}`);
    throw new Error(error);
  }
}

async function QueryCustomersFromDms(socket) {
  try {
    const { data: CustomerGetResponse } = await axios.post(
      PBS_ENDPOINTS.ContactGet,
      {
        SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
        //ContactId: "00000000000000000000000000000000",
        // ContactCode: socket.JobData.owner.accountingid,
        FirstName: socket.JobData.ownr_fn,
        LastName: socket.JobData.ownr_co_nm ? socket.JobData.ownr_co_nm : socket.JobData.ownr_ln,
        PhoneNumber: socket.JobData.ownr_ph1,
        EmailAddress: socket.JobData.ownr_ea
        // ModifiedSince: "0001-01-01T00:00:00.0000000Z",
        // ModifiedUntil: "0001-01-01T00:00:00.0000000Z",
        // ContactIdList: ["00000000000000000000000000000000"],
        // IncludeInactive: false,
        // PayableAccount: "String",
        // ReceivableAccount: "String",
        // DriverLicense: "String",
        // ZipCode: "String",
      },
      { auth: PBS_CREDENTIALS, socket }
    );
    CheckForErrors(socket, CustomerGetResponse);
    return CustomerGetResponse?.Contacts;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
    throw new Error(error);
  }
}

async function QueryCustomerBycodeFromDms(socket, CustomerRef) {
  try {
    const { data: CustomerGetResponse } = await axios.post(
      PBS_ENDPOINTS.ContactGet,
      {
        SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
        ContactId: CustomerRef
        //ContactCode: socket.JobData.owner.accountingid,
        //FirstName: socket.JobData.ownr_co_nm
        // ? socket.JobData.ownr_co_nm
        // : socket.JobData.ownr_fn,
        //LastName: socket.JobData.ownr_ln,
        //PhoneNumber: socket.JobData.ownr_ph1,
        // EmailAddress: "String",
        // ModifiedSince: "0001-01-01T00:00:00.0000000Z",
        // ModifiedUntil: "0001-01-01T00:00:00.0000000Z",
        // ContactIdList: ["00000000000000000000000000000000"],
        // IncludeInactive: false,
        // PayableAccount: "String",
        // ReceivableAccount: "String",
        // DriverLicense: "String",
        // ZipCode: "String",
      },
      { auth: PBS_CREDENTIALS, socket }
    );
    CheckForErrors(socket, CustomerGetResponse);
    return CustomerGetResponse?.Contacts;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
    throw new Error(error);
  }
}

async function UpsertContactData(socket, selectedCustomerId) {
  try {
    const { data: ContactChangeResponse } = await axios.post(
      PBS_ENDPOINTS.ContactChange,
      {
        ContactInfo: {
          // Id: socket.JobData.owner.id,
          ...(selectedCustomerId ? { ContactId: selectedCustomerId } : {}),
          SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
          Code: socket.JobData.owner.accountingid,
          ...(socket.JobData.ownr_co_nm
            ? {
                //LastName: socket.JobData.ownr_ln,
                FirstName: socket.JobData.ownr_co_nm,
                IsBusiness: true
              }
            : {
                LastName: socket.JobData.ownr_ln,
                FirstName: socket.JobData.ownr_fn,
                IsBusiness: false
              }),

          //Salutation: "String",
          //MiddleName: "String",
          //ContactName: "String",
          IsInactive: false,

          //ApartmentNumber: "String",
          Address: socket.JobData.ownr_addr1,
          City: socket.JobData.ownr_city,
          //County: socket.JobData.ownr_addr1,
          State: socket.JobData.ownr_st,
          ZipCode: socket.JobData.ownr_zip,
          //BusinessPhone: "String",
          //BusinessPhoneExt: "String",
          HomePhone: socket.JobData.ownr_ph2,
          CellPhone: socket.JobData.ownr_ph1,
          //BusinessPhoneRawReverse: "String",
          //HomePhoneRawReverse: "String",
          //CellPhoneRawReverse: "String",
          //FaxNumber: "String",
          EmailAddress: socket.JobData.ownr_ea
          //Notes: "String",
          //CriticalMemo: "String",
          //BirthDate: "0001-01-01T00:00:00.0000000Z",
          //  Gender: "String",
          // DriverLicense: "String",
          //PreferredContactMethods: ["String"],
          // LastUpdate: "0001-01-01T00:00:00.0000000Z",
          // CustomFields: [{ Key: "String", Value: "String", Type: "String" }],
          // FleetType: "String",
          // CommunicationPreferences: {
          //   Email: "String",
          //   Phone: "String",
          //   TextMessage: "String",
          //   Letter: "String",
          //   Preferred: "String",
          // },
          // SalesRepRef: "00000000000000000000000000000000",
          // Language: "String",
          // PayableAccount: "String",
          // ReceivableAccount: "String",
          // IsStatic: false,
          // PrimaryImageRef: "00000000000000000000000000000000",
          // PayableAccounts: [{ SerialNumber: "String", Account: "String" }],
          // ReceivableAccounts: [{ SerialNumber: "String", Account: "String" }],
          // ManufacturerLoyaltyNumber: "String",
        },
        IsAsynchronous: false
        // UserRequest: "String",
        // UserRef: "00000000000000000000000000000000",
      },
      { auth: PBS_CREDENTIALS, socket }
    );
    CheckForErrors(socket, ContactChangeResponse);
    return ContactChangeResponse;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in UpsertContactData - ${error}`);
    throw new Error(error);
  }
}

async function UpsertVehicleData(socket, ownerRef) {
  try {
    const { data: VehicleChangeResponse } = await axios.post(
      PBS_ENDPOINTS.VehicleChange,
      {
        VehicleInfo: {
          //Id: "string/00000000-0000-0000-0000-000000000000",
          //VehicleId: "00000000000000000000000000000000",
          SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
          //StockNumber: "String",
          VIN: socket.JobData.v_vin,
          LicenseNumber: socket.JobData.plate_no,
          //FleetNumber: "String",
          //Status: "String",
          OwnerRef: ownerRef, // "00000000000000000000000000000000",
          // ModelNumber: socket.JobData.vehicle?.v_makecode,
          Make: socket.JobData.v_make_desc,
          Model: socket.JobData.v_model_desc,
          Trim: socket.JobData.vehicle?.v_trimcode,
          //VehicleType: "String",
          Year: socket.JobData.v_model_yr,
          Odometer: socket.JobData.kmout,
          ExteriorColor: {
            // Code: socket.JobData.v_color,
            Description: socket.JobData.v_color
          }
          // InteriorColor: { Code: "String", Description: "String" },
          //Engine: "String",
          // Cylinders: "String",
          // Transmission: "String",
          // DriveWheel: "String",
          // Fuel: "String",
          // Weight: 0,
          // InServiceDate: "0001-01-01T00:00:00.0000000Z",
          // LastServiceDate: "0001-01-01T00:00:00.0000000Z",
          // LastServiceMileage: 0,
          // Lot: "String",
          // LotDescription: "String",
          // Category: "String",
          // Options: [
          //   {
          //     Group: "String",
          //     Code: "String",
          //     Description: "String",
          //     AdditionalInfo: "String",
          //     Price: 0,
          //     Cost: 0,
          //     Residual: 0,
          //   },
          // ],
          // Refurbishments: [
          //   {
          //     ReferenceNumber: "String",
          //     Description: "String",
          //     Price: 0,
          //     Cost: 0,
          //     Date: "0001-01-01T00:00:00.0000000Z",
          //     ApplicationModel: "String",
          //   },
          // ],
          // Order: {
          //   InvoiceNumber: "String",
          //   Price: 0,
          //   Status: "String",
          //   Eta: "String",
          //   EstimatedCost: 0,
          //   OrderDate: "String",
          //   StatusDate: "String",
          //   IgnitionKeyCode: "String",
          //   DoorKeyCode: "String",
          //   Description: "String",
          //   LocationStatus: "String",
          //   LocationStatusDate: "0001-01-01T00:00:00.0000000Z",
          // },
          // MSR: 0,
          // BaseMSR: 0,
          // Retail: 0,
          // DateReceived: "0001-01-01T00:00:00.0000000Z",
          // InternetPrice: 0,
          // Lotpack: 0,
          // Holdback: 0,
          // InternetNotes: "String",
          // Notes: "String",
          // CriticalMemo: "String",
          // IsCertified: false,
          // LastSaleDate: "0001-01-01T00:00:00.0000000Z",
          // LastUpdate: "0001-01-01T00:00:00.0000000Z",
          // AppraisedValue: 0,
          // Warranties: [
          //   {
          //     Type: "String",
          //     CompanyName: "String",
          //     CoveragePlan: "String",
          //     Description: "String",
          //     Price: 0,
          //     Cost: 0,
          //     Term: "String",
          //     Deductible: 0,
          //     PolicyNumber: "String",
          //     StartDate: "String",
          //     StartMileage: 0,
          //     ExpirationDate: "String",
          //     ExpirationMileage: 0,
          //   },
          // ],
          // Freight: 0,
          // Air: 0,
          // Inventory: 0,
          // IsInactive: false,
          // CustomFields: [{ Key: "String", Value: "String", Type: "String" }],
          // FloorPlanCode: "String",
          // FloorPlanAmount: 0,
          // Insurance: {
          //   Company: "String",
          //   Policy: "String",
          //   ExpiryDate: "0001-01-01T00:00:00.0000000Z",
          //   AgentName: "String",
          //   AgentPhoneNumber: "String",
          // },
          // Body: "String",
          // ShortVIN: "String",
          // AdditionalDrivers: ["00000000000000000000000000000000"],
          // OrderDetails: { Distributor: "String" },
          // PrimaryImageRef: "00000000000000000000000000000000",
          // Hold: {
          //   VehicleRef: "00000000000000000000000000000000",
          //   HoldFrom: "0001-01-01T00:00:00.0000000Z",
          //   HoldUntil: "0001-01-01T00:00:00.0000000Z",
          //   UserRef: "00000000000000000000000000000000",
          //   ContactRef: "00000000000000000000000000000000",
          //   Comments: "String",
          // },
          // SeatingCapacity: "String",
          // DeliveryDate: "0001-01-01T00:00:00.0000000Z",
          // WarrantyExpiry: "0001-01-01T00:00:00.0000000Z",
          // IsConditionallySold: false,
          // SalesDivision: 0,
          // StyleRef: "String",
        },
        IsAsynchronous: false
        // UserRequest: "String",
        // UserRef: "00000000000000000000000000000000",
      },
      { auth: PBS_CREDENTIALS, socket }
    );
    CheckForErrors(socket, VehicleChangeResponse);
    return VehicleChangeResponse;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
    throw new Error(error);
  }
}

async function GetVehicleData(socket, ownerRef) {
  try {
    const {
      data: { Vehicles }
    } = await axios.post(
      PBS_ENDPOINTS.VehicleGet,
      {
        SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
        // "VehicleId": "00000000000000000000000000000000",
        // "Year": "String",
        // "YearFrom": "String",
        // "YearTo": "String",
        // "Make": "String",
        // "Model": "String",
        // "Trim": "String",
        // "ModelNumber": "String",
        // "StockNumber": "String",
        VIN: socket.JobData.v_vin
        // "LicenseNumber": "String",
        // "Lot": "String",
        // "Status": "String",
        // "StatusList": ["String"],
        // "OwnerRef": "00000000000000000000000000000000",
        // "ModifiedSince": "0001-01-01T00:00:00.0000000Z",
        // "ModifiedUntil": "0001-01-01T00:00:00.0000000Z",
        // "LastSaleSince": "0001-01-01T00:00:00.0000000Z",
        // "VehicleIDList": ["00000000000000000000000000000000"],
        // "IncludeInactive": false,
        // "IncludeBuildVehicles": false,
        // "IncludeBlankLot": false,
        // "ShortVIN": "String",
        // "ResultLimit": 0,
        // "LotAccessDivisions": [0],
        // "OdometerTo": 0,
        // "OdometerFrom": 0
      },
      { auth: PBS_CREDENTIALS, socket }
    );
    CheckForErrors(socket, Vehicles);
    if (Vehicles.length === 1) {
      return Vehicles[0];
    } else {
      WsLogger.createLogEvent(socket, "ERROR", `Error in Getting Vehicle Data - ${Vehicles.length} vehicle(s) found`);
    }
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
    throw new Error(error);
  }
}

async function InsertAccountPostingData(socket) {
  try {
    const allocations = await CalculateAllocations(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 = {
          Account: alloc.profitCenter.dms_acctnumber,
          ControlNumber: socket.JobData.ro_number,
          Amount: alloc.sale.multiply(-1).toFormat("0.00"),
          //Comment: "String",
          //AdditionalInfo: "String",
          InvoiceNumber: socket.JobData.ro_number,
          InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
        };
        wips.push(item);
      }

      //Add the cost Item.
      if (alloc.cost.getAmount() > 0 && !alloc.tax) {
        const item = {
          Account: alloc.costCenter.dms_acctnumber,
          ControlNumber: socket.JobData.ro_number,
          Amount: alloc.cost.toFormat("0.00"),
          //Comment: "String",
          //AdditionalInfo: "String",
          InvoiceNumber: socket.JobData.ro_number,
          InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
        };
        wips.push(item);

        const itemWip = {
          Account: alloc.costCenter.dms_wip_acctnumber,
          ControlNumber: socket.JobData.ro_number,
          Amount: alloc.cost.multiply(-1).toFormat("0.00"),
          //Comment: "String",
          //AdditionalInfo: "String",
          InvoiceNumber: socket.JobData.ro_number,
          InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
        };
        wips.push(itemWip);
        //Add to the WIP account.
      }

      if (alloc.tax) {
        if (alloc.sale.getAmount() > 0) {
          const item2 = {
            Account: alloc.profitCenter.dms_acctnumber,
            ControlNumber: socket.JobData.ro_number,
            Amount: alloc.sale.multiply(-1).toFormat("0.00"),
            //Comment: "String",
            //AdditionalInfo: "String",
            InvoiceNumber: socket.JobData.ro_number,
            InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
          };
          wips.push(item2);
        }
      }
    });
    socket.txEnvelope.payers.forEach((payer) => {
      const item = {
        Account: payer.dms_acctnumber,
        ControlNumber: payer.controlnumber,
        Amount: Dinero({ amount: Math.round(payer.amount * 100) }).toFormat("0.0"),
        //Comment: "String",
        //AdditionalInfo: "String",
        InvoiceNumber: socket.JobData.ro_number,
        InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
      };

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

    const { data: AccountPostingChange } = await axios.post(
      PBS_ENDPOINTS.AccountingPostingChange,
      {
        SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
        Posting: {
          Reference: socket.JobData.ro_number,
          JournalCode: socket.txEnvelope.journal,
          //Sample TransactionDate: "0001-01-01T00:00:00.0000000Z",
          TransactionDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString(),
          Description: socket.txEnvelope.story,
          //AdditionalInfo: "String",
          Source: InstanceManager({ imex: "ImEX Online", rome: "Rome Online" }),
          Lines: wips
        }
      },
      { auth: PBS_CREDENTIALS, socket }
    );

    CheckForErrors(socket, AccountPostingChange);
    return AccountPostingChange;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in InsertAccountPostingData - ${error}`);
    throw new Error(error);
  }
}

async function MarkJobExported(socket, jobid) {
  WsLogger.createLogEvent(socket, "INFO", `Marking job as exported for id ${jobid}`);
  const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});

  const result = await client
    .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
    .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 result = await client
      .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
      .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)}`);
  }
}

async function CreateRepairOrderInPBS(socket) {
  try {
    const { RepairOrders } = await RepairOrderGet(socket);
    if (RepairOrders.length === 0) {
      const InsertedRepairOrder = await RepairOrderChange(socket);
      socket.InsertedRepairOrder = InsertedRepairOrder;
      WsLogger.createLogEvent(socket, "INFO", `No repair orders found for vehicle. Inserting record.`);
    } else if (RepairOrders.length > 0) {
      //Find out if it's a matching RO.
      //This logic is used because the integration will simply add another line to an open RO if it exists.
      const matchingRo = RepairOrders.find((ro) =>
        ro.Memo?.toLowerCase()?.includes(socket.JobData.ro_number.toLowerCase())
      );
      if (!matchingRo) {
        WsLogger.createLogEvent(socket, "INFO", `ROs found for vehicle, but none match. Inserting record.`);
        const InsertedRepairOrder = await RepairOrderChange(socket);
        socket.InsertedRepairOrder = InsertedRepairOrder;
      } else {
        WsLogger.createLogEvent(
          socket,
          "WARN",
          `Repair order appears to already exist in PBS. ${matchingRo.RepairOrderNumber}`
        );
      }
    }
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in CreateRepairOrderInPBS - ${error} - ${JSON.stringify(error)}`);
  }
}

async function RepairOrderGet(socket) {
  try {
    const { data: RepairOrderGet } = await axios.post(
      PBS_ENDPOINTS.RepairOrderGet,
      {
        SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
        //"RepairOrderId": "374728766",
        //"RepairOrderNumber": "4" || socket.JobData.ro_number,
        //"RawRepairOrderNumber": socket.JobData.ro_number,
        // "Tag": "String",
        //"ContactRef": socket.contactRef,
        // "ContactRefList": ["00000000000000000000000000000000"],
        VehicleRef: socket.vehicleRef?.ReferenceId || socket.vehicleRef?.VehicleId
        // "VehicleRefList": ["00000000000000000000000000000000"],
        // "Status": "String",
        // "CashieredSince": "0001-01-01T00:00:00.0000000Z",
        // "CashieredUntil": "0001-01-01T00:00:00.0000000Z",
        // "OpenDateSince": "0001-01-01T00:00:00.0000000Z",
        // "OpenDateUntil": "0001-01-01T00:00:00.0000000Z",
        //"ModifiedSince": "2025-01-01T00:00:00.0000000Z",
        // "ModifiedUntil": "0001-01-01T00:00:00.0000000Z",
        // "Shop": "String"
      },
      { auth: PBS_CREDENTIALS, socket }
    );
    CheckForErrors(socket, RepairOrderGet);
    return RepairOrderGet;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`);
    throw new Error(error);
  }
}

async function RepairOrderChange(socket) {
  try {
    const { data: RepairOrderChangeResponse } = await axios.post(
      PBS_ENDPOINTS.RepairOrderChange,
      {
        //Additional details at https://partnerhub.pbsdealers.com/json/metadata?op=RepairOrderChange
        RepairOrderInfo: {
          //"Id": "string/00000000-0000-0000-0000-000000000000",
          //"RepairOrderId": "00000000000000000000000000000000",
          SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
          RepairOrderNumber: "00000000000000000000000000000000", //This helps force a new RO.
          RawRepairOrderNumber: "00000000000000000000000000000000",
          // "RepairOrderNumber": socket.JobData.ro_number, //These 2 values are ignored as confirmed by PBS.
          // "RawRepairOrderNumber": socket.JobData.ro_number,
          DateOpened: moment(),
          // "DateOpenedUTC": "0001-01-01T00:00:00.0000000Z",
          // "DateCashiered": "0001-01-01T00:00:00.0000000Z",
          // "DateCashieredUTC": "0001-01-01T00:00:00.0000000Z",
          DatePromised: socket.JobData.scheduled_completion,
          // "DatePromisedUTC": "0001-01-01T00:00:00.0000000Z",
          DateVehicleCompleted: socket.JobData.actual_completion,
          // "DateCustomerNotified": "0001-01-01T00:00:00.0000000Z",
           "CSR": "IMEX", //Hardcoded for now as the only shop that uses this RO posting is RC and they paid for a custom build.
           Shop: "RB",
          // "CSRRef": "00000000000000000000000000000000",
          // "BookingUser": "String",
          // "BookingUserRef": "00000000000000000000000000000000",
          ContactRef: socket.ownerRef?.ReferenceId || socket.ownerRef?.ContactId,
          VehicleRef: socket.vehicleRef?.ReferenceId || socket.vehicleRef?.VehicleId,
          MileageIn: socket.JobData.km_in,
          Tag: "BODYSHOP",
          //"Status": "CLOSED", //Values here do not impact the status. Confirmed by PBS support.
          Requests: [
            {
              // "RepairOrderRequestRef": "b1842ecad62c4279bbc2fef4f6bf6cde",
              // "RepairOrderRequestId": 1,
              // "CSR": "PBS",
              // "CSRRef": "1ce12ac692564e94bda955d529ee911a",
              // "Skill": "GEN",
              RequestCode: "MISC",
              RequestDescription: `VEHICLE REPAIRED AT BODYSHOP. PLEASE REFERENCE IMEX SHOP MANAGEMENT SYSTEM. ${socket.txEnvelope.story}`,
              Status: "Completed",
              // "TechRef": "00000000000000000000000000000000",
              AllowedHours: 0,
              EstimateLabour: 0,
              EstimateParts: 0,
              ComeBack: false,
              AddedOperation: true,
              PartLines: [],
              PartRequestLines: [],
              LabourLines: [],
              SubletLines: [],
              TimePunches: [],
              Summary: {
                Labour: 0,
                Parts: 0,
                OilGas: 0,
                SubletTow: 0,
                Misc: 0,
                Environment: 0,
                ShopSupplies: 0,
                Freight: 0,
                WarrantyDeductible: 0,
                Discount: 0,
                SubTotal: 0,
                Tax1: 0,
                Tax2: 0,
                InvoiceTotal: 0,
                CustomerDeductible: 0,
                GrandTotal: 0,
                LabourDiscount: 0,
                PartDiscount: 0,
                ServiceFeeTotal: 0,
                OEMDiscount: 0
              },
              LineType: "RequestLine"
            }
          ],

          Memo: socket.txEnvelope.story
        },
        IsAsynchronous: false
        // "UserRequest": "String",
        // "UserRef": "00000000000000000000000000000000"
      },

      { auth: PBS_CREDENTIALS, socket }
    );
    CheckForErrors(socket, RepairOrderChangeResponse);
    return RepairOrderChangeResponse;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`);
    throw new Error(error);
  }
}
