const logger = require("../../utils/logger");
const Dinero = require("dinero.js");

const apiGqlClient = require("../../graphql-client/graphql-client").client;
const queries = require("../../graphql-client/queries");
const { refresh: refreshOauthToken } = require("./qbo-callback");
const OAuthClient = require("intuit-oauth");
const moment = require("moment-timezone");
const {
  QueryInsuranceCo,
  InsertInsuranceCo,
  InsertJob,
  InsertOwner,
  QueryJob,
  QueryOwner
} = require("../qbo/qbo-receivables");
const { urlBuilder } = require("./qbo");
const { DineroQbFormat } = require("../accounting-constants");
const { findTaxCode } = require("../qb-receivables-lines");

exports.default = async (req, res) => {
  const oauthClient = new OAuthClient({
    clientId: process.env.QBO_CLIENT_ID,
    clientSecret: process.env.QBO_SECRET,
    environment: process.env.NODE_ENV === "production" ? "production" : "sandbox",
    redirectUri: process.env.QBO_REDIRECT_URI
  });
  try {
    //Fetch the API Access Tokens & Set them for the session.
    const response = await apiGqlClient.request(queries.GET_QBO_AUTH, {
      email: req.user.email
    });
    const { qbo_realmId } = response.associations[0];
    oauthClient.setToken(response.associations[0].qbo_auth);
    if (!qbo_realmId) {
      res.status(401).json({ error: "No company associated." });
      return;
    }
    await refreshOauthToken(oauthClient, req);

    const { payments: paymentsToQuery, elgen } = req.body;

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

    logger.log("qbo-payment-create", "DEBUG", req.user.email, null, { paymentsToQuery });

    const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
      payments: paymentsToQuery
    });

    const { payments, bodyshops } = result;
    const bodyshop = bodyshops[0];

    const ret = [];

    for (const payment of payments) {
      try {
        let isThreeTier = bodyshop.accountingconfig.tiers === 3;
        let twoTierPref = bodyshop.accountingconfig.twotierpref;

        //Replace this with a for-each loop to check every single Job that's included in the list.

        //QB Multi AR - If it is in this scenario, overwrite whatever defaults are set since multi AR
        //will always go Source => RO
        if (payment.payer !== "Customer" && payment.payer !== "Insurance") {
          payment.job.ins_co_nm = payment.payer;
          twoTierPref = "source";
          isThreeTier = false;
        }

        let insCoCustomerTier, ownerCustomerTier, jobTier;
        if (isThreeTier || (!isThreeTier && twoTierPref === "source")) {
          //Insert the insurance company tier.
          //Query for top level customer, the insurance company name.
          insCoCustomerTier = await QueryInsuranceCo(oauthClient, qbo_realmId, req, payment.job);
          if (!insCoCustomerTier) {
            //Creating the Insurance Customer.
            insCoCustomerTier = await InsertInsuranceCo(oauthClient, qbo_realmId, req, payment.job, bodyshop);
          }
        }

        if (isThreeTier || (!isThreeTier && twoTierPref === "name")) {
          //Insert the name/owner and account for whether the source should be the ins co in 3 tier..
          ownerCustomerTier = await QueryOwner(oauthClient, qbo_realmId, req, payment.job, insCoCustomerTier);
          //Query for the owner itself.
          if (!ownerCustomerTier) {
            ownerCustomerTier = await InsertOwner(
              oauthClient,
              qbo_realmId,
              req,
              payment.job,
              isThreeTier,
              insCoCustomerTier
            );
          }
        }

        //Query for the Job or Create it.
        jobTier = await QueryJob(
          oauthClient,
          qbo_realmId,
          req,
          payment.job,
          isThreeTier ? ownerCustomerTier : twoTierPref === "source" ? insCoCustomerTier : ownerCustomerTier
        );

        // Need to validate that the job tier is associated to the right individual?

        if (!jobTier) {
          jobTier = await InsertJob(oauthClient, qbo_realmId, req, payment.job, ownerCustomerTier || insCoCustomerTier);
        }

        if (payment.amount > 0) {
          await InsertPayment(oauthClient, qbo_realmId, req, payment, jobTier, bodyshop);
        } else {
          await InsertCreditMemo(oauthClient, qbo_realmId, req, payment, jobTier, bodyshop);
        }

        // //No error. Mark the payment exported & insert export log.
        if (elgen) {
          await client.setHeaders({ Authorization: BearerToken }).request(queries.QBO_MARK_PAYMENT_EXPORTED, {
            paymentId: payment.id,
            payment: {
              exportedat: moment().tz(bodyshop.timezone)
            },
            logs: [
              {
                bodyshopid: bodyshop.id,
                paymentid: payment.id,
                successful: true,
                useremail: req.user.email
              }
            ]
          });
        }

        ret.push({ paymentid: payment.id, success: true });
      } catch (error) {
        logger.log("qbo-payment-create-error", "ERROR", req.user.email, null, {
          error: error?.authResponse?.body || error?.message
        });
        //Add the export log error.
        if (elgen) {
          await client.setHeaders({ Authorization: BearerToken }).request(queries.INSERT_EXPORT_LOG, {
            logs: [
              {
                bodyshopid: bodyshop.id,
                paymentid: payment.id,
                successful: false,
                message: JSON.stringify([error?.authResponse?.body || error?.message]),
                useremail: req.user.email
              }
            ]
          });
        }

        ret.push({
          paymentid: payment.id,
          success: false,
          errorMessage: error?.authResponse?.body || error?.message
        });
      }
    }

    res.status(200).json(ret);
  } catch (error) {
    logger.log("qbo-payment-create-error", "ERROR", req.user.email, null, {
      error: error.message,
      stack: error.stack
    });
    res.status(400).json(error);
  }
};

