const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment-timezone");
var builder = require("xmlbuilder2");
const _ = require("lodash");
const logger = require("../utils/logger");
const fs = require("fs");
require("dotenv").config({
  path: path.resolve(
    process.cwd(),
    `.env.${process.env.NODE_ENV || "development"}`
  ),
});
let Client = require("ssh2-sftp-client");

const client = require("../graphql-client/graphql-client").client;
const { sendServerEmail } = require("../email/sendemail");
const AHDineroFormat = "0.00";
const AhDateFormat = "MMDDYYYY";

const repairOpCodes = ["OP4", "OP9", "OP10"];
const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"];

const ftpSetup = {
  host: process.env.AUTOHOUSE_HOST,
  port: process.env.AUTOHOUSE_PORT,
  username: process.env.AUTOHOUSE_USER,
  password: process.env.AUTOHOUSE_PASSWORD,
  debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data),
  algorithms: {
    serverHostKey: ["ssh-rsa", "ssh-dss"],
  },
};

exports.default = async (req, res) => {
  //Query for the List of Bodyshop Clients.
  logger.log("autohouse-start", "DEBUG", "api", null, null);
  const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);

  const specificShopIds = req.body.bodyshopIds; // ['uuid]
  const { start, end, skipUpload } = req.body; //YYYY-MM-DD
  const allxmlsToUpload = [];
  const allErrors = [];
  try {
    for (const bodyshop of specificShopIds
      ? bodyshops.filter((b) => specificShopIds.includes(b.id))
      : bodyshops) {
      logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, {
        shopname: bodyshop.shopname,
      });
      const erroredJobs = [];
      try {
        const { jobs, bodyshops_by_pk } = await client.request(
          queries.AUTOHOUSE_QUERY,
          {
            bodyshopid: bodyshop.id,
            start: start
              ? moment(start).startOf("day")
              : moment().subtract(5, "days").startOf("day"),
            ...(end && { end: moment(end).startOf("day") }),
          }
        );

        const autoHouseObject = {
          AutoHouseExport: {
            RepairOrder: jobs.map((j) =>
              CreateRepairOrderTag(
                { ...j, bodyshop: bodyshops_by_pk },
                function ({ job, error }) {
                  erroredJobs.push({ job: job, error: error.toString() });
                }
              )
            ),
          },
        };

        if (erroredJobs.length > 0) {
          logger.log("autohouse-failed-jobs", "ERROR", "api", bodyshop.id, {
            count: erroredJobs.length,
            jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)),
          });
        }

        var ret = builder
          .create(
            {
              // version: "1.0",
              // encoding: "UTF-8",
              //keepNullNodes: true,
            },
            autoHouseObject
          )
          .end({ allowEmptyTags: true });

        allxmlsToUpload.push({
          count: autoHouseObject.AutoHouseExport.RepairOrder.length,
          xml: ret,
          filename: `IM_${bodyshop.autohouseid}_${moment().format(
            "DDMMYYYY_HHMMss"
          )}.xml`,
        });

        logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, {
          shopname: bodyshop.shopname,
        });
      } catch (error) {
        //Error at the shop level.
        logger.log("autohouse-error-shop", "ERROR", "api", bodyshop.id, {
          ...error,
        });

        allErrors.push({
          bodyshopid: bodyshop.id,
          imexshopid: bodyshop.imexshopid,
          autuhouseid: bodyshop.autuhouseid,
          fatal: true,
          errors: [error.toString()],
        });
      } finally {
        allErrors.push({
          bodyshopid: bodyshop.id,
          imexshopid: bodyshop.imexshopid,
          autohouseid: bodyshop.autohouseid,
          errors: erroredJobs.map((ej) => ({
            ro_number: ej.job?.ro_number,
            jobid: ej.job?.id,
            error: ej.error,
          })),
        });
      }
    }

    if (skipUpload) {
      for (const xmlObj of allxmlsToUpload) {
        fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
      }

      res.json(allxmlsToUpload);
      sendServerEmail({
        subject: `Autohouse Report ${moment().format("MM-DD-YY")}`,
        text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
      Uploaded: ${JSON.stringify(
        allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
        null,
        2
      )}
      `,
      });
      return;
    }

    let sftp = new Client();
    sftp.on("error", (errors) =>
      logger.log("autohouse-sftp-error", "ERROR", "api", null, {
        ...errors,
      })
    );
    try {
      //Connect to the FTP and upload all.

      await sftp.connect(ftpSetup);

      for (const xmlObj of allxmlsToUpload) {
        logger.log("autohouse-sftp-upload", "DEBUG", "api", null, {
          filename: xmlObj.filename,
        });

        const uploadResult = await sftp.put(
          Buffer.from(xmlObj.xml),
          `/${xmlObj.filename}`
        );
        logger.log("autohouse-sftp-upload-result", "DEBUG", "api", null, {
          uploadResult,
        });
      }

      //***TODO  Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
    } catch (error) {
      logger.log("autohouse-sftp-error", "ERROR", "api", null, {
        ...error,
      });
    } finally {
      sftp.end();
    }
    sendServerEmail({
      subject: `Autohouse Report ${moment().format("MM-DD-YY")}`,
      text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
      Uploaded: ${JSON.stringify(
        allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
        null,
        2
      )}
      `,
    });
    res.sendStatus(200);
  } catch (error) {
    res.status(200).json(error);
  }
};

