const GraphQLClient = require("graphql-request").GraphQLClient;

const queries = require("../graphql-client/queries");
const CreateFortellisLogEvent = require("../fortellis/fortellis-logger");
const Dinero = require("dinero.js");
const _ = require("lodash");
const WsLogger = require("../web-sockets/createLogEvent");

const InstanceManager = require("../utils/instanceMgr").default;
const { DiscountNotAlreadyCounted } = InstanceManager({
  imex: require("../job/job-totals"),
  rome: require("../job/job-totals-USA")
});

exports.defaultRoute = async function (req, res) {
  try {
    WsLogger.createLogEvent(req, "DEBUG", `Received request to calculate allocations for ${req.body.jobid}`);
    const jobData = await QueryJobData(req, req.BearerToken, req.body.jobid);
    return res.status(200).json({ data: calculateAllocations(req, jobData) });
  } catch (error) {
    WsLogger.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
    WsLogger.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error.stack}`);
    res.status(500).json({ error: `Error encountered in CdkCalculateAllocations. ${error}` });
  }
};

exports.default = async function (socket, jobid, isFortellis = false) {
  try {
    const jobData = await QueryJobData(socket, "Bearer " + socket.handshake.auth.token, jobid, isFortellis);
    return calculateAllocations(socket, jobData, isFortellis);
  } catch (error) {
    const loggingFunction = isFortellis ? CreateFortellisLogEvent : WsLogger.createLogEvent;
    loggingFunction(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
    loggingFunction(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error.stack}`);
  }
};

async function QueryJobData(connectionData, token, jobid, isFortellis) {
  const loggingFunction = isFortellis ? CreateFortellisLogEvent : WsLogger.createLogEvent;

  loggingFunction(connectionData, "DEBUG", `Querying job data for id ${jobid}`);
  const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
  const result = await client.setHeaders({ Authorization: token }).request(queries.GET_CDK_ALLOCATIONS, { id: jobid });
  //loggingFunction(connectionData, "DEBUG", `Job data query result ${JSON.stringify(result, null, 2)}`);
  return result.jobs_by_pk;
}

