const _ = require("lodash");
const Dinero = require("dinero.js");
const queries = require("../graphql-client/queries");
const logger = require("../utils/logger");
const { ParseCalopCode } = require("../job/job-totals-USA");
const InstanceManager = require("../utils/instanceMgr").default;
const { DiscountNotAlreadyCounted } = InstanceManager({
  imex: require("../job/job-totals"),
  rome: require("../job/job-totals-USA")
});

// Dinero.defaultCurrency = "USD";
// Dinero.globalLocale = "en-CA";
Dinero.globalRoundingMode = "HALF_EVEN";

const isImEX = InstanceManager({ imex: true, rome: false });
const isRome = InstanceManager({ imex: false, rome: true });

async function JobCosting(req, res) {
  const { jobid } = req.body;

  const BearerToken = req.BearerToken;
  const client = req.userGraphQLClient;

  //Uncomment for further testing
  logger.log("job-costing-start", "DEBUG", req.user.email, jobid, null);

  try {
    const resp = await client.setHeaders({ Authorization: BearerToken }).request(queries.QUERY_JOB_COSTING_DETAILS, {
      id: jobid
    });

    const ret = GenerateCostingData(resp.jobs_by_pk);

    res.status(200).json(ret);
  } catch (error) {
    logger.log("job-costing-error", "ERROR", req.user.email, jobid, {
      message: error.message,
      stack: error.stack
    });

    res.status(400).send(JSON.stringify(error));
  }
}