const CreateRepairOrderTag = (job, errorCallback) => {
  //Level 2

  if (!job.job_totals) {
    errorCallback({
      jobid: job.id,
      job: job,
      ro_number: job.ro_number,
      error: { toString: () => "No job totals for RO." },
    });
    return {};
  }

  const repairCosts = CreateCosts(job);

  try {
    const ret = {
      RepairOrderInformation: {
        ShopInternalName: job.bodyshop.autohouseid,
        ID: parseInt(job.ro_number.match(/\d/g).join(""), 10),
        RO: job.ro_number,
        Est: parseInt(job.ro_number.match(/\d/g).join(""), 10), //We no longer use estimate id.
        GUID: job.id,
        TransType: StatusMapping(job.status, job.bodyshop.md_ro_statuses),
        ShopName: job.bodyshop.shopname,
        ShopAddress: job.bodyshop.address1,
        ShopCity: job.bodyshop.city,
        ShopState: job.bodyshop.state,
        ShopZip: job.bodyshop.zip_post,
        ShopPhone: job.bodyshop.phone,
        EstimatorID: `${job.est_ct_ln ? job.est_ct_ln : ""}${
          job.est_ct_ln ? ", " : ""
        }${job.est_ct_fn ? job.est_ct_fn : ""}`,
        EstimatorName: `${job.est_ct_ln ? job.est_ct_ln : ""}${
          job.est_ct_ln ? ", " : ""
        }${job.est_ct_fn ? job.est_ct_fn : ""}`,
      },
      CustomerInformation: {
        FirstName: job.ownr_fn || "",
        LastName: job.ownr_ln || "",
        Street: job.ownr_addr1 || "",
        City: job.ownr_city || "",
        State: job.ownr_st || "",
        Zip: (job.ownr_zip && job.ownr_zip.substring(0, 3)) || "",
        Phone1: job.ownr_ph1 || "",
        Phone2: null,
        Phone2Extension: null,
        Phone3: null,
        Phone3Extension: null,
        FileComments: null,
        Source: null,
        Email: job.ownr_ea || "",
        RetWhsl: null,
        Cat: null,
        InsuredorClaimantFlag: null,
      },
      VehicleInformation: {
        Year: job.v_model_yr
          ? parseInt(job.v_model_yr.match(/\d/g))
            ? parseInt(job.v_model_yr.match(/\d/g).join(""), 10)
            : ""
          : "",
        Make: job.v_make_desc || "",
        Model: job.v_model_desc || "",
        VIN: job.v_vin || "",
        License: job.plate_no,
        MileageIn: job.kmin || 0,
        Vehiclecolor: job.v_color,
        VehicleProductionDate: null,
        VehiclePaintCode: null,
        VehicleTrimCode: null,
        VehicleBodyStyle: null,
        DriveableFlag: job.driveable ? "Y" : "N",
      },

      InsuranceInformation: {
        InsuranceCo: job.ins_co_nm || "",
        CompanyName: job.ins_co_nm || "",
        Address: job.ins_addr1 || "",
        City: job.ins_addr1 || "",
        State: job.ins_city || "",
        Zip: job.ins_zip || "",
        Phone: job.ins_ph1 || "",
        Fax: job.ins_fax || "",
        ClaimType: null,
        LossType: job.loss_type || "",
        Policy: job.policy_no || "",
        Claim: job.clm_no || "",
        InsuredLastName: null,
        InsuredFirstName: null,
        ClaimantLastName: null,
        ClaimantFirstName: null,
        Assignment: null,
        InsuranceAgentLastName: null,
        InsuranceAgentFirstName: null,
        InsAgentPhone: null,
        InsideAdjuster: null,
        OutsideAdjuster: null,
      },
      Dates: {
        DateofLoss:
          (job.loss_date && moment(job.loss_date).format(AhDateFormat)) || "",
        InitialCustomerContactDate: null,
        FirstFollowUpDate: null,
        ReferralDate: null,
        EstimateAppointmentDate: null,
        SecondFollowUpDate: null,
        AssignedDate:
          (job.asgn_date && moment(job.asgn_date).format(AhDateFormat)) || "",
        EstComplete: null,
        CustomerAuthorizationDate: null,
        InsuranceAuthorizationDate: null,
        DateOpened:
          (job.date_open &&
            moment(job.date_open)
              .tz(job.bodyshop.timezone)
              .format(AhDateFormat)) ||
          (job.created_at &&
            moment(job.created_at)
              .tz(job.bodyshop.timezone)
              .format(AhDateFormat)) ||
          "",
        ScheduledArrivalDate:
          (job.scheduled_in &&
            moment(job.scheduled_in)
              .tz(job.bodyshop.timezone)
              .format(AhDateFormat)) ||
          "",
        CarinShop:
          (job.actual_in &&
            moment(job.actual_in)
              .tz(job.bodyshop.timezone)
              .format(AhDateFormat)) ||
          "",
        InsInspDate: null,
        StartDate: job.date_repairstarted
          ? (job.date_repairstarted &&
              moment(job.date_repairstarted)
                .tz(job.bodyshop.timezone)
                .format(AhDateFormat)) ||
            ""
          : (job.date_repairstarted &&
              moment(job.actual_in)
                .tz(job.bodyshop.timezone)
                .format(AhDateFormat)) ||
            "",
        PartsOrder: null,
        TeardownHold: null,
        SupplementSubmittedDate: null,
        SupplementApprovedDate: null,
        AssntoBody: null,
        AssntoMech: null,
        AssntoPaint: null,
        AssntoDetail: null,
        // PromiseDate:
        //   (job.scheduled_completion &&
        //     moment(job.scheduled_completion).format(AhDateFormat)) ||
        //   "",
        //InsuranceTargetOut: null,
        CarComplete:
          (job.actual_completion &&
            moment(job.actual_completion)
              .tz(job.bodyshop.timezone)
              .format(AhDateFormat)) ||
          "",
        DeliveryAppointmentDate:
          // (job.scheduled_delivery &&
          //   moment(job.scheduled_delivery)
          //     .tz(job.bodyshop.timezone)
          //     .format(AhDateFormat)) ||
          (job.scheduled_completion &&
            moment(job.scheduled_completion)
              .tz(job.bodyshop.timezone)
              .format(AhDateFormat)) ||
          "",
        DateClosed:
          (job.date_invoiced &&
            moment(job.date_invoiced)
              .tz(job.bodyshop.timezone)
              .format(AhDateFormat)) ||
          "",
        CustomerPaidInFullDate: null,
        InsurancePaidInFullDate: null,
        CustPickup:
          (job.actual_delivery &&
            moment(job.actual_delivery)
              .tz(job.bodyshop.timezone)
              .format(AhDateFormat)) ||
          "",
        AccountPostedDate:
          job.date_exported &&
          moment(job.date_exported)
            .tz(job.bodyshop.timezone)
            .format(AhDateFormat),
        CSIProcessedDate: null,
        ThankYouLetterSent: null,
        AdditionalFollowUpDate: null,
      },
      Rates: {
        BodyRate: job.rate_lab || 0,
        RefinishRate: job.rate_lar || 0,
        MechanicalRate: job.rate_lam || 0,
        StructuralRate: job.rate_las || 0,
        ElectricalRate: job.rate_lae || 0,
        FrameRate: job.rate_laf || 0,
        GlassRate: job.rate_lag || 0,
        DetailRate: job.rate_lad || 0,
        LaborMiscRate: 0,
        PMRate: job.rate_mapa || 0,
        BMRate: job.rate_mash || 0,
        TaxRate:
          (job.parts_tax_rates &&
            job.parts_tax_rates.PAN &&
            job.parts_tax_rates.PAN.prt_tax_rt) ||
          0,
        StorageRateperDay: 0,
        DaysStored: 0,
      },
      // EstimateTotals: {
      //   BodyHours: null,
      //   RefinishHours: null,
      //   MechanicalHours: null,
      //   StructuralHours: null,
      //   PartsTotal: null,
      //   PartsOEM: null,
      //   PartsAM: null,
      //   PartsReconditioned: null,
      //   PartsRecycled: null,
      //   PartsOther: null,
      //   SubletTotal: null,
      //   BodyLaborTotal: null,
      //   RefinishLaborTotal: null,
      //   MechanicalLaborTotal: null,
      //   StructuralLaborTotal: null,
      //   MiscellaneousChargeTotal: null,
      //   PMTotal: null,
      //   BMTotal: null,
      //   MiscTotal: null,
      //   TowingTotal: null,
      //   StorageTotal: null,
      //   DetailTotal: null,
      //   SalesTaxTotal: null,
      //   GrossTotal: null,
      //   DeductibleTotal: null,
      //   DepreciationTotal: null,
      //   Discount: null,
      //   CustomerPay: null,
      //   InsurancePay: null,
      //   Deposit: null,
      //   AmountDue: null,
      // },
      // SupplementTotals: {
      //   BodyHours: null,
      //   RefinishHours: null,
      //   MechanicalHours: null,
      //   StructuralHours: null,
      //   PartsTotal: null,
      //   PartsOEM: null,
      //   PartsAM: null,
      //   PartsReconditioned: null,
      //   PartsRecycled: null,
      //   PartsOther: null,
      //   SubletTotal: null,
      //   BodyLaborTotal: null,
      //   RefinishLaborTotal: null,
      //   MechanicalLaborTotal: null,
      //   StructuralLaborTotal: null,
      //   MiscellaneousChargeTotal: null,
      //   PMTotal: null,
      //   BMTotal: null,
      //   MiscTotal: null,
      //   TowingTotal: null,
      //   StorageTotal: null,
      //   DetailTotal: null,
      //   SalesTaxTotal: null,
      //   GrossTotal: null,
      //   DeductibleTotal: null,
      //   DepreciationTotal: null,
      //   Discount: null,
      //   CustomerPay: null,
      //   InsurancePay: null,
      //   Deposit: null,
      //   AmountDue: null,
      // },
      RevisedTotals: {
        BodyHours: job.job_totals.rates.lab.hours.toFixed(2),
        BodyRepairHours: job.joblines
          .filter((line) => repairOpCodes.includes(line.lbr_op))
          .reduce((acc, val) => acc + val.mod_lb_hrs, 0)
          .toFixed(2),
        BodyReplaceHours: job.joblines
          .filter((line) => replaceOpCodes.includes(line.lbr_op))
          .reduce((acc, val) => acc + val.mod_lb_hrs, 0)
          .toFixed(2),
        RefinishHours: job.job_totals.rates.lar.hours.toFixed(2),
        MechanicalHours: job.job_totals.rates.lam.hours.toFixed(2),
        StructuralHours: job.job_totals.rates.las.hours.toFixed(2),

        ElectricalHours: job.job_totals.rates.lae.hours.toFixed(2),
        FrameHours: job.job_totals.rates.laf.hours.toFixed(2),
        GlassHours: job.job_totals.rates.lag.hours.toFixed(2),
        DetailHours: job.job_totals.rates.lad.hours.toFixed(2),
        LaborMiscHours: (
          job.job_totals.rates.la1.hours +
          job.job_totals.rates.la2.hours +
          job.job_totals.rates.la3.hours +
          job.job_totals.rates.la4.hours +
          job.job_totals.rates.lau.hours
        ).toFixed(2),

        PartsTotal: Dinero(job.job_totals.parts.parts.total).toFormat(
          AHDineroFormat
        ),
        PartsTotalCost: repairCosts.PartsTotalCost.toFormat(AHDineroFormat),
        PartsOEM: Dinero(
          job.job_totals.parts.parts.list.PAN &&
            job.job_totals.parts.parts.list.PAN.total
        )
          .add(
            Dinero(
              job.job_totals.parts.parts.list.PAP &&
                job.job_totals.parts.parts.list.PAP.total
            )
          )
          .toFormat(AHDineroFormat),
        PartsOEMCost: repairCosts.PartsOemCost.toFormat(AHDineroFormat),
        PartsAM: Dinero(
          job.job_totals.parts.parts.list.PAA &&
            job.job_totals.parts.parts.list.PAA.total
        ).toFormat(AHDineroFormat),
        PartsAMCost: repairCosts.PartsAMCost.toFormat(AHDineroFormat),
        PartsReconditioned:
          repairCosts.PartsReconditionedCost.toFormat(AHDineroFormat),
        PartsReconditionedCost:
          repairCosts.PartsReconditionedCost.toFormat(AHDineroFormat),
        PartsRecycled: Dinero(
          job.job_totals.parts.parts.list.PAL &&
            job.job_totals.parts.parts.list.PAL.total
        ).toFormat(AHDineroFormat),
        PartsRecycledCost:
          repairCosts.PartsRecycledCost.toFormat(AHDineroFormat),
        PartsOther: Dinero(
          job.job_totals.parts.parts.list.PAO &&
            job.job_totals.parts.parts.list.PAO.total
        ).toFormat(AHDineroFormat),
        PartsOtherCost: repairCosts.PartsOtherCost.toFormat(AHDineroFormat),
        SubletTotal: Dinero(job.job_totals.parts.sublets.total).toFormat(
          AHDineroFormat
        ),
        SubletTotalCost: repairCosts.SubletTotalCost.toFormat(AHDineroFormat),
        BodyLaborTotal: Dinero(job.job_totals.rates.lab.total).toFormat(
          AHDineroFormat
        ),
        BodyLaborTotalCost:
          repairCosts.BodyLaborTotalCost.toFormat(AHDineroFormat),
        RefinishLaborTotal: Dinero(job.job_totals.rates.lar.total).toFormat(
          AHDineroFormat
        ),
        RefinishLaborTotalCost:
          repairCosts.RefinishLaborTotalCost.toFormat(AHDineroFormat),
        MechanicalLaborTotal: Dinero(job.job_totals.rates.lam.total).toFormat(
          AHDineroFormat
        ),
        MechanicalLaborTotalCost:
          repairCosts.MechanicalLaborTotalCost.toFormat(AHDineroFormat),
        StructuralLaborTotal: Dinero(job.job_totals.rates.las.total).toFormat(
          AHDineroFormat
        ),
        StructuralLaborTotalCost:
          repairCosts.StructuralLaborTotalCost.toFormat(AHDineroFormat),

        ElectricalLaborTotal: Dinero(job.job_totals.rates.lae.total).toFormat(
          AHDineroFormat
        ),
        ElectricalLaborTotalCost:
          repairCosts.ElectricalLaborTotalCost.toFormat(AHDineroFormat),
        FrameLaborTotal: Dinero(job.job_totals.rates.laf.total).toFormat(
          AHDineroFormat
        ),
        FrameLaborTotalCost:
          repairCosts.FrameLaborTotalCost.toFormat(AHDineroFormat),
        GlassLaborTotal: Dinero(job.job_totals.rates.lag.total).toFormat(
          AHDineroFormat
        ),
        GlassLaborTotalCost:
          repairCosts.GlassLaborTotalCost.toFormat(AHDineroFormat),
        DetailLaborTotal: Dinero(job.job_totals.rates.lad.total).toFormat(
          AHDineroFormat
        ),
        DetailLaborTotalCost:
          repairCosts.DetailLaborTotalCost.toFormat(AHDineroFormat),
        LaborMiscTotal: Dinero(job.job_totals.rates.la1.total)
          .add(Dinero(job.job_totals.rates.la2.total))
          .add(Dinero(job.job_totals.rates.la3.total))
          .add(Dinero(job.job_totals.rates.la4.total))
          .add(Dinero(job.job_totals.rates.lau.total))
          .toFormat(AHDineroFormat),
        LaborMiscTotalCost:
          repairCosts.LaborMiscTotalCost.toFormat(AHDineroFormat),
        MiscellaneousChargeTotal: 0,
        MiscellaneousChargeTotalCost: 0,
        PMTotal: Dinero(job.job_totals.rates.mapa.total).toFormat(
          AHDineroFormat
        ),
        PMTotalCost: repairCosts.PMTotalCost.toFormat(AHDineroFormat),
        BMTotal: Dinero(job.job_totals.rates.mash.total).toFormat(
          AHDineroFormat
        ),
        BMTotalCost: repairCosts.BMTotalCost.toFormat(AHDineroFormat),
        MiscTotal: Dinero(job.job_totals.additional.additionalCosts).toFormat(
          AHDineroFormat
        ),
        MiscTotalCost: 0,
        TowingTotal: Dinero(job.job_totals.additional.towing).toFormat(
          AHDineroFormat
        ),
        TowingTotalCost: repairCosts.TowingTotalCost.toFormat(AHDineroFormat),
        StorageTotal: Dinero(job.job_totals.additional.storage).toFormat(
          AHDineroFormat
        ),
        StorageTotalCost: repairCosts.StorageTotalCost.toFormat(AHDineroFormat),
        DetailTotal: 0,
        DetailTotalCost: 0,
        SalesTaxTotal: Dinero(job.job_totals.totals.local_tax)
          .add(Dinero(job.job_totals.totals.state_tax))
          .add(Dinero(job.job_totals.totals.federal_tax))
          .add(Dinero(job.job_totals.additional.pvrt))
          .toFormat(AHDineroFormat),
        SalesTaxTotalCost: 0,
        GrossTotal: Dinero(job.job_totals.totals.total_repairs).toFormat(
          AHDineroFormat
        ),
        DeductibleTotal: Dinero({
          amount: Math.round((job.ded_amt || 0) * 100),
        }).toFormat(AHDineroFormat),
        DepreciationTotal: Dinero(
          job.job_totals.totals.custPayable.dep_taxes
        ).toFormat(AHDineroFormat),
        Discount: Dinero(job.job_totals.additional.adjustments).toFormat(
          AHDineroFormat
        ),
        CustomerPay: Dinero(job.job_totals.totals.custPayable.total).toFormat(
          AHDineroFormat
        ),
        InsurancePay: Dinero(job.job_totals.totals.total_repairs)
          .subtract(Dinero(job.job_totals.totals.custPayable.total))
          .toFormat(AHDineroFormat),
        Deposit: 0,
        AmountDue: 0,
      },
      Misc: {
        ProductionStatus: null,
        StatusDescription: null,
        Hub50Comment: null,
        DateofChange: null,
        BodyTechName: null,
        TotalLossYN: job.tlos_ind ? "Y" : "N",
        InsScreenCommentsLine1: null,
        InsScreenCommentsLine2: null,
        AssignmentCaller: null,
        AssignmentDivision: null,
        LocationofPrimaryImpact:
          (job.area_of_damage && job.area_of_damage.impact1) || 0,
        LocationofSecondaryImpact:
          (job.area_of_damage && job.area_of_damage.impact2) || 0,
        PaintTechID: null,
        PaintTechName: null,
        ImportType: null,
        ImportFile: null,
        GSTTax: Dinero(job.job_totals.totals.federal_tax).toFormat(
          AHDineroFormat
        ),
        RepairDelayStatusCode: null,
        RepairDelaycomment: null,
        AgentMktgID: null,
        AgentCity: null,
        Picture1: null,
        Picture2: null,
        ExtNoteDate: null,
        RentalOrdDate: null,
        RentalPUDate: null,
        RentalDueDate: null,
        RentalActRetDate: null,
        RentalCompanyID: null,
        // CSIID: null,
        InsGroupCode: null,
      },

      DetailLines: {
        DetailLine:
          job.joblines.length > 0
            ? job.joblines.map((jl) =>
                GenerateDetailLines(job, jl, job.bodyshop.md_order_statuses)
              )
            : [generateNullDetailLine()],
      },
    };
    return ret;
  } catch (error) {
    logger.log("autohouse-job-calculate-error", "ERROR", "api", null, {
      error,
    });

    errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
  }
};

