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

const queries = require("../../graphql-client/queries");
const WsLogger = require("../../web-sockets/createLogEvent");
const moment = require("moment");
const Dinero = require("dinero.js");
const AxiosLib = require("axios").default;
const axios = AxiosLib.create();
const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants");
const { CheckForErrors } = require("./pbs-job-export");

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

  const headers = {
    ...x.headers.common,
    ...x.headers[x.method],
    ...x.headers
  };

  const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
    x.url
  } | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
  //console.log(printable);

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

  return x;
});

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

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

  return x;
});

async function PbsCalculateAllocationsAp(socket, billids) {
  try {
    WsLogger.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${billids}`);
    const { bills, bodyshops } = await QueryBillData(socket, billids);
    const bodyshop = bodyshops[0];
    socket.bodyshop = bodyshop;
    socket.bills = bills;

    //Each bill will enter it's own top level transaction.

    const transactionlist = [];
    if (bills.length === 0) {
      WsLogger.createLogEvent(
        socket,
        "ERROR",
        `No bills found for export. Ensure they have not already been exported and try again.`
      );
    }
    bills.forEach((bill) => {
      //Keep the allocations at the bill level.

      const transactionObject = {
        SerialNumber: socket.bodyshop.pbs_serialnumber,
        billid: bill.id,
        Posting: {
          Reference: bill.invoice_number,
          JournalCode: socket.txEnvelope ? socket.txEnvelope.journal : null,
          TransactionDate: moment().tz(socket.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z",
          //Description: "Bulk AP posting.",
          //AdditionalInfo: "String",
          Source: "ImEX Online", //TODO:AIO Resolve this for rome online.
          Lines: [] //socket.apAllocations,
        }
      };

      const billHash = {
        [bodyshop.md_responsibility_centers.taxes.federal_itc.name]: {
          Account: bodyshop.md_responsibility_centers.taxes.federal_itc.dms_acctnumber,
          ControlNumber: bill.vendor.dmsid,
          Amount: Dinero(),
          // Comment: "String",
          AdditionalInfo: bill.vendor.name,
          InvoiceNumber: bill.invoice_number,
          InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString()
        },
        [bodyshop.md_responsibility_centers.taxes.state.name]: {
          Account: bodyshop.md_responsibility_centers.taxes.state.dms_acctnumber,
          ControlNumber: bill.vendor.dmsid,
          Amount: Dinero(),
          // Comment: "String",
          AdditionalInfo: bill.vendor.name,
          InvoiceNumber: bill.invoice_number,
          InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString()
        }
      };

      bill.billlines.forEach((bl) => {
        let lineDinero = Dinero({
          amount: Math.round((bl.actual_cost || 0) * 100)
        })
          .multiply(bl.quantity)
          .multiply(bill.is_credit_memo ? -1 : 1);
        const cc = getCostAccount(bl, bodyshop.md_responsibility_centers);

        if (!billHash[cc.name]) {
          billHash[cc.name] = {
            Account: bodyshop.pbs_configuration.appostingaccount === "wip" ? cc.dms_wip_acctnumber : cc.dms_acctnumber,
            ControlNumber: bodyshop.pbs_configuration.apcontrol === "ro" ? bill.job.ro_number : bill.vendor.dmsid,
            Amount: Dinero(),
            // Comment: "String",
            AdditionalInfo: bill.vendor.name,
            InvoiceNumber: bill.invoice_number,
            InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString()
          };
        }

        //Add the line amount.
        billHash[cc.name] = {
          ...billHash[cc.name],
          Amount: billHash[cc.name].Amount.add(lineDinero)
        };

        //Does the line have taxes?
        if (bl.applicable_taxes.federal) {
          billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name] = {
            ...billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name],
            Amount: billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name].Amount.add(
              lineDinero.percentage(bill.federal_tax_rate || 0)
            )
          };
        }
        if (bl.applicable_taxes.state) {
          billHash[bodyshop.md_responsibility_centers.taxes.state.name] = {
            ...billHash[bodyshop.md_responsibility_centers.taxes.state.name],
            Amount: billHash[bodyshop.md_responsibility_centers.taxes.state.name].Amount.add(
              lineDinero.percentage(bill.state_tax_rate || 0)
            )
          };
        }
        //End tax check
      });

      let APAmount = Dinero();
      Object.keys(billHash).map((key) => {
        if (billHash[key].Amount.getAmount() > 0 || billHash[key].Amount.getAmount() < 0) {
          transactionObject.Posting.Lines.push({
            ...billHash[key],
            Amount: billHash[key].Amount.toFormat("0.00")
          });
          //Calculate the total expense for the bill iteratively to
          // create the corresponding credit to AP.
          APAmount = APAmount.add(billHash[key].Amount);
        }
      });

      transactionObject.Posting.Lines.push({
        Account: bodyshop.md_responsibility_centers.ap.dms_acctnumber,
        ControlNumber: bill.vendor.dmsid,
        Amount: APAmount.multiply(-1).toFormat("0.00"),
        // Comment: "String",
        AdditionalInfo: bill.vendor.name,
        InvoiceNumber: bill.invoice_number,
        InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString()
      });

      transactionlist.push(transactionObject);
    });

    return transactionlist;
  } catch (error) {
    WsLogger.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`);
  }
}

exports.PbsCalculateAllocationsAp = PbsCalculateAllocationsAp;

async function QueryBillData(socket, billids) {
  WsLogger.createLogEvent(socket, "DEBUG", `Querying bill data for id(s) ${billids}`);
  const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});

  const result = await client
    .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
    .request(queries.GET_PBS_AP_ALLOCATIONS, { billids: billids });

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

  return result;
}

//@returns the account object.
function getCostAccount(billline, respcenters) {
  if (!billline.cost_center) return null;

  const acctName = respcenters.defaults.costs[billline.cost_center];

  return respcenters.costs.find((c) => c.name === acctName);
}

async function MarkApExported(socket, billids) {
  WsLogger.createLogEvent(socket, "DEBUG", `Marking bills as exported for id ${billids}`);
  const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});

  const result = await client
    .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
    .request(queries.MARK_BILLS_EXPORTED, {
      billids,
      bill: {
        exported: true,
        exported_at: new Date()
      },
      logs: socket.bills.map((bill) => ({
        bodyshopid: socket.bodyshop.id,
        billid: bill.id,
        successful: true,
        useremail: socket.user.email
      }))
    });

  return result;
}

const defaultHandler = async (socket, { billids, txEnvelope }) => {
  WsLogger.createLogEvent(socket, "DEBUG", `Exporting selected AP.`);

  //apAllocations has the same shap as the lines key for the accounting posting to PBS.
  socket.apAllocations = await PbsCalculateAllocationsAp(socket, billids);
  socket.txEnvelope = txEnvelope;
  for (const allocation of socket.apAllocations) {
    const { billid, ...restAllocation } = allocation;
    const { data: AccountPostingChange } = await axios.post(PBS_ENDPOINTS.AccountingPostingChange, restAllocation, {
      auth: PBS_CREDENTIALS,
      socket
    });

    CheckForErrors(socket, AccountPostingChange);

    if (AccountPostingChange.WasSuccessful) {
      WsLogger.createLogEvent(socket, "DEBUG", `Marking bill as exported.`);
      await MarkApExported(socket, [billid]);

      socket.emit("ap-export-success", billid);
    } else {
      WsLogger.createLogEvent(socket, "ERROR", `Export was not successful.`);
      socket.emit("ap-export-failure", {
        billid,
        error: AccountPostingChange.Message
      });
    }
  }
  socket.emit("ap-export-complete");
};

exports.PbsExportAp = defaultHandler;