async function JobCostingMulti(req, res) {
  const { jobids } = req.body;

  const logger = req.logger;
  const BearerToken = req.BearerToken;
  const client = req.userGraphQLClient;

  //Uncomment for further testing
  logger.log("job-costing-multi-start", "DEBUG", req?.user?.email, null, {
    jobids
  });

  try {
    const resp = await client
      .setHeaders({ Authorization: BearerToken })
      .request(queries.QUERY_JOB_COSTING_DETAILS_MULTI, {
        ids: jobids
      });

    const multiSummary = {
      costCenterData: [],
      summaryData: {
        totalLaborSales: Dinero({ amount: 0 }),
        totalPartsSales: Dinero({ amount: 0 }),
        totalAdditionalSales: Dinero({ amount: 0 }),
        totalSubletSales: Dinero({ amount: 0 }),
        totalSales: Dinero({ amount: 0 }),
        totalLaborCost: Dinero({ amount: 0 }),
        totalPartsCost: Dinero({ amount: 0 }),
        totalAdditionalCost: Dinero({ amount: 0 }),
        totalSubletCost: Dinero({ amount: 0 }),
        totalCost: Dinero({ amount: 0 }),
        gpdollars: Dinero({ amount: 0 }),
        gppercent: null,
        gppercentFormatted: null,
        totalLaborGp: Dinero({ amount: 0 }),
        totalPartsGp: Dinero({ amount: 0 }),
        totalAdditionalGp: Dinero({ amount: 0 }),
        totalSubletGp: Dinero({ amount: 0 }),
        totalLaborGppercent: null,
        totalLaborGppercentFormatted: null,
        totalPartsGppercent: null,
        totalPartsGppercentFormatted: null,
        totalAdditionalGppercent: null,
        totalAdditionalGppercentFormatted: null,
        totalSubletGppercent: null,
        totalSubletGppercentFormatted: null
      }
    };

    const ret = {};
    resp.jobs.map((job) => {
      const costingData = GenerateCostingData(job);
      ret[job.id] = costingData;

      //Merge on a cost center basis.

      costingData.costCenterData.forEach((c) => {
        //Find the Cost Center if it exists.

        const CostCenterIndex = multiSummary.costCenterData.findIndex((x) => x.cost_center === c.cost_center);

        if (CostCenterIndex >= 0) {
          //Add it in place
          multiSummary.costCenterData[CostCenterIndex] = {
            ...multiSummary.costCenterData[CostCenterIndex],
            sale_labor_dinero: multiSummary.costCenterData[CostCenterIndex].sale_labor_dinero.add(c.sale_labor_dinero),
            sale_parts_dinero: multiSummary.costCenterData[CostCenterIndex].sale_parts_dinero.add(c.sale_parts_dinero),
            sale_additional_dinero: multiSummary.costCenterData[CostCenterIndex].sale_additional_dinero.add(
              c.sale_additional_dinero
            ),
            sale_sublet_dinero: multiSummary.costCenterData[CostCenterIndex].sale_sublet_dinero.add(
              c.sale_sublet_dinero
            ),
            cost_labor_dinero: multiSummary.costCenterData[CostCenterIndex].cost_labor_dinero.add(c.cost_labor_dinero),
            cost_parts_dinero: multiSummary.costCenterData[CostCenterIndex].cost_parts_dinero.add(c.cost_parts_dinero),
            cost_additional_dinero: multiSummary.costCenterData[CostCenterIndex].cost_additional_dinero.add(
              c.cost_additional_dinero
            ),
            cost_sublet_dinero: multiSummary.costCenterData[CostCenterIndex].cost_sublet_dinero.add(
              c.cost_sublet_dinero
            ),
            gpdollars_dinero: multiSummary.costCenterData[CostCenterIndex].gpdollars_dinero.add(c.gpdollars_dinero),
            costs_dinero: multiSummary.costCenterData[CostCenterIndex].costs_dinero.add(c.costs_dinero),
            sales_dinero: multiSummary.costCenterData[CostCenterIndex].sales_dinero.add(c.sales_dinero)
          };
        } else {
          //Add it to the list instead.
          multiSummary.costCenterData.push(c);
        }
      });

      //Add all summary data.
      multiSummary.summaryData.totalPartsSales = multiSummary.summaryData.totalPartsSales.add(
        costingData.summaryData.totalPartsSales
      );
      multiSummary.summaryData.totalAdditionalSales = multiSummary.summaryData.totalAdditionalSales.add(
        costingData.summaryData.totalAdditionalSales
      );
      multiSummary.summaryData.totalSubletSales = multiSummary.summaryData.totalSubletSales.add(
        costingData.summaryData.totalSubletSales
      );
      multiSummary.summaryData.totalSales = multiSummary.summaryData.totalSales.add(costingData.summaryData.totalSales);
      multiSummary.summaryData.totalLaborCost = multiSummary.summaryData.totalLaborCost.add(
        costingData.summaryData.totalLaborCost
      );
      multiSummary.summaryData.totalLaborSales = multiSummary.summaryData.totalLaborSales.add(
        costingData.summaryData.totalLaborSales
      );
      multiSummary.summaryData.totalPartsCost = multiSummary.summaryData.totalPartsCost.add(
        costingData.summaryData.totalPartsCost
      );
      multiSummary.summaryData.totalAdditionalCost = multiSummary.summaryData.totalAdditionalCost.add(
        costingData.summaryData.totalAdditionalCost
      );
      multiSummary.summaryData.totalSubletCost = multiSummary.summaryData.totalSubletCost.add(
        costingData.summaryData.totalSubletCost
      );
      multiSummary.summaryData.totalCost = multiSummary.summaryData.totalCost.add(costingData.summaryData.totalCost);
      multiSummary.summaryData.gpdollars = multiSummary.summaryData.gpdollars.add(costingData.summaryData.gpdollars);

      multiSummary.summaryData.totalLaborGp = multiSummary.summaryData.totalLaborGp.add(
        costingData.summaryData.totalLaborGp
      );
      multiSummary.summaryData.totalPartsGp = multiSummary.summaryData.totalPartsGp.add(
        costingData.summaryData.totalPartsGp
      );
      multiSummary.summaryData.totalAdditionalGp = multiSummary.summaryData.totalAdditionalGp.add(
        costingData.summaryData.totalAdditionalGp
      );
      multiSummary.summaryData.totalSubletGp = multiSummary.summaryData.totalSubletGp.add(
        costingData.summaryData.totalSubletGp
      );

      //Take the summary data & add it to total summary data.
    });

    //For each center, recalculate and toFormat() the values.

    multiSummary.summaryData.totalLaborGppercent = (
      (multiSummary.summaryData.totalLaborGp.getAmount() / multiSummary.summaryData.totalLaborSales.getAmount()) *
      100
    ).toFixed(1);
    multiSummary.summaryData.totalLaborGppercentFormatted = formatGpPercent(
      multiSummary.summaryData.totalLaborGppercent
    );

    multiSummary.summaryData.totalPartsGppercent = (
      (multiSummary.summaryData.totalPartsGp.getAmount() / multiSummary.summaryData.totalPartsSales.getAmount()) *
      100
    ).toFixed(1);

    multiSummary.summaryData.totalPartsGppercentFormatted = formatGpPercent(
      multiSummary.summaryData.totalPartsGppercent
    );

    multiSummary.summaryData.totalAdditionalGppercent = (
      (multiSummary.summaryData.totalAdditionalGp.getAmount() /
        multiSummary.summaryData.totalAdditionalSales.getAmount()) *
      100
    ).toFixed(1);

    multiSummary.summaryData.totalAdditionalGppercentFormatted = formatGpPercent(
      multiSummary.summaryData.totalAdditionalGppercent
    );

    multiSummary.summaryData.totalSubletGppercent = (
      (multiSummary.summaryData.totalSubletGp.getAmount() / multiSummary.summaryData.totalSubletSales.getAmount()) *
      100
    ).toFixed(1);

    multiSummary.summaryData.totalSubletGppercentFormatted = formatGpPercent(
      multiSummary.summaryData.totalSubletGppercent
    );

    multiSummary.summaryData.gppercent = (
      (multiSummary.summaryData.gpdollars.getAmount() / multiSummary.summaryData.totalSales.getAmount()) *
      100
    ).toFixed(1);

    multiSummary.summaryData.gppercentFormatted = formatGpPercent(multiSummary.summaryData.gppercent);

    const finalCostingdata = multiSummary.costCenterData.map((c) => {
      return {
        ...c,
        sale_labor: c.sale_labor_dinero && c.sale_labor_dinero.toFormat(),
        sale_parts: c.sale_parts_dinero && c.sale_parts_dinero.toFormat(),
        sale_additional: c.sale_additional_dinero && c.sale_additional_dinero.toFormat(),
        sale_sublet: c.sale_sublet_dinero && c.sale_sublet_dinero.toFormat(),
        sales: c.sales_dinero.toFormat(),
        cost_parts: c.cost_parts_dinero && c.cost_parts_dinero.toFormat(),
        cost_labor: c.cost_labor_dinero && c.cost_labor_dinero.toFormat(),
        cost_additional: c.cost_additional_dinero && c.cost_additional_dinero.toFormat(),
        cost_sublet: c.cost_sublet_dinero && c.cost_sublet_dinero.toFormat(),
        costs: c.costs_dinero.toFormat(),
        gpdollars: c.gpdollars_dinero.toFormat(),
        gppercent: formatGpPercent(((c.gpdollars_dinero.getAmount() / c.sales_dinero.getAmount()) * 100).toFixed(1))
      };
    });

    //Calculate thte total gross profit percentages.

    res.status(200).json({
      allCostCenterData: finalCostingdata,
      allSummaryData: multiSummary.summaryData,
      data: ret
    });
  } catch (error) {
    logger.log("job-costing-multi-error", "ERROR", req?.user?.email, null, {
      jobids,
      message: error.message,
      stack: error.stack
    });
    res.status(400).send(error);
  }
}