const CreateCosts = (job) => {
  //Create a mapping based on AH Requirements

  //For DMS, the keys in the object below are the CIECA part types.
  const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => {
    //At the bill level.
    bill_val.billlines.map((line_val) => {
      //At the bill line level.

      if (!bill_acc[line_val.cost_center])
        bill_acc[line_val.cost_center] = Dinero();

      bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add(
        Dinero({
          amount: Math.round((line_val.actual_cost || 0) * 100),
        })
          .multiply(line_val.quantity)
          .multiply(bill_val.is_credit_memo ? -1 : 1)
      );

      return null;
    });
    return bill_acc;
  }, {});

  //If the hourly rates for job costing are set, add them in.
  if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa) {
    if (
      !billTotalsByCostCenters[
        job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
      ]
    )
      billTotalsByCostCenters[
        job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
      ] = Dinero();
    billTotalsByCostCenters[
      job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
    ] = billTotalsByCostCenters[
      job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
    ].add(
      Dinero({
        amount:
          (job.bodyshop.jc_hourly_rates &&
            job.bodyshop.jc_hourly_rates.mapa * 100) ||
          0,
      }).multiply(job.job_totals.rates.mapa.hours)
    );
  }
  if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) {
    if (
      !billTotalsByCostCenters[
        job.bodyshop.md_responsibility_centers.defaults.costs.MASH
      ]
    )
      billTotalsByCostCenters[
        job.bodyshop.md_responsibility_centers.defaults.costs.MASH
      ] = Dinero();
    billTotalsByCostCenters[
      job.bodyshop.md_responsibility_centers.defaults.costs.MASH
    ] = billTotalsByCostCenters[
      job.bodyshop.md_responsibility_centers.defaults.costs.MASH
    ].add(
      Dinero({
        amount:
          (job.bodyshop.jc_hourly_rates &&
            job.bodyshop.jc_hourly_rates.mash * 100) ||
          0,
      }).multiply(job.job_totals.rates.mash.hours)
    );
  }
  //Uses CIECA Labor types.
  const ticketTotalsByCostCenter = job.timetickets.reduce(
    (ticket_acc, ticket_val) => {
      //At the invoice level.
      if (!ticket_acc[ticket_val.cost_center])
        ticket_acc[ticket_val.cost_center] = Dinero();

      ticket_acc[ticket_val.cost_center] = ticket_acc[
        ticket_val.cost_center
      ].add(
        Dinero({
          amount: Math.round((ticket_val.rate || 0) * 100),
        }).multiply(ticket_val.actualhrs || ticket_val.productivehrs || 0)
      );

      return ticket_acc;
    },
    {}
  );
  //CIECA STANDARD MAPPING OBJECT.

  const ciecaObj = {
    ATS: "ATS",
    LA1: "LA1",
    LA2: "LA2",
    LA3: "LA3",
    LA4: "LA4",
    LAA: "LAA",
    LAB: "LAB",
    LAD: "LAD",
    LAE: "LAE",
    LAF: "LAF",
    LAG: "LAG",
    LAM: "LAM",
    LAR: "LAR",
    LAS: "LAS",
    LAU: "LAU",
    PAA: "PAA",
    PAC: "PAC",
    PAG: "PAG",
    PAL: "PAL",
    PAM: "PAM",
    PAN: "PAN",
    PAO: "PAO",
    PAP: "PAP",
    PAR: "PAR",
    PAS: "PAS",
    TOW: "TOW",
    MAPA: "MAPA",
    MASH: "MASH",
    PASL: "PASL",
  };
  const defaultCosts =
    job.bodyshop.cdk_dealerid || job.bodyshop.pbs_serialnumber
      ? ciecaObj
      : job.bodyshop.md_responsibility_centers.defaults.costs;

  return {
    PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => {
      if (
        key !== defaultCosts.PAS &&
        key !== defaultCosts.PASL &&
        key !== defaultCosts.MAPA &&
        key !== defaultCosts.MASH &&
        key !== defaultCosts.TOW
      )
        return acc.add(billTotalsByCostCenters[key]);
      return acc;
    }, Dinero()),
    PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add(
      billTotalsByCostCenters[defaultCosts.PAP] || Dinero()
    ),
    PartsAMCost: billTotalsByCostCenters[defaultCosts.PAA] || Dinero(),
    PartsReconditionedCost:
      billTotalsByCostCenters[defaultCosts.PAM] || Dinero(),
    PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAL] || Dinero(),
    PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),
    SubletTotalCost:
      billTotalsByCostCenters[defaultCosts.PAS] ||
      Dinero(billTotalsByCostCenters[defaultCosts.PASL] || Dinero()),
    BodyLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(),
    RefinishLaborTotalCost:
      ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(),
    MechanicalLaborTotalCost:
      ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(),
    StructuralLaborTotalCost:
      ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(),
    ElectricalLaborTotalCost:
      ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(),
    FrameLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(),
    GlassLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(),
    DetailLaborTotalCost:
      ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(),
    LaborMiscTotalCost: (ticketTotalsByCostCenter[defaultCosts.LA1] || Dinero())
      .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero())
      .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero())
      .add(ticketTotalsByCostCenter[defaultCosts.LA3] || Dinero())
      .add(ticketTotalsByCostCenter[defaultCosts.LA4] || Dinero())
      .add(ticketTotalsByCostCenter[defaultCosts.LAU] || Dinero()),
    PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(),
    BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(),
    MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),
    TowingTotalCost: billTotalsByCostCenters[defaultCosts.TOW] || Dinero(),
    StorageTotalCost: Dinero(),
    DetailTotal: Dinero(),
    DetailTotalCost: Dinero(),
    SalesTaxTotalCost: Dinero(),
  };
};