async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef) {
  const { paymentMethods, invoices } = await QueryMetaData(
    oauthClient,
    qbo_realmId,
    req,
    payment.job.ro_number,
    false,
    parentRef,
    payment.job.shopid
  );

  if (invoices && invoices.length !== 1) {
    throw new Error(`More than 1 invoice with DocNumber ${payment.job.ro_number} found.`);
  }

  const paymentQbo = {
    CustomerRef: {
      value: parentRef.Id
    },
    TxnDate: moment(payment.date).format("YYYY-MM-DD"),
    DocNumber: payment.paymentnum,
    TotalAmt: Dinero({
      amount: Math.round(payment.amount * 100)
    }).toFormat(DineroQbFormat),
    PaymentMethodRef: {
      value: paymentMethods[payment.type]
    },
    PrivateNote: payment.memo?.substring(0, 4000)?.trim() ?? "",
    PaymentRefNum: payment.transactionid,
    ...(invoices?.length === 1 && invoices[0]
      ? {
          Line: [
            {
              Amount: Dinero({ amount: Math.round(payment.amount * 100) }).toFormat(DineroQbFormat),
              LinkedTxn: [
                {
                  TxnId: invoices[0].Id,
                  TxnType: "Invoice"
                }
              ]
            }
          ]
        }
      : {})
  };
  logger.log("qbo-payments-objectlog", "DEBUG", req.user.email, payment.id, {
    paymentQbo
  });
  try {
    const url = urlBuilder(qbo_realmId, "payment");
    const result = await oauthClient.makeApiCall({
      url: url,
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(paymentQbo)
    });
    logger.LogIntegrationCall({
      platform: "QBO",
      method: "POST",
      name: "InsertPayment",
      paymentid: payment.id,
      status: result.status,
      bodyshopid: payment.job.shopid,
      email: req.user.email
    });
    logger.log("qbo-payments-insert", "DEBUG", req.user.email, null, {
      method: "InsertPayment",
      call: url,
      paymentQbo: paymentQbo,
      result: result.json
    });

    if (result.status >= 400) {
      throw new Error(JSON.stringify(result.json.Fault));
    }
    if (result.status === 200) return result?.json.Customer;
  } catch (error) {
    logger.log("qbo-payables-error", "DEBUG", req.user.email, payment.id, {
      method: "InsertPayment",
      validationError: error.message,
      stack: error.stack
    });
    throw error;
  }
}