function GenerateCostingData(job) {
  const defaultProfits = job.bodyshop.md_responsibility_centers.defaults.profits;
  const allCenters = _.union(
    job.bodyshop.md_responsibility_centers.profits.map((p) => p.name),
    job.bodyshop.md_responsibility_centers.costs.map((p) => p.name),
    ["Unknown"]
  );

  const materialsHours = { mapaHrs: 0, mashHrs: 0 };
  let mashOpCodes = isRome && ParseCalopCode(job.materials["MASH"]?.cal_opcode);
  let hasMapaLine = false;
  let hasMashLine = false;

  //Massage the data.
  const jobLineTotalsByProfitCenter =
    job &&
    job.joblines.reduce(
      (acc, val) => {
        //Shop or Paint Material Flags
        if (val.db_ref === "936008") {
          //If either of these DB REFs change, they also need to change in job-totals/job-costing calculations.
          hasMapaLine = true;
        }
        if (val.db_ref === "936007") {
          hasMashLine = true;
        }

        //Labor Profit Center
        if (val.mod_lbr_ty) {
          const laborProfitCenter = val.profitcenter_labor || defaultProfits[val.mod_lbr_ty] || "Unknown";

          //Uncomment for further testing
          // if (laborProfitCenter === "Unknown") {
          //   logger.log("job-costing unknown type", "debug", null, null, {
          //     line_desc: val.line_desc,
          //     mod_lbr_ty: val.mod_lbr_ty
          //   });
          // }

          const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`;

          let laborAmount = Dinero();
          laborAmount = Dinero({
            amount: Math.round((job[rateName] || 0) * 100)
          }).multiply(val.mod_lb_hrs || 0);

          if (
            job.cieca_pfl &&
            job.cieca_pfl[val.mod_lbr_ty.toUpperCase()] &&
            typeof job.cieca_pfl[val.mod_lbr_ty.toUpperCase()].lbr_adjp === "number" &&
            job.cieca_pfl[val.mod_lbr_ty.toUpperCase()].lbr_adjp !== 0
          ) {
            let adjp = 0;
            if (
              val.mod_lbr_ty === "la1" ||
              val.mod_lbr_ty === "la2" ||
              val.mod_lbr_ty === "la3" ||
              val.mod_lbr_ty === "la4"
            ) {
              adjp =
                Math.abs(job.cieca_pfl["LAU"].lbr_adjp) > 1
                  ? job.cieca_pfl["LAU"].lbr_adjp
                  : job.cieca_pfl["LAU"].lbr_adjp * 100; //Adjust lbr_adjp to whole number
            } else {
              if (job.cieca_pfl[val.mod_lbr_ty.toUpperCase()]) {
                adjp =
                  Math.abs(job.cieca_pfl[val.mod_lbr_ty.toUpperCase()].lbr_adjp) > 1
                    ? job.cieca_pfl[val.mod_lbr_ty.toUpperCase()].lbr_adjp
                    : job.cieca_pfl[val.mod_lbr_ty.toUpperCase()].lbr_adjp * 100; //Adjust lbr_adjp to whole number
              } else {
                adjp =
                  Math.abs(job.cieca_pfl["LAB"].lbr_adjp) > 1
                    ? job.cieca_pfl["LAB"].lbr_adjp
                    : job.cieca_pfl["LAB"].lbr_adjp * 100; //Adjust lbr_adjp to whole number
              }
            }
            laborAmount = laborAmount.add(
              laborAmount.percentage(adjp < 0 ? adjp * -1 : adjp).multiply(adjp < 0 ? -1 : 1)
            );
          }

          if (!acc.labor[laborProfitCenter]) acc.labor[laborProfitCenter] = Dinero();
          acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(laborAmount);

          if (val.act_price > 0 && val.lbr_op === "OP14" && !val.part_type) {
            //Scenario where SGI may pay out hours using a part price.
            acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(
              Dinero({
                amount: Math.round((val.act_price || 0) * 100)
              }).multiply(val.part_qty)
            );
          }

          if (val.mod_lbr_ty === "LAR") {
            materialsHours.mapaHrs += val.mod_lb_hrs || 0;
          }
          if (isImEX) {
            if (val.mod_lbr_ty !== "LAR") {
              materialsHours.mashHrs += val.mod_lb_hrs || 0;
            }
          } else {
            if (val.mod_lbr_ty !== "LAR" && mashOpCodes.includes(val.lbr_op)) {
              materialsHours.mashHrs += val.mod_lb_hrs || 0;
            }
            if (val.manual_line === true && !mashOpCodes.includes(val.lbr_op) && val.mod_lbr_ty !== "LAR") {
              materialsHours.mashHrs += val.mod_lb_hrs || 0;
            }
          }
        }

        // Part Profit Center
        if (val.part_type && val.part_type !== "PAE" && val.part_type !== "PAS" && val.part_type !== "PASL") {
          const partsProfitCenter = val.profitcenter_part || defaultProfits[val.part_type] || "Unknown";

          //Uncomment for further testing
          // if (partsProfitCenter === "Unknown" || !partsProfitCenter) {
          //   logger.log(
          //     partsProfitCenter === "Unknown"
          //       ? "job-costing unknown type"
          //       : "Unknown cost/profit center mapping for parts.",
          //     "debug",
          //     null,
          //     null,
          //     {
          //       line_desc: val.line_desc,
          //       part_type: val.part_type
          //     }
          //   );
          // }

          let partsAmount = Dinero({
            amount: val.act_price_before_ppc
              ? Math.round(val.act_price_before_ppc * 100)
              : Math.round(val.act_price * 100)
          })
            .multiply(val.part_qty || 1)
            .add(
              ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
                DiscountNotAlreadyCounted(val, job.joblines)
                ? val.prt_dsmk_m
                  ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
                  : Dinero({
                      amount: val.act_price_before_ppc
                        ? Math.round(val.act_price_before_ppc * 100)
                        : Math.round(val.act_price * 100)
                    })
                      .multiply(val.part_qty || 0)
                      .percentage(Math.abs(val.prt_dsmk_p || 0))
                      .multiply(val.prt_dsmk_p > 0 ? 1 : -1)
                : Dinero()
            );

          if (!acc.parts[partsProfitCenter]) acc.parts[partsProfitCenter] = Dinero();
          acc.parts[partsProfitCenter] = acc.parts[partsProfitCenter].add(partsAmount);
        }

        //Sublet Profit Center
        if (val.part_type && val.part_type !== "PAE" && (val.part_type === "PAS" || val.part_type === "PASL")) {
          const partsProfitCenter = val.profitcenter_part || defaultProfits[val.part_type] || "Unknown";

          //Uncomment for further testing
          // if (partsProfitCenter === "Unknown" || !partsProfitCenter) {
          //   logger.log(
          //     partsProfitCenter === "Unknown"
          //       ? "job-costing unknown type"
          //       : "job-costing Unknown cost/profit center mapping for sublet",
          //     "debug",
          //     null,
          //     null,
          //     {
          //       line_desc: val.line_desc,
          //       part_type: val.part_type
          //     }
          //   );
          // }

          const partsAmount = Dinero({
            amount: Math.round((val.act_price || 0) * 100)
          })
            .multiply(val.part_qty || 1)
            .add(
              ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
                DiscountNotAlreadyCounted(val, job.joblines)
                ? val.prt_dsmk_m
                  ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
                  : Dinero({
                      amount: Math.round(val.act_price * 100)
                    })
                      .multiply(val.part_qty || 0)
                      .percentage(Math.abs(val.prt_dsmk_p || 0))
                      .multiply(val.prt_dsmk_p > 0 ? 1 : -1)
                : Dinero()
            );
          if (!acc.sublet[partsProfitCenter]) acc.sublet[partsProfitCenter] = Dinero();
          acc.sublet[partsProfitCenter] = acc.sublet[partsProfitCenter].add(partsAmount);
        }

        //Additional Profit Center
        if ((!val.part_type && !val.mod_lbr_ty) || (!val.part_type && val.mod_lbr_ty && val.lbr_op !== "OP14")) {
          //Does it already have a defined profit center?
          //If so, use it, otherwise try to use the same from the auto-allocate logic in IO app jobs-close-auto-allocate.
          const partsProfitCenter = val.profitcenter_part || getAdditionalCostCenter(val, defaultProfits) || "Unknown";

          //Uncomment for further testing
          // if (partsProfitCenter === "Unknown") {
          //   logger.log("job-costing unknown type", "debug", null, null, {
          //     line_desc: val.line_desc,
          //     part_type: val.part_type
          //   });
          // }

          const partsAmount = Dinero({
            amount: Math.round((val.act_price || 0) * 100)
          })
            .multiply(val.part_qty || 1)
            .add(
              ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
                DiscountNotAlreadyCounted(val, job.joblines)
                ? val.prt_dsmk_m
                  ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
                  : Dinero({
                      amount: Math.round(val.act_price * 100)
                    })
                      .multiply(val.part_qty || 0)
                      .percentage(Math.abs(val.prt_dsmk_p || 0))
                      .multiply(val.prt_dsmk_p > 0 ? 1 : -1)
                : Dinero()
            );

          if (!acc.additional[partsProfitCenter]) acc.additional[partsProfitCenter] = Dinero();
          acc.additional[partsProfitCenter] = acc.additional[partsProfitCenter].add(partsAmount);
        }

        return acc;
      },
      { parts: {}, labor: {}, additional: {}, sublet: {} }
    );

  // Profile Discount for Parts
  Object.keys(jobLineTotalsByProfitCenter.parts).forEach((key) => {
    let disc = Dinero(),
      markup = Dinero();
    const convertedKey = Object.keys(defaultProfits).find((k) => defaultProfits[k] === key);
    if (convertedKey && job.parts_tax_rates && job.parts_tax_rates[convertedKey.toUpperCase()]) {
      if (
        job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp !== undefined &&
        job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp >= 0
      ) {
        const discountRate =
          Math.abs(job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp) > 1
            ? job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp
            : job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp * 100;
        disc = jobLineTotalsByProfitCenter.parts[key].percentage(discountRate).multiply(-1);
      }
      if (
        job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp !== undefined &&
        job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp >= 0
      ) {
        const markupRate =
          Math.abs(job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp) > 1
            ? job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp
            : job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp * 100;
        markup = jobLineTotalsByProfitCenter.parts[key].percentage(markupRate);
      }
    }

    if (isRome) {
      if (convertedKey) {
        const correspondingCiecaStlTotalLine = job.cieca_stl?.data.find(
          (c) => c.ttl_typecd === convertedKey.toUpperCase()
        );
        if (
          correspondingCiecaStlTotalLine &&
          Math.abs(jobLineTotalsByProfitCenter.parts[key].getAmount() - correspondingCiecaStlTotalLine.ttl_amt * 100) >
            1
        ) {
          jobLineTotalsByProfitCenter.parts[key] = jobLineTotalsByProfitCenter.parts[key].add(disc).add(markup);
        }
      }
    }
  });

  if (!hasMapaLine) {
    let threshold;
    if (
      job.materials["MAPA"] &&
      job.materials["MAPA"].cal_maxdlr !== undefined &&
      (isRome ? job.materials["MAPA"].cal_maxdlr >= 0 : job.materials["MAPA"].cal_maxdlr > 0)
    ) {
      //It has an upper threshhold.
      threshold = Dinero({
        amount: Math.round(job.materials["MAPA"].cal_maxdlr * 100)
      });
    }

    if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]])
      jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = Dinero();

    const origMAPAAmount = jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]];

    jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = jobLineTotalsByProfitCenter.additional[
      defaultProfits["MAPA"]
    ].add(
      Dinero({
        amount: Math.round((job.rate_mapa || 0) * 100)
      }).multiply(materialsHours.mapaHrs || 0)
    );
    let adjp = 0;
    if (
      job.materials["MAPA"] &&
      job.materials["MAPA"].mat_adjp &&
      typeof job.materials["MAPA"].mat_adjp === "number" &&
      job.materials["MAPA"].mat_adjp !== 0
    ) {
      adjp =
        Math.abs(job.materials["MAPA"].mat_adjp) > 1
          ? job.materials["MAPA"].mat_adjp
          : job.materials["MAPA"].mat_adjp * 100; //Adjust mat_adjp to whole number
    }

    jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = jobLineTotalsByProfitCenter.additional[
      defaultProfits["MAPA"]
    ].add(
      jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]]
        .percentage(adjp < 0 ? adjp * -1 : adjp)
        .multiply(adjp < 0 ? -1 : 1)
    );

    if (threshold && jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]].greaterThanOrEqual(threshold)) {
      jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = threshold.add(origMAPAAmount);
    }
  }
  if (!hasMashLine) {
    let threshold;
    if (
      job.materials["MASH"] &&
      job.materials["MASH"].cal_maxdlr !== undefined &&
      (isRome ? job.materials["MASH"].cal_maxdlr >= 0 : job.materials["MASH"].cal_maxdlr > 0)
    ) {
      //It has an upper threshhold.
      threshold = Dinero({
        amount: Math.round(job.materials["MASH"].cal_maxdlr * 100)
      });
    }

    if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]])
      jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = Dinero();

    const origMASHAmount = jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]];

    jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = jobLineTotalsByProfitCenter.additional[
      defaultProfits["MASH"]
    ].add(
      Dinero({
        amount: Math.round((job.rate_mash || 0) * 100)
      }).multiply(materialsHours.mashHrs || 0)
    );
    let adjp = 0;
    if (
      job.materials["MASH"] &&
      job.materials["MASH"].mat_adjp &&
      typeof job.materials["MASH"].mat_adjp === "number" &&
      job.materials["MASH"].mat_adjp !== 0
    ) {
      adjp =
        Math.abs(job.materials["MASH"].mat_adjp) > 1
          ? job.materials["MASH"].mat_adjp
          : job.materials["MASH"].mat_adjp * 100; //Adjust mat_adjp to whole number
    }

    jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = jobLineTotalsByProfitCenter.additional[
      defaultProfits["MASH"]
    ].add(
      jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]]
        .percentage(adjp < 0 ? adjp * -1 : adjp)
        .multiply(adjp < 0 ? -1 : 1)
    );

    if (threshold && jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]].greaterThanOrEqual(threshold)) {
      jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = threshold.add(origMASHAmount);
    }
  }

  if (isRome) {
    const stlTowing = job.cieca_stl?.data.find((c) => c.ttl_type === "OTTW");
    const stlStorage = job.cieca_stl?.data.find((c) => c.ttl_type === "OTST");

    if (!jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]])
      jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = Dinero();

    if (stlTowing)
      jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = Dinero({
        amount: Math.round((stlTowing.ttl_amt || 0) * 100)
      });
    if (!stlTowing && job.towing_payable && job.towing_payable > 0)
      jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = jobLineTotalsByProfitCenter.additional[
        defaultProfits["TOW"]
      ].add(
        Dinero({
          amount: Math.round((job.towing_payable || 0) * 100)
        })
      );

    if (!jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]])
      jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]] = Dinero();

    if (stlStorage)
      jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = Dinero({
        amount: Math.round((stlStorage.ttl_amt || 0) * 100)
      });
    if (!stlStorage && job.storage_payable && job.storage_payable > 0)
      jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]] = jobLineTotalsByProfitCenter.additional[
        defaultProfits["STO"]
      ].add(
        Dinero({
          amount: Math.round((job.storage_payable || 0) * 100)
        })
      );
  }

  //Is it a DMS Setup?
  const selectedDmsAllocationConfig =
    (job.bodyshop.md_responsibility_centers.dms_defaults &&
      job.bodyshop.md_responsibility_centers.dms_defaults.find((d) => d.name === job.dms_allocation)) ||
    job.bodyshop.md_responsibility_centers.defaults;

  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 (job.bodyshop.pbs_serialnumber || job.bodyshop.cdk_dealerid || job.bodyshop.rr_dealerid) {
          if (!bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]])
            bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = Dinero();

          bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = bill_acc[
            selectedDmsAllocationConfig.costs[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)
          );
        } else {
          const isSubletCostCenter =
            line_val.cost_center === job.bodyshop.md_responsibility_centers.defaults.costs.PAS ||
            line_val.cost_center === job.bodyshop.md_responsibility_centers.defaults.costs.PASL;

          const isAdditionalCostCenter =
            // line_val.cost_center ===
            //   job.bodyshop.md_responsibility_centers.defaults.costs.PAS ||
            // line_val.cost_center ===
            //   job.bodyshop.md_responsibility_centers.defaults.costs.PASL ||
            line_val.cost_center === job.bodyshop.md_responsibility_centers.defaults.costs.TOW ||
            line_val.cost_center === job.bodyshop.md_responsibility_centers.defaults.costs.MAPA ||
            line_val.cost_center === job.bodyshop.md_responsibility_centers.defaults.costs.MASH;

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

            bill_acc.additionalCosts[line_val.cost_center] = bill_acc.additionalCosts[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)
            );
          } else if (isSubletCostCenter) {
            if (!bill_acc.subletCosts[line_val.cost_center]) bill_acc.subletCosts[line_val.cost_center] = Dinero();

            bill_acc.subletCosts[line_val.cost_center] = bill_acc.subletCosts[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)
            );
          } else {
            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;
    },
    { additionalCosts: {}, subletCosts: {} }
  );

  //If the hourly rates for job costing are set, add them in.

  if (
    job.bodyshop.jc_hourly_rates &&
    (job.bodyshop.jc_hourly_rates.mapa ||
      typeof job.bodyshop.jc_hourly_rates.mapa === "number" ||
      isNaN(job.bodyshop.jc_hourly_rates.mapa) === false)
  ) {
    if (!billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA])
      billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = Dinero();
    if (job.bodyshop.use_paint_scale_data === true) {
      if (job.mixdata.length > 0) {
        billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = Dinero({
          amount: Math.round(((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100)
        });
      } else {
        billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] =
          billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA].add(
            Dinero({
              amount: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa * 100) || 0)
            }).multiply(materialsHours.mapaHrs)
          );
      }
    } else {
      billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] =
        billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA].add(
          Dinero({
            amount: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa * 100) || 0)
          }).multiply(materialsHours.mapaHrs)
        );
    }
  }

  if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) {
    if (!billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MASH])
      billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MASH] = Dinero();
    billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MASH] =
      billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MASH].add(
        Dinero({
          amount: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash * 100) || 0)
        }).multiply(materialsHours.mashHrs)
      );
  }

  const ticketTotalsByCostCenter = job.timetickets.reduce((ticket_acc, ticket_val) => {
    //At the invoice level.

    if (job.bodyshop.pbs_serialnumber || job.bodyshop.cdk_dealerid || job.bodyshop.rr_dealerid) {
      if (!ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]])
        ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]] = Dinero();

      ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]] = ticket_acc[
        selectedDmsAllocationConfig.costs[ticket_val.ciecacode]
      ].add(
        Dinero({
          amount: Math.round((ticket_val.rate || 0) * 100)
        }).multiply(
          ticket_val.flat_rate
            ? ticket_val.productivehrs || ticket_val.actualhrs || 0
            : ticket_val.actualhrs || ticket_val.productivehrs || 0
        ) //Should base this on the employee.
      );
    } else {
      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.flat_rate
            ? ticket_val.productivehrs || ticket_val.actualhrs || 0
            : ticket_val.actualhrs || ticket_val.productivehrs || 0
        ) //Should base this on the employee.
      );
    }

    return ticket_acc;
  }, {});

  const summaryData = {
    totalLaborSales: Dinero({ amount: 0 }),
    totalPartsSales: Dinero({ amount: 0 }),
    totalAdditionalSales: Dinero({ amount: 0 }),
    totalSubletSales: Dinero({ amount: 0 }),
    totalSales: Dinero({ amount: 0 }),
    totalLaborCost: Dinero({ amount: 0 }),
    totalPartsCost: Dinero({ amount: 0 }),
    totalAdditionalCost: Dinero({ amount: 0 }),
    totalSubletCost: Dinero({ amount: 0 }),
    totalCost: Dinero({ amount: 0 }),
    totalLaborGp: Dinero({ amount: 0 }),
    totalPartsGp: Dinero({ amount: 0 }),
    totalAdditionalGp: Dinero({ amount: 0 }),
    totalSubletGp: Dinero({ amount: 0 }),
    gpdollars: Dinero({ amount: 0 }),
    totalLaborGppercent: null,
    totalLaborGppercentFormatted: null,
    totalPartsGppercent: null,
    totalPartsGppercentFormatted: null,
    totalAdditionalGppercent: null,
    totalAdditionalGppercentFormatted: null,
    totalSubletGppercent: null,
    totalSubletGppercentFormatted: null,
    gppercent: null,
    gppercentFormatted: null
  };

  const costCenterData = allCenters.map((key, idx) => {
    const ccVal = key; // defaultProfits[key];
    const sale_labor = jobLineTotalsByProfitCenter.labor[ccVal] || Dinero({ amount: 0 });
    const sale_parts = jobLineTotalsByProfitCenter.parts[ccVal] || Dinero({ amount: 0 });
    const sale_additional = jobLineTotalsByProfitCenter.additional[ccVal] || Dinero({ amount: 0 });
    const sale_sublet = jobLineTotalsByProfitCenter.sublet[ccVal] || Dinero({ amount: 0 });

    const cost_labor = ticketTotalsByCostCenter[ccVal] || Dinero({ amount: 0 });
    const cost_parts = billTotalsByCostCenters[ccVal] || Dinero({ amount: 0 });
    const cost_additional = billTotalsByCostCenters.additionalCosts[ccVal] || Dinero({ amount: 0 });
    const cost_sublet = billTotalsByCostCenters.subletCosts[ccVal] || Dinero({ amount: 0 });

    const costs = cost_labor.add(cost_parts).add(cost_additional).add(cost_sublet);
    const totalSales = sale_labor.add(sale_parts).add(sale_additional).add(sale_sublet);
    const gpdollars = totalSales.subtract(costs);
    const gppercent = ((gpdollars.getAmount() / Math.abs(totalSales.getAmount())) * 100).toFixed(1);

    //Push summary data to avoid extra loop.
    summaryData.totalLaborSales = summaryData.totalLaborSales.add(sale_labor);
    summaryData.totalPartsSales = summaryData.totalPartsSales.add(sale_parts);
    summaryData.totalAdditionalSales = summaryData.totalAdditionalSales.add(sale_additional);
    summaryData.totalSubletSales = summaryData.totalSubletSales.add(sale_sublet);
    summaryData.totalSales = summaryData.totalSales.add(totalSales);
    summaryData.totalLaborCost = summaryData.totalLaborCost.add(cost_labor);
    summaryData.totalPartsCost = summaryData.totalPartsCost.add(cost_parts);
    summaryData.totalAdditionalCost = summaryData.totalAdditionalCost.add(cost_additional);
    summaryData.totalSubletCost = summaryData.totalSubletCost.add(cost_sublet);
    summaryData.totalCost = summaryData.totalCost.add(costs);

    return {
      id: idx,
      cost_center: ccVal,
      sale_labor: sale_labor && sale_labor.toFormat(),
      sale_labor_dinero: sale_labor,
      sale_parts: sale_parts && sale_parts.toFormat(),
      sale_parts_dinero: sale_parts,
      sale_additional: sale_additional && sale_additional.toFormat(),
      sale_additional_dinero: sale_additional,
      sale_sublet: sale_sublet && sale_sublet.toFormat(),
      sale_sublet_dinero: sale_sublet,
      sales: totalSales.toFormat(),
      sales_dinero: totalSales,
      cost_parts: cost_parts && cost_parts.toFormat(),
      cost_parts_dinero: cost_parts,
      cost_labor: cost_labor && cost_labor.toFormat(),
      cost_labor_dinero: cost_labor,
      cost_additional: cost_additional && cost_additional.toFormat(),
      cost_additional_dinero: cost_additional,
      cost_sublet: cost_sublet && cost_sublet.toFormat(),
      cost_sublet_dinero: cost_sublet,
      costs: costs.toFormat(),
      costs_dinero: costs,
      gpdollars_dinero: gpdollars,
      gpdollars: gpdollars.toFormat(),
      gppercent: formatGpPercent(gppercent)
    };
  });

  //Push adjustments to bottom line.
  if (job.adjustment_bottom_line) {
    //Add to totals.
    const Adjustment = Dinero({
      amount: Math.round(job.adjustment_bottom_line * 100)
    }); //Need to invert, since this is being assigned as a cost.
    summaryData.totalLaborSales = summaryData.totalLaborSales.add(Adjustment);
    summaryData.totalSales = summaryData.totalSales.add(Adjustment);
    //Add to lines.
    costCenterData.push({
      id: "Adj",
      cost_center: "Adjustment",
      sale_labor: Adjustment.toFormat(),
      sale_labor_dinero: Adjustment,
      sale_parts: Dinero().toFormat(),
      sale_parts_dinero: Dinero(),
      sale_additional: Dinero(),
      sale_additional_dinero: Dinero(),
      sale_sublet: Dinero(),
      sale_sublet_dinero: Dinero(),
      sales: Adjustment.toFormat(),
      sales_dinero: Adjustment,
      cost_parts: Dinero().toFormat(),
      cost_parts_dinero: Dinero(),
      cost_labor: Dinero().toFormat(), //Adjustment.toFormat(),
      cost_labor_dinero: Dinero(), // Adjustment,
      cost_additional: Dinero(),
      cost_additional_dinero: Dinero(),
      cost_sublet: Dinero(),
      cost_sublet_dinero: Dinero(),
      costs: Dinero().toFormat(),
      costs_dinero: Dinero(),
      gpdollars_dinero: Dinero(),
      gpdollars: Dinero().toFormat(),
      gppercent: formatGpPercent(0)
    });
  }
  //Push adjustments to bottom line.
  if (job.job_totals?.totals?.ttl_adjustment) {
    //Add to totals.
    const Adjustment = Dinero(job.job_totals.totals.ttl_adjustment); //Need to invert, since this is being assigned as a cost.
    summaryData.totalAdditionalSales = summaryData.totalAdditionalSales.add(Adjustment);
    summaryData.totalSales = summaryData.totalSales.add(Adjustment);
    //Add to lines.
    costCenterData.push({
      id: "AdjEst",
      cost_center: "Adjustment (Est. Match)",
      sale_labor: Dinero().toFormat(),
      sale_labor_dinero: Dinero(),
      sale_parts: Dinero().toFormat(),
      sale_parts_dinero: Dinero(),
      sale_additional: Adjustment.toFormat(),
      sale_additional_dinero: Adjustment,
      sale_sublet: Dinero(),
      sale_sublet_dinero: Dinero(),
      sales: Adjustment.toFormat(),
      sales_dinero: Adjustment,
      cost_parts: Dinero().toFormat(),
      cost_parts_dinero: Dinero(),
      cost_labor: Dinero().toFormat(), //Adjustment.toFormat(),
      cost_labor_dinero: Dinero(), // Adjustment,
      cost_additional: Dinero(),
      cost_additional_dinero: Dinero(),
      cost_sublet: Dinero(),
      cost_sublet_dinero: Dinero(),
      costs: Dinero().toFormat(),
      costs_dinero: Dinero(),
      gpdollars_dinero: Dinero(),
      gpdollars: Dinero().toFormat(),
      gppercent: formatGpPercent(0)
    });
  }

  //Final summary data massaging.

  summaryData.totalLaborGp = summaryData.totalLaborSales.subtract(summaryData.totalLaborCost);
  summaryData.totalLaborGppercent = (
    (summaryData.totalLaborGp.getAmount() / summaryData.totalLaborSales.getAmount()) *
    100
  ).toFixed(1);
  summaryData.totalLaborGppercentFormatted = formatGpPercent(summaryData.totalLaborGppercent);

  summaryData.totalPartsGp = summaryData.totalPartsSales.subtract(summaryData.totalPartsCost);
  summaryData.totalPartsGppercent = (
    (summaryData.totalPartsGp.getAmount() / summaryData.totalPartsSales.getAmount()) *
    100
  ).toFixed(1);
  summaryData.totalPartsGppercentFormatted = formatGpPercent(summaryData.totalPartsGppercent);
  summaryData.totalAdditionalGp = summaryData.totalAdditionalSales.subtract(summaryData.totalAdditionalCost);
  summaryData.totalAdditionalGppercent = (
    (summaryData.totalAdditionalGp.getAmount() / summaryData.totalAdditionalSales.getAmount()) *
    100
  ).toFixed(1);
  summaryData.totalAdditionalGppercentFormatted = formatGpPercent(summaryData.totalAdditionalGppercent);
  summaryData.totalSubletGp = summaryData.totalSubletSales.subtract(summaryData.totalSubletCost);
  summaryData.totalSubletGppercent = (
    (summaryData.totalSubletGp.getAmount() / summaryData.totalSubletSales.getAmount()) *
    100
  ).toFixed(1);
  summaryData.totalSubletGppercentFormatted = formatGpPercent(summaryData.totalSubletGppercent);

  summaryData.gpdollars = summaryData.totalSales.subtract(summaryData.totalCost);
  summaryData.gppercent = (
    (summaryData.gpdollars.getAmount() / Math.abs(summaryData.totalSales.getAmount())) *
    100
  ).toFixed(1);

  if (isNaN(summaryData.gppercent)) summaryData.gppercentFormatted = 0;
  else if (!isFinite(summaryData.gppercent)) summaryData.gppercentFormatted = "- ∞";
  else {
    summaryData.gppercentFormatted = `${summaryData.gppercent}%`;
  }

  return { summaryData, costCenterData };
}

exports.JobCosting = JobCosting;
exports.JobCostingMulti = JobCostingMulti;

const formatGpPercent = (gppercent) => {
  let gppercentFormatted;
  if (isNaN(gppercent)) gppercentFormatted = "0%";
  else if (!isFinite(gppercent)) gppercentFormatted = "- ∞";
  else {
    gppercentFormatted = `${gppercent}%`;
  }

  return gppercentFormatted;
};

//Verify that this stays in line with jobs-close-auto-allocate logic from the application.
const getAdditionalCostCenter = (jl, profitCenters) => {
  if (!jl.part_type && !jl.mod_lbr_ty) {
    const lineDesc = jl.line_desc ? jl.line_desc.toLowerCase() : "";

    if (lineDesc.includes("shop mat")) {
      return profitCenters["MASH"];
    } else if (lineDesc.includes("paint/mat")) {
      return profitCenters["MAPA"];
    } else if (lineDesc.includes("ats amount")) {
      return profitCenters["ATS"];
    } else if (lineDesc.includes("towing")) {
      return profitCenters["TOW"];
    } else if (jl.act_price > 0) {
      //TODO:AIO Ensure that this is tested.
      return profitCenters["PAO"];
    } else {
      return null;
    }
  }
};