const StatusMapping = (status, md_ro_statuses) => {
  //Possible return statuses EST, SCH, ARR, IPR, RDY, DEL, CLO, CAN, UNDEFINED.
  const {
    default_imported,
    default_open,
    default_scheduled,
    default_arrived,
    default_completed,
    default_delivered,
    default_invoiced,
    default_exported,
    default_void,
  } = md_ro_statuses;

  if (status === default_open || status === default_imported) return "EST";
  else if (status === default_scheduled) return "SCH";
  else if (status === default_arrived) return "ARR";
  else if (status === default_completed) return "RDY";
  else if (status === default_delivered) return "DEL";
  else if (status === default_invoiced || status === default_exported)
    return "CLO";
  else if (status === default_void) return "VOID";
  else if (md_ro_statuses.production_statuses.includes(status)) return "IPR";
  else return "UNDEFINED";
};

const GenerateDetailLines = (job, line, statuses) => {
  const ret = {
    BackOrdered: line.status === statuses.default_bo ? "1" : "0",
    Cost:
      (line.billlines[0] &&
        (line.billlines[0].actual_cost * line.billlines[0].quantity).toFixed(
          2
        )) ||
      0,
    //Critical: null,
    Description: line.line_desc
      ? line.line_desc.replace(/[^\x00-\x7F]/g, "")
      : "",
    DiscountMarkup: line.prt_dsmk_m || 0,
    InvoiceNumber: line.billlines[0] && line.billlines[0].bill.invoice_number,
    IOUPart: 0,
    LineNumber: line.line_no || 0,
    MarkUp: null,
    OrderedOn:
      (line.parts_order_lines[0] &&
        moment(line.parts_order_lines[0].parts_order.order_date).format(
          AhDateFormat
        )) ||
      "",
    OriginalCost: null,
    OriginalInvoiceNumber: null,
    PriceEach: line.act_price || 0,
    PartNumber: line.oem_partno
      ? line.oem_partno.replace(/[^\x00-\x7F]/g, "")
      : "",
    ProfitPercent: null,
    PurchaseOrderNumber: null,
    Qty: line.part_qty || 0,
    Status: line.status || "",
    SupplementNumber: line.line_ind ? line.line_ind.replace(/[^\d.-]/g, "") : 0,
    Type: line.part_type || "",
    Vendor: (line.billlines[0] && line.billlines[0].bill.vendor.name) || "",
    VendorPaid: null,
    VendorPrice:
      (line.billlines[0] && line.billlines[0].actual_price.toFixed(2)) || 0,
    Deleted: null,
    ExpectedOn: null,
    ReceivedOn:
      line.billlines[0] &&
      moment(line.billlines[0].bill.date).format(AhDateFormat),
    OrderedBy: null,
    ShipVia: null,
    VendorContact: null,
    EstimateAmount: (line.act_price * line.part_qty).toFixed(2) || 0, //Rebecca
  };
  return ret;
};

const generateNullDetailLine = () => {
  return {
    BackOrdered: "0",
    Cost: 0,
    Critical: null,
    Description: "No Lines on Estimate",
    DiscountMarkup: 0,
    InvoiceNumber: null,
    IOUPart: 0,
    LineNumber: 0,
    MarkUp: null,
    OrderedOn: "",
    OriginalCost: null,
    OriginalInvoiceNumber: null,
    PriceEach: 0,
    PartNumber: 0,
    ProfitPercent: null,
    PurchaseOrderNumber: null,
    Qty: 0,
    Status: "",
    SupplementNumber: 0,
    Type: "",
    Vendor: "",
    VendorPaid: null,
    VendorPrice: 0,
    Deleted: 0,
    ExpectedOn: "",
    ReceivedOn: "",
    OrderedBy: "",
    ShipVia: "",
    VendorContact: "",
    EstimateAmount: 0,
  };
};