function calculateAllocations(connectionData, job, isFortellis) {
  const { bodyshop } = job;
  const loggingFunction = isFortellis ? CreateFortellisLogEvent : WsLogger.createLogEvent;
  const taxAllocations = InstanceManager({
    executeFunction: true,
    deubg: true,
    args: [],
    imex: () => ({
      state: {
        center: bodyshop.md_responsibility_centers.taxes.state.name,
        sale: Dinero(job.job_totals.totals.state_tax),
        cost: Dinero(),
        profitCenter: bodyshop.md_responsibility_centers.taxes.state,
        costCenter: bodyshop.md_responsibility_centers.taxes.state
      },
      federal: {
        center: bodyshop.md_responsibility_centers.taxes.federal.name,
        sale: Dinero(job.job_totals.totals.federal_tax),
        cost: Dinero(),
        profitCenter: bodyshop.md_responsibility_centers.taxes.federal,
        costCenter: bodyshop.md_responsibility_centers.taxes.federal
      }
    }),
    rome: () => ({
      tax_ty1: {
        center: bodyshop.md_responsibility_centers.taxes[`tax_ty1`].name,
        sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty1Tax`]),
        cost: Dinero(),
        profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty1`],
        costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty1`]
      },
      tax_ty2: {
        center: bodyshop.md_responsibility_centers.taxes[`tax_ty2`].name,
        sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty2Tax`]),
        cost: Dinero(),
        profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty2`],
        costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty2`]
      },
      tax_ty3: {
        center: bodyshop.md_responsibility_centers.taxes[`tax_ty3`].name,
        sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty3Tax`]),
        cost: Dinero(),
        profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty3`],
        costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty3`]
      },
      tax_ty4: {
        center: bodyshop.md_responsibility_centers.taxes[`tax_ty4`].name,
        sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty4Tax`]),
        cost: Dinero(),
        profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty4`],
        costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty4`]
      },
      tax_ty5: {
        center: bodyshop.md_responsibility_centers.taxes[`tax_ty5`].name,
        sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty5Tax`]),
        cost: Dinero(),
        profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`],
        costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`]
      }
    })
  });

  //Determine if there are MAPA and MASH lines already on the estimate.
  //If there are, don't do anything extra (mitchell estimate)
  //Otherwise, calculate them and add them to the default MAPA and MASH centers.
  let hasMapaLine = false;
  let hasMashLine = false;

  const profitCenterHash = job.joblines.reduce((acc, val) => {
    //Check the Parts Assignment
    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;
    }

    if (val.profitcenter_part) {
      if (!acc[val.profitcenter_part]) acc[val.profitcenter_part] = Dinero();

      let DineroAmount = Dinero({
        amount: Math.round(val.act_price * 100)
      }).multiply(val.part_qty || 1);

      DineroAmount = DineroAmount.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()
      );

      acc[val.profitcenter_part] = acc[val.profitcenter_part].add(DineroAmount);
    }
    if (val.profitcenter_labor && val.mod_lbr_ty) {
      //Check the Labor Assignment.

      if (!acc[val.profitcenter_labor]) acc[val.profitcenter_labor] = Dinero();

      acc[val.profitcenter_labor] = acc[val.profitcenter_labor].add(
        Dinero({
          amount: Math.round(job[`rate_${val.mod_lbr_ty.toLowerCase()}`] * 100)
        }).multiply(val.mod_lb_hrs)
      );
    }
    return acc;
  }, {});

  const selectedDmsAllocationConfig = bodyshop.md_responsibility_centers.dms_defaults.find(
    (d) => d.name === job.dms_allocation
  );
  loggingFunction(
    connectionData,
    "DEBUG",
    `Using DMS Allocation ${selectedDmsAllocationConfig && selectedDmsAllocationConfig.name} for cost export.`
  );

  let costCenterHash = {};
  //Check whether to skip this if PBS and using AP module.
  const disablebillwip = !!bodyshop?.pbs_configuration?.disablebillwip;

  if (!disablebillwip) {
    costCenterHash = job.bills.reduce((bill_acc, bill_val) => {
      bill_val.billlines.map((line_val) => {
        if (!bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]])
          bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = Dinero();

        let lineDinero = Dinero({
          amount: Math.round((line_val.actual_cost || 0) * 100)
        })
          .multiply(line_val.quantity)
          .multiply(bill_val.is_credit_memo ? -1 : 1);

        bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] =
          bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]].add(lineDinero);
        return null;
      });
      return bill_acc;
    }, {});
  }

  job.timetickets.forEach((ticket) => {
    //Get the total amount of the ticket.
    let TicketTotal = Dinero({
      amount: Math.round(
        ticket.rate *
          (ticket.employee && ticket.employee.flat_rate ? ticket.productivehrs || 0 : ticket.actualhrs || 0) *
          100
      )
    });
    //Add it to the right cost center.
    if (!costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]])
      costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]] = Dinero();

    costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]] =
      costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]].add(TicketTotal);
  });

  if (!hasMapaLine && job.job_totals.rates.mapa.total.amount > 0) {
    // //console.log("Adding MAPA Line Manually.");
    const mapaAccountName = selectedDmsAllocationConfig.profits.MAPA;

    const mapaAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === mapaAccountName);

    if (mapaAccount) {
      if (!profitCenterHash[mapaAccountName]) profitCenterHash[mapaAccountName] = Dinero();

      profitCenterHash[mapaAccountName] = profitCenterHash[mapaAccountName].add(
        Dinero(job.job_totals.rates.mapa.total)
      );
    } else {
      ////console.log("NO MAPA ACCOUNT FOUND!!");
    }
  }

  if (!hasMashLine && job.job_totals.rates.mash.total.amount > 0) {
    // console.log("Adding MASH Line Manually.");

    const mashAccountName = selectedDmsAllocationConfig.profits.MASH;

    const mashAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === mashAccountName);

    if (mashAccount) {
      if (!profitCenterHash[mashAccountName]) profitCenterHash[mashAccountName] = Dinero();

      profitCenterHash[mashAccountName] = profitCenterHash[mashAccountName].add(
        Dinero(job.job_totals.rates.mash.total)
      );
    } else {
      //  console.log("NO MASH ACCOUNT FOUND!!");
    }
  }
  // console.log(
  //   Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting),
  //   typeof Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting)
  // );
  if (bodyshop?.cdk_configuration?.sendmaterialscosting) {
    //Manually send the percentage of the costing.

    //Paint Mat
    const mapaAccountName = selectedDmsAllocationConfig.costs.MAPA;
    const mapaAccount = bodyshop.md_responsibility_centers.costs.find((c) => c.name === mapaAccountName);
    if (mapaAccount) {
      if (!costCenterHash[mapaAccountName]) costCenterHash[mapaAccountName] = Dinero();
      if (job.bodyshop.use_paint_scale_data === true) {
        if (job.mixdata.length > 0) {
          costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
            Dinero({
              amount: Math.round(((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100)
            })
          );
        } else {
          costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
            Dinero(job.job_totals.rates.mapa.total).percentage(bodyshop?.cdk_configuration?.sendmaterialscosting)
          );
        }
      } else {
        costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
          Dinero(job.job_totals.rates.mapa.total).percentage(bodyshop?.cdk_configuration?.sendmaterialscosting)
        );
      }
    } else {
      //console.log("NO MAPA ACCOUNT FOUND!!");
    }

    //Shop Mat
    const mashAccountName = selectedDmsAllocationConfig.costs.MASH;
    const mashAccount = bodyshop.md_responsibility_centers.costs.find((c) => c.name === mashAccountName);
    if (mashAccount) {
      if (!costCenterHash[mashAccountName]) costCenterHash[mashAccountName] = Dinero();
      costCenterHash[mashAccountName] = costCenterHash[mashAccountName].add(
        Dinero(job.job_totals.rates.mash.total).percentage(bodyshop?.cdk_configuration?.sendmaterialscosting)
      );
    } else {
      //  console.log("NO MASH ACCOUNT FOUND!!");
    }
  }

  const { ca_bc_pvrt } = job;
  if (ca_bc_pvrt) {
    // const pvrtAccount = bodyshop.md_responsibility_centers.profits.find(
    //   (c) => c.name === mashAccountName
    // );

    taxAllocations.state.sale = taxAllocations.state.sale.add(Dinero({ amount: Math.round((ca_bc_pvrt || 0) * 100) }));
  }

  if (job.towing_payable && job.towing_payable !== 0) {
    const towAccountName = selectedDmsAllocationConfig.profits.TOW;

    const towAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === towAccountName);

    if (towAccount) {
      if (!profitCenterHash[towAccountName]) profitCenterHash[towAccountName] = Dinero();

      profitCenterHash[towAccountName] = profitCenterHash[towAccountName].add(
        Dinero({
          amount: Math.round((job.towing_payable || 0) * 100)
        })
      );
    } else {
      //  console.log("NO MASH ACCOUNT FOUND!!");
    }
  }
  if (job.storage_payable && job.storage_payable !== 0) {
    const storageAccountName = selectedDmsAllocationConfig.profits.TOW;

    const towAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === storageAccountName);

    if (towAccount) {
      if (!profitCenterHash[storageAccountName]) profitCenterHash[storageAccountName] = Dinero();

      profitCenterHash[storageAccountName] = profitCenterHash[storageAccountName].add(
        Dinero({
          amount: Math.round((job.storage_payable || 0) * 100)
        })
      );
    } else {
      //  console.log("NO MASH ACCOUNT FOUND!!");
    }
  }

  if (job.adjustment_bottom_line && job.adjustment_bottom_line !== 0) {
    const otherAccountName = selectedDmsAllocationConfig.profits.PAO;

    const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === otherAccountName);

    if (otherAccount) {
      if (!profitCenterHash[otherAccountName]) profitCenterHash[otherAccountName] = Dinero();

      profitCenterHash[otherAccountName] = profitCenterHash[otherAccountName].add(
        Dinero({
          amount: Math.round((job.adjustment_bottom_line || 0) * 100)
        })
      );
    } else {
      //  console.log("NO MASH ACCOUNT FOUND!!");
    }
  }

  if (InstanceManager({ rome: true })) {
    //profile level adjustments for parts
    Object.keys(job.job_totals.parts.adjustments).forEach((key) => {
      const accountName = selectedDmsAllocationConfig.profits[key];
      const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === accountName);
      if (otherAccount) {
        if (!profitCenterHash[accountName]) profitCenterHash[accountName] = Dinero();

        profitCenterHash[accountName] = profitCenterHash[accountName].add(
          Dinero(job.job_totals.parts.adjustments[key])
        );
      } else {
        loggingFunction(
          connectionData,
          "ERROR",
          `Error encountered in CdkCalculateAllocations. Unable to find adjustment account: ${accountName}`
        );
      }
    });
    //profile level adjustments for labor and materials
    Object.keys(job.job_totals.rates).forEach((key) => {
      if (
        job.job_totals.rates[key] &&
        job.job_totals.rates[key].adjustment &&
        Dinero(job.job_totals.rates[key].adjustment).isZero() === false
      ) {
        const accountName = selectedDmsAllocationConfig.profits[key.toUpperCase()];
        const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === accountName);
        if (otherAccount) {
          if (!profitCenterHash[accountName]) profitCenterHash[accountName] = Dinero();

          profitCenterHash[accountName] = profitCenterHash[accountName].add(
            Dinero(job.job_totals.rates[key].adjustments)
          );
        } else {
          loggingFunction(
            connectionData,
            "ERROR",
            `Error encountered in CdkCalculateAllocations. Unable to find adjustment account: ${accountName}`
          );
        }
      }
    });
  }

  const jobAllocations = _.union(Object.keys(profitCenterHash), Object.keys(costCenterHash)).map((key) => {
    const profitCenter = bodyshop.md_responsibility_centers.profits.find((c) => c.name === key);
    const costCenter = bodyshop.md_responsibility_centers.costs.find((c) => c.name === key);

    return {
      center: key,
      sale: profitCenterHash[key] ? profitCenterHash[key] : Dinero(),
      cost: costCenterHash[key] ? costCenterHash[key] : Dinero(),
      profitCenter,
      costCenter
    };
  });

  return [
    ...jobAllocations,
    ...Object.keys(taxAllocations)
      .filter((key) => taxAllocations[key].sale.getAmount() > 0 || taxAllocations[key].cost.getAmount() > 0)
      .map((key) => {
        if (
          key === "federal" &&
          selectedDmsAllocationConfig.gst_override &&
          selectedDmsAllocationConfig.gst_override !== ""
        ) {
          const ret = { ...taxAllocations[key], tax: key };
          ret.costCenter.dms_acctnumber = selectedDmsAllocationConfig.gst_override;
          ret.profitCenter.dms_acctnumber = selectedDmsAllocationConfig.gst_override;
          return ret;
        } else {
          return { ...taxAllocations[key], tax: key };
        }
      }),

    ...(job.job_totals.totals.ttl_adjustment
      ? [
          {
            center: "SUB ADJ",
            sale: Dinero(job.job_totals.totals.ttl_adjustment),
            cost: Dinero(),
            profitCenter: {
              name: "SUB ADJ",
              accountdesc: "SUB ADJ",
              accountitem: "SUB ADJ",
              accountname: "SUB ADJ",
              dms_acctnumber: bodyshop.md_responsibility_centers.ttl_adjustment.dms_acctnumber
            },
            costCenter: {}
          }
        ]
      : []),
    ...(job.job_totals.totals.ttl_tax_adjustment
      ? [
          {
            center: "TAX ADJ",
            sale: Dinero(job.job_totals.totals.ttl_tax_adjustment),
            cost: Dinero(),
            profitCenter: {
              name: "TAX ADJ",
              accountdesc: "TAX ADJ",
              accountitem: "TAX ADJ",
              accountname: "TAX ADJ",
              dms_acctnumber: bodyshop.md_responsibility_centers.ttl_tax_adjustment.dms_acctnumber
            },
            costCenter: {}
          }
        ]
      : [])
  ];
}
