import { useApolloClient, useMutation } from "@apollo/client/react";
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
import { Button, Checkbox, Divider, Form, Modal, Space } from "antd";
import _ from "lodash";
import { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { INSERT_NEW_BILL } from "../../graphql/bills.queries";
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
import { QUERY_JOB_LBR_ADJUSTMENTS, UPDATE_JOB } from "../../graphql/jobs.queries";
import { MUTATION_MARK_RETURN_RECEIVED } from "../../graphql/parts-orders.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import confirmDialog from "../../utils/asyncConfirm";
import useLocalStorage from "../../utils/useLocalStorage";
import BillEnterAiScan from "../bill-enter-ai-scan/bill-enter-ai-scan.component.jsx";
import BillFormContainer from "../bill-form/bill-form.container";
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
import { handleUpload as handleUploadToImageProxy } from "../documents-upload-imgproxy/documents-upload-imgproxy.utility";
import { handleUpload } from "../documents-upload/documents-upload.utility";
import BillAiFeedback from "../bill-ai-feedback/bill-ai-feedback.component.jsx";

const mapStateToProps = createStructuredSelector({
  billEnterModal: selectBillEnterModal,
  bodyshop: selectBodyshop,
  currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
  toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
  insertAuditTrail: ({ jobid, billid, operation, type }) =>
    dispatch(insertAuditTrail({ jobid, billid, operation, type }))
});

const Templates = TemplateList("job_special");

function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop, currentUser, insertAuditTrail }) {
  const [form] = Form.useForm();
  const { t } = useTranslation();
  const [enterAgain, setEnterAgain] = useState(false);
  const [insertBill] = useMutation(INSERT_NEW_BILL);
  const [updateJobLines] = useMutation(UPDATE_JOB_LINE);
  const [updatePartsOrderLines] = useMutation(MUTATION_MARK_RETURN_RECEIVED);
  const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES);
  const [loading, setLoading] = useState(false);
  const [scanLoading, setScanLoading] = useState(false);
  const [isAiScan, setIsAiScan] = useState(false);
  const [rawAIData, setRawAIData] = useState(null);
  const client = useApolloClient();
  const [generateLabel, setGenerateLabel] = useLocalStorage("enter_bill_generate_label", false);
  const notification = useNotification();
  const fileInputRef = useRef(null);
  const pollingIntervalRef = useRef(null);
  const formTopRef = useRef(null);

  const {
    treatments: { Enhanced_Payroll, Imgproxy, Bill_OCR_AI }
  } = useTreatmentsWithConfig({
    attributes: {},
    names: ["Enhanced_Payroll", "Imgproxy", "Bill_OCR_AI"],
    splitKey: bodyshop.imexshopid
  });

  const formValues = useMemo(() => {
    return {
      ...billEnterModal.context.bill,
      //Added as a part of IO-2436 for capturing parts price changes.
      billlines: billEnterModal.context?.bill?.billlines?.map((line) => ({
        ...line,
        original_actual_price: line.actual_price
      })),
      jobid: (billEnterModal.context.job && billEnterModal.context.job.id) || null,
      federal_tax_rate: (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.federal_tax_rate) || 0,
      state_tax_rate: (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.state_tax_rate) || 0,
      local_tax_rate: (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.local_tax_rate) || 0
    };
  }, [billEnterModal, bodyshop]);

  const handleFinish = async (values) => {
    let totals = CalculateBillTotal(values);
    if (totals.discrepancy.getAmount() !== 0) {
      if (!(await confirmDialog(t("bills.labels.savewithdiscrepancy")))) {
        return;
      }
    }

    setLoading(true);

    // eslint-disable-next-line no-unused-vars
    const { upload, location, outstanding_returns, inventory, federal_tax_exempt, ...remainingValues } = values;

    let adjustmentsToInsert = {};
    let payrollAdjustmentsToInsert = [];

    const r1 = await insertBill({
      variables: {
        bill: [
          {
            ...remainingValues,
            billlines: {
              data:
                remainingValues.billlines &&
                remainingValues.billlines.map((i) => {
                  const {
                    deductedfromlbr,
                    lbr_adjustment,
                    // eslint-disable-next-line no-unused-vars
                    location: lineLocation,
                    // eslint-disable-next-line no-unused-vars
                    part_type,
                    // eslint-disable-next-line no-unused-vars
                    create_ppc,
                    // eslint-disable-next-line no-unused-vars
                    original_actual_price,
                    // eslint-disable-next-line no-unused-vars
                    confidence,
                    ...restI
                  } = i;

                  if (Enhanced_Payroll.treatment === "on") {
                    if (
                      deductedfromlbr &&
                      true //payroll is on
                    ) {
                      payrollAdjustmentsToInsert.push({
                        id: i.joblineid,
                        convertedtolbr: true,
                        convertedtolbr_data: {
                          mod_lb_hrs: lbr_adjustment.mod_lb_hrs * -1,
                          mod_lbr_ty: lbr_adjustment.mod_lbr_ty
                        }
                      });
                    }
                  } else {
                    if (deductedfromlbr) {
                      adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] =
                        (adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) -
                        restI.actual_price / lbr_adjustment.rate;
                    }
                  }

                  return {
                    ...restI,
                    deductedfromlbr: deductedfromlbr,
                    lbr_adjustment,
                    joblineid: i.joblineid === "noline" ? null : i.joblineid,
                    applicable_taxes: {
                      federal: (i.applicable_taxes && i.applicable_taxes.federal) || false,
                      state: (i.applicable_taxes && i.applicable_taxes.state) || false,
                      local: (i.applicable_taxes && i.applicable_taxes.local) || false
                    }
                  };
                })
            }
          }
        ]
      },
      refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"],
      awaitRefetchQueries: true
    });

    await Promise.all(
      payrollAdjustmentsToInsert.map((li) => {
        return updateJobLines({
          variables: {
            lineId: li.id,
            line: {
              convertedtolbr: li.convertedtolbr,
              convertedtolbr_data: li.convertedtolbr_data
            }
          }
        });
      })
    );

    const adjKeys = Object.keys(adjustmentsToInsert);
    if (adjKeys.length > 0) {
      //Query the adjustments, merge, and update them.
      const existingAdjustments = await client.query({
        query: QUERY_JOB_LBR_ADJUSTMENTS,
        variables: {
          id: values.jobid
        }
      });

      const newAdjustments = _.cloneDeep(existingAdjustments.data.jobs_by_pk.lbr_adjustments);

      adjKeys.forEach((key) => {
        newAdjustments[key] = (newAdjustments[key] || 0) + adjustmentsToInsert[key];

        insertAuditTrail({
          jobid: values.jobid,
          operation: AuditTrailMapping.jobmodifylbradj({
            mod_lbr_ty: key,
            hours: adjustmentsToInsert[key].toFixed(1)
          }),
          type: "jobmodifylbradj"
        });
      });

      const jobUpdate = client.mutate({
        mutation: UPDATE_JOB,
        variables: {
          jobId: values.jobid,
          job: { lbr_adjustments: newAdjustments }
        }
      });
      if (jobUpdate.errors) {
        notification.error({
          title: t("jobs.errors.saving", {
            message: JSON.stringify(jobUpdate.errors)
          })
        });
        return;
      }
    }

    const markPolReceived = outstanding_returns && outstanding_returns.filter((o) => o.cm_received === true);

    if (markPolReceived && markPolReceived.length > 0) {
      const r2 = await updatePartsOrderLines({
        variables: { partsLineIds: markPolReceived.map((p) => p.id) },
        refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"]
      });
      if (r2.errors) {
        setLoading(false);
        setEnterAgain(false);
        notification.error({
          title: t("parts_orders.errors.updating", {
            message: JSON.stringify(r2.errors)
          })
        });
      }
    }

    if (r1.errors) {
      setLoading(false);
      setEnterAgain(false);
      notification.error({
        title: t("bills.errors.creating", {
          message: JSON.stringify(r1.errors)
        })
      });
    }

    const billId = r1.data.insert_bills.returning[0].id;
    const markInventoryConsumed = inventory && inventory.filter((i) => i.consumefrominventory);

    if (markInventoryConsumed && markInventoryConsumed.length > 0) {
      const r2 = await updateInventoryLines({
        variables: {
          InventoryIds: markInventoryConsumed.map((p) => p.id),
          consumedbybillid: billId
        }
      });
      if (r2.errors) {
        setLoading(false);
        setEnterAgain(false);
        notification.error({
          title: t("inventory.errors.updating", {
            message: JSON.stringify(r2.errors)
          })
        });
      }
    }
    //If it's not a credit memo, update the statuses.

    if (!values.is_credit_memo) {
      await Promise.all(
        remainingValues.billlines
          .filter((il) => il.joblineid !== "noline")
          .map((li) => {
            return updateJobLines({
              variables: {
                lineId: li.joblineid,
                line: {
                  location: li.location || location,
                  status: bodyshop.md_order_statuses.default_received || "Received*",
                  //Added parts price changes.
                  ...(li.create_ppc && li.original_actual_price !== li.actual_price
                    ? {
                        act_price_before_ppc: li.original_actual_price,
                        act_price: li.actual_price
                      }
                    : {})
                }
              }
            });
          })
      );
    }

    /////////////////////////
    if (upload && upload.length > 0) {
      //insert Each of the documents?

      if (bodyshop.uselocalmediaserver) {
        upload.forEach((u) => {
          handleLocalUpload({
            ev: { file: u.originFileObj },
            context: {
              jobid: values.jobid,
              invoice_number: remainingValues.invoice_number,
              vendorid: remainingValues.vendorid
            },
            notification
          });
        });
      } else {
        //Check if using Imgproxy or cloudinary

        if (Imgproxy.treatment === "on") {
          upload.forEach((u) => {
            handleUploadToImageProxy(
              { file: u.originFileObj },
              {
                bodyshop: bodyshop,
                uploaded_by: currentUser.email,
                jobId: values.jobid,
                billId: billId,
                tagsArray: null,
                callback: null
              },
              notification
            );
          });
        } else {
          upload.forEach((u) => {
            handleUpload(
              { file: u.originFileObj },
              {
                bodyshop: bodyshop,
                uploaded_by: currentUser.email,
                jobId: values.jobid,
                billId: billId,
                tagsArray: null,
                callback: null
              },
              notification
            );
          });
        }
      }
    }
    ///////////////////////////
    setLoading(false);
    notification.success({
      title: t("bills.successes.created")
    });

    if (generateLabel) {
      GenerateDocument(
        {
          name: Templates.parts_invoice_label_single.key,
          variables: {
            id: billId
          }
        },
        {},
        "p",
        null,
        notification
      );
    }

    if (billEnterModal.actions.refetch) billEnterModal.actions.refetch();

    insertAuditTrail({
      jobid: values.jobid,
      billid: billId,
      operation: AuditTrailMapping.billposted(r1.data.insert_bills.returning[0].invoice_number),
      type: "billposted"
    });

    if (enterAgain) {
      form.resetFields();
      form.setFieldsValue({
        ...formValues,
        vendorid: values.vendorid,
        billlines: []
      });
      setIsAiScan(false);
      setRawAIData(null);
      // form.resetFields();
    } else {
      toggleModalVisible();
    }
    setEnterAgain(false);
  };

  const handleCancel = () => {
    const r = window.confirm(t("general.labels.cancel"));
    if (r === true) {
      // Clean up polling on cancel
      if (pollingIntervalRef.current) {
        clearInterval(pollingIntervalRef.current);
        pollingIntervalRef.current = null;
      }
      setScanLoading(false);
      setIsAiScan(false);
      setRawAIData(null);
      toggleModalVisible();
    }
  };

  //Workaround needed to bypass react-compiler error about manipulating refs in child components. Refactor may be needed in the future to clean this up.
  const setPollingIntervalRef = (func) => {
    pollingIntervalRef.current = func;
  };

  useEffect(() => {
    if (enterAgain) form.submit();
  }, [enterAgain, form]);

  useEffect(() => {
    if (billEnterModal.open) {
      form.setFieldsValue(formValues);
    } else {
      form.resetFields();
      // Clean up polling on modal close
      if (pollingIntervalRef.current) {
        clearInterval(pollingIntervalRef.current);
        pollingIntervalRef.current = null;
      }
      setScanLoading(false);
      setIsAiScan(false);
      setRawAIData(null);
    }
  }, [billEnterModal.open, form, formValues]);

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      if (pollingIntervalRef.current) {
        clearInterval(pollingIntervalRef.current);
        pollingIntervalRef.current = null;
      }
    };
  }, []);

  return (
    <Modal
      title={
        <Space size="large">
          {t("bills.labels.new")}
          {Bill_OCR_AI.treatment === "on" && (
            <BillEnterAiScan
              fileInputRef={fileInputRef}
              form={form}
              pollingIntervalRef={pollingIntervalRef}
              setPollingIntervalRef={setPollingIntervalRef}
              scanLoading={scanLoading}
              setScanLoading={setScanLoading}
              setIsAiScan={setIsAiScan}
              setRawAIData={setRawAIData}
            />
          )}
        </Space>
      }
      width={"98%"}
      open={billEnterModal.open}
      okText={t("general.actions.save")}
      keyboard="false"
      onOk={() => form.submit()}
      onCancel={handleCancel}
      afterClose={() => {
        form.resetFields();
        setLoading(false);
      }}
      footer={
        <Space orientation="vertical">
          {isAiScan && (
            <>
              <BillAiFeedback billForm={form} rawAIData={rawAIData} />
              <Divider orientation="horizontal" />
            </>
          )}
          <Space wrap align="top">
            <Checkbox checked={generateLabel} onChange={(e) => setGenerateLabel(e.target.checked)}>
              {t("bills.labels.generatepartslabel")}
            </Checkbox>
            <Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
            <Button loading={loading} onClick={() => form.submit()} id="save-bill-enter-modal">
              {t("general.actions.save")}
            </Button>
            {billEnterModal.context && billEnterModal.context.id ? null : (
              <Button
                type="primary"
                loading={loading}
                onClick={() => {
                  setEnterAgain(true);
                }}
                id="save-and-new-bill-enter-modal"
              >
                {t("general.actions.saveandnew")}
              </Button>
            )}
          </Space>
        </Space>
      }
      destroyOnHidden
    >
      <Form
        onFinish={handleFinish}
        autoComplete={"off"}
        layout="vertical"
        form={form}
        onFinishFailed={(errorInfo) => {
          setEnterAgain(false);
          // Scroll to the top of the form to show validation errors
          if (errorInfo.errorFields && errorInfo.errorFields.length > 0) {
            setTimeout(() => {
              formTopRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
            }, 100);
          }
        }}
      >
        <div ref={formTopRef}>
          <RbacWrapper action="bills:enter">
            <BillFormContainer
              form={form}
              isAiScan={isAiScan}
              disableInvNumber={billEnterModal.context.disableInvNumber}
            />
          </RbacWrapper>
        </div>
      </Form>
    </Modal>
  );
}

export default connect(mapStateToProps, mapDispatchToProps)(BillEnterModalContainer);