async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditMemo, parentTierRef, bodyshopid) {
  const invoice = await oauthClient.makeApiCall({
    url: urlBuilder(qbo_realmId, "query", `select * From Invoice where DocNumber like '${ro_number}%'`),
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    }
  });
  logger.LogIntegrationCall({
    platform: "QBO",
    method: "POST",
    name: "QueryInvoice",
    status: invoice.status,
    bodyshopid,
    email: req.user.email
  });
  const paymentMethods = await oauthClient.makeApiCall({
    url: urlBuilder(qbo_realmId, "query", `select * From PaymentMethod`),
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    }
  });
  logger.LogIntegrationCall({
    platform: "QBO",
    method: "POST",
    name: "QueryPaymentMethod",
    status: paymentMethods.status,
    bodyshopid,
    email: req.user.email
  });

  //   const classes = await oauthClient.makeApiCall({
  //     url: urlBuilder(qbo_realmId, "query", `select * From Class`),
  //     method: "POST",
  //     headers: {
  //       "Content-Type": "application/json",
  //     },
  //   });

  const paymentMethodMapping = Object.fromEntries(
    (paymentMethods.json?.QueryResponse?.PaymentMethod || []).map((t) => [t.Name, t.Id])
  );

  //   const accountMapping = {};

  //   accounts.json &&
  //     accounts.json.QueryResponse &&
  //     accounts.json.QueryResponse.Account.forEach((t) => {
  //       accountMapping[t.Name] = t.Id;
  //     });

  //   const classMapping = {};
  //   classes.json &&
  //     classes.json.QueryResponse &&
  //     classes.json.QueryResponse.Class.forEach((t) => {
  //       accountMapping[t.Name] = t.Id;
  //     });
  let ret = {};

  if (isCreditMemo) {
    const taxCodes = await oauthClient.makeApiCall({
      url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`),
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      }
    });
    logger.LogIntegrationCall({
      platform: "QBO",
      method: "POST",
      name: "QueryTaxCode",
      status: taxCodes.status,
      bodyshopid,
      email: req.user.email
    });
    const items = await oauthClient.makeApiCall({
      url: urlBuilder(qbo_realmId, "query", `select * From Item`),
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      }
    });
    logger.LogIntegrationCall({
      platform: "QBO",
      method: "POST",
      name: "QueryItems",
      status: items.status,
      bodyshopid,
      email: req.user.email
    });

    const taxCodeMapping = Object.fromEntries((taxCodes.json?.QueryResponse?.TaxCode || []).map((t) => [t.Name, t.Id]));

    const itemMapping = Object.fromEntries((items.json?.QueryResponse?.Item || []).map((item) => [item.Name, item.Id]));

    ret = {
      ...ret,
      items: itemMapping,
      taxCodes: taxCodeMapping
    };
  }

  return {
    ...ret,
    paymentMethods: paymentMethodMapping,
    invoices:
      invoice.json?.QueryResponse?.Invoice &&
      (parentTierRef
        ? [invoice.json?.QueryResponse?.Invoice.find((x) => x.CustomerRef?.value === parentTierRef?.Id)]
        : [invoice.json?.QueryResponse?.Invoice?.[0]])
  };
}

async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRef) {
  const { invoices, items, taxCodes } = await QueryMetaData(
    oauthClient,
    qbo_realmId,
    req,
    payment.job.ro_number,
    true,
    parentRef,
    payment.job.shopid
  );

  if (invoices?.length !== 1) {
    throw new Error(`More than 1 invoice with DocNumber ${payment.ro_number} found.`);
  }

  const paymentQbo = {
    CustomerRef: {
      value: parentRef.Id
    },
    TxnDate: moment(payment.date).format("YYYY-MM-DD"),
    DocNumber: payment.paymentnum,
    ...(invoices?.[0] ? { InvoiceRef: { value: invoices[0].Id } } : {}),
    PaymentRefNum: payment.transactionid,
    Line: [
      {
        DetailType: "SalesItemLineDetail",
        Amount: Dinero({ amount: Math.round(payment.amount * -100) }).toFormat(DineroQbFormat),
        SalesItemLineDetail: {
          ItemRef: {
            value: items[payment.job.bodyshop.md_responsibility_centers.refund.accountitem]
          },
          Qty: 1,
          TaxCodeRef: {
            value:
              taxCodes[
                findTaxCode(
                  {
                    local: false,
                    federal: false,
                    state: false
                  },
                  payment.job.bodyshop.md_responsibility_centers.sales_tax_codes
                )
              ]
          }
        }
      }
    ]
  };
  logger.log("qbo-payments-objectlog", "DEBUG", req.user.email, payment.id, {
    paymentQbo
  });
  try {
    const url = urlBuilder(qbo_realmId, "creditmemo");
    const result = await oauthClient.makeApiCall({
      url: url,
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(paymentQbo)
    });
    logger.LogIntegrationCall({
      platform: "QBO",
      method: "POST",
      name: "InsertCreditMemo",
      paymentid: payment.id,
      status: result.status,
      bodyshopid: req.user.bodyshopid,
      email: req.user.email
    });
    logger.log("qbo-metadata-query", "DEBUG", req.user.email, null, {
      method: "InsertCreditMemo",
      call: url,
      paymentQbo: paymentQbo,
      result: result.json
    });

    if (result.status >= 400) {
      throw new Error(JSON.stringify(result.json.Fault));
    }
    if (result.status === 200) return result?.json;
  } catch (error) {
    logger.log("qbo-payables-error", "DEBUG", req.user.email, payment.id, {
      method: "InsertCreditMemo",
      validationError: error.message,
      accountmeta: JSON.stringify({ items, taxCodes }),
      stack: error.stack
    });
    throw error;
  }
}
