import { CopyFilled, DeleteFilled } from "@ant-design/icons";
import { useLazyQuery, useMutation } from "@apollo/client/react";
import { Button, Card, Col, Form, Input, message, Row, Space, Spin, Statistic } from "antd";
import axios from "axios";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_PAYMENT_RESPONSE, QUERY_RO_AND_OWNER_BY_JOB_PKS } from "../../graphql/payment_response.queries";
import { getCurrentUser, logImEXEvent } from "../../firebase/firebase.utils";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectCardPayment } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";

const mapStateToProps = createStructuredSelector({
  cardPaymentModal: selectCardPayment,
  bodyshop: selectBodyshop,
  currentUser: getCurrentUser
});

const mapDispatchToProps = (dispatch) => ({
  insertAuditTrail: ({ jobid, operation, type }) =>
    dispatch(
      insertAuditTrail({
        jobid,
        operation,
        type
      })
    ),
  toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
});

const CardPaymentModalComponent = ({
  bodyshop,
  currentUser,
  cardPaymentModal,
  toggleModalVisible,
  insertAuditTrail
}) => {
  const { context, actions } = cardPaymentModal;

  const [form] = Form.useForm();
  const [paymentLink, setPaymentLink] = useState();
  const isMountedRef = useRef(true);

  const [loading, setLoading] = useState(false);
  const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
  const { t } = useTranslation();
  const notification = useNotification();

  const [loadRoAndOwnerByJobPks, { data, loading: queryLoading, error: queryError, refetch, called }] = useLazyQuery(
    QUERY_RO_AND_OWNER_BY_JOB_PKS,
    {
      fetchPolicy: "network-only",
    }
  );

  const safeRefetchRoAndOwner = useCallback(
    (vars) => {
      // First run: execute the lazy query
      if (!called) return loadRoAndOwnerByJobPks({ variables: vars });
      // Subsequent runs: refetch expects the variables object directly (not { variables: ... })
      return refetch(vars);
    },
    [called, loadRoAndOwnerByJobPks, refetch]
  );

  useEffect(() => {
    return () => {
      isMountedRef.current = false;
    };
  }, []);

  const setLoadingSafe = useCallback((value) => {
    if (isMountedRef.current) setLoading(value);
  }, []);

  // Watch form payments so we can query jobs when all jobids are filled (without side effects during render)
  const payments = Form.useWatch(["payments"], form);

  const jobids = useMemo(() => {
    if (!Array.isArray(payments) || payments.length === 0) return [];
    return payments.map((p) => p?.jobid).filter(Boolean);
  }, [payments]);

  const allJobIdsFilled = useMemo(() => {
    if (!Array.isArray(payments) || payments.length === 0) return false;
    return payments.every((p) => !!p?.jobid);
  }, [payments]);

  const lastJobidsKeyRef = useRef("");

  useEffect(() => {
    if (!allJobIdsFilled) return;

    const nextKey = jobids.join("|");
    if (!nextKey || nextKey === lastJobidsKeyRef.current) return;

    lastJobidsKeyRef.current = nextKey;
    safeRefetchRoAndOwner({ jobids });
  }, [allJobIdsFilled, jobids, safeRefetchRoAndOwner]);

  const collectIPayFields = () => {
    const iPayFields = document.querySelectorAll(".ipayfield");
    const iPayData = {};
    iPayFields.forEach((field) => {
      iPayData[field.dataset.ipayname] = field.value;
    });
    return iPayData;
  };

  const SetIntellipayCallbackFunctions = () => {
    console.log("*** Set IntelliPay callback functions.");

    const isLikelyUserCancel = (response) => {
      const reason = String(response?.declinereason ?? "").toLowerCase();
      // Heuristics: adjust if IntelliPay gives you a known cancel code/message
      return (
        reason.includes("cancel") ||
        reason.includes("canceled") ||
        reason.includes("closed") ||
        // many gateways won't have a paymentid if user cancels before submitting
        !response?.paymentid
      );
    };

    window.intellipay.runOnClose(() => {
      // This is the path for Cancel / X
      try {
        // If IntelliPay uses this flag, clear it so initialize() won't reopen unexpectedly
        window.intellipay.isAutoOpen = false;
      } catch {
        // ignore
      }

      // Optional: if IntelliPay needs re-init after close, uncomment:
      // try { window.intellipay.initialize?.(); } catch {}

      setLoadingSafe(false);
    });

    window.intellipay.runOnApproval(() => {
      // keep your existing behavior
      setTimeout(() => {
        if (actions?.refetch) actions.refetch();
        setLoadingSafe(false);
        toggleModalVisible();
      }, 750);
    });

    window.intellipay.runOnNonApproval(async (response) => {
      try {
        // If cancel is reported as "non-approval", don't record it as a failed payment
        if (isLikelyUserCancel(response)) return;

        const { payments } = form.getFieldsValue();

        await insertPaymentResponse({
          variables: {
            paymentResponse: payments.map((payment) => ({
              amount: payment.amount,
              bodyshopid: bodyshop.id,
              jobid: payment.jobid,
              declinereason: response.declinereason,
              ext_paymentid: response.paymentid?.toString?.() ?? null,
              successful: false,
              response
            }))
          }
        });

        payments.forEach((payment) =>
          insertAuditTrail({
            jobid: payment.jobid,
            operation: AuditTrailMapping.failedpayment(),
            type: "failedpayment"
          })
        );
      } finally {
        // IMPORTANT: always clear loading, even on errors
        setLoadingSafe(false);
      }
    });
  };

  const handleIntelliPayCharge = async () => {
    setLoading(true);
    // Validate
    try {
      await form.validateFields();
    } catch {
      setLoadingSafe(false);
      return;
    }

    const iPayData = collectIPayFields();
    const { payments } = form.getFieldsValue();

    try {
      logImEXEvent("payment_cc_lightbox");
      const response = await axios.post("/intellipay/lightbox_credentials", {
        bodyshop,
        refresh: !!window.intellipay,
        paymentSplitMeta: form.getFieldsValue(),
        iPayData: iPayData,
        comment: btoa(JSON.stringify({ payments, userEmail: currentUser.email }))
      });

      if (window.intellipay) {
        // Use Function constructor instead of eval for security (still executes dynamic code but safer)
        // IntelliPay provides initialization code that must be executed
        Function(response.data)();
        pollForIntelliPay(() => {
          SetIntellipayCallbackFunctions();
          window.intellipay.autoOpen();
        });
      } else {
        const rg = document.createRange();
        const node = rg.createContextualFragment(response.data);
        document.documentElement.appendChild(node);
        pollForIntelliPay(() => {
          SetIntellipayCallbackFunctions();

          window.intellipay.isAutoOpen = true;
          window.intellipay.initialize();
        });
      }
    } catch {
      notification.error({
        title: t("job_payments.notifications.error.openingip")
      });
      setLoadingSafe(false);
    }
  };

  const handleIntelliPayChargeShortLink = async () => {
    setLoading(true);
    // Validate
    try {
      await form.validateFields();
    } catch {
      setLoadingSafe(false);
      return;
    }

    const iPayData = collectIPayFields();

    try {
      const { payments } = form.getFieldsValue();
      logImEXEvent("payment_cc_shortlink");
      const response = await axios.post("/intellipay/generate_payment_url", {
        bodyshop,
        amount: payments.reduce((acc, val) => acc + (val?.amount || 0), 0),
        account: payments && data?.jobs?.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null,
        comment: btoa(JSON.stringify({ payments, userEmail: currentUser.email })),
        paymentSplitMeta: form.getFieldsValue(),
        iPayData: iPayData
      });

      if (response?.data?.shorUrl) {
        setPaymentLink(response.data.shorUrl);
        await navigator.clipboard.writeText(response.data.shorUrl);
        message.success(t("general.actions.copied"));
      }
      setLoadingSafe(false);
    } catch {
      notification.error({
        title: t("job_payments.notifications.error.openingip")
      });
      setLoadingSafe(false);
    }
  };

  return (
    <Card title="Card Payment">
      <Spin spinning={loading}>
        <Form
          form={form}
          layout="vertical"
          initialValues={{
            payments: context.jobid ? [{ jobid: context.jobid }] : []
          }}
        >
          <Form.List name={["payments"]}>
            {(fields, { add, remove }) => (
              <div>
                {fields.map((field, index) => (
                  <Form.Item key={field.key}>
                    <Row gutter={[16, 16]}>
                      <Col span={16}>
                        <Form.Item
                          key={`${index}jobid`}
                          label={t("jobs.fields.ro_number")}
                          name={[field.name, "jobid"]}
                          rules={[{ required: true }]}
                        >
                          <JobSearchSelectComponent notExported={false} clm_no />
                        </Form.Item>
                      </Col>
                      <Col span={6}>
                        <Form.Item
                          key={`${index}amount`}
                          label={t("payments.fields.amount")}
                          name={[field.name, "amount"]}
                          rules={[{ required: true }]}
                        >
                          <CurrencyFormItemComponent />
                        </Form.Item>
                      </Col>
                      <Col span={2}>
                        <DeleteFilled style={{ margin: "1rem" }} onClick={() => remove(field.name)} />
                      </Col>
                    </Row>
                  </Form.Item>
                ))}
                <Form.Item>
                  <Button type="dashed" onClick={() => add()} style={{ width: "100%" }}>
                    {t("general.actions.add")}
                  </Button>
                </Form.Item>
              </div>
            )}
          </Form.List>

          {/* Hidden IntelliPay fields driven by watched payments + query result.  IMPORTANT: no refetch() here (avoid side effects during render). */}
          <Input
            className="ipayfield"
            data-ipayname="account"
            type="hidden"
            value={data?.jobs?.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null}
          />
          <Input
            className="ipayfield"
            data-ipayname="email"
            type="hidden"
            value={data?.jobs?.length > 0 ? (data.jobs.find((j) => j.ownr_ea)?.ownr_ea ?? null) : null}
          />

          <Form.Item
            shouldUpdate={(prevValues, curValues) =>
              prevValues.payments?.map((p) => p?.amount).join() !== curValues.payments?.map((p) => p?.amount).join()
            }
          >
            {() => {
              const { payments } = form.getFieldsValue();
              const totalAmountToCharge = payments?.reduce((acc, val) => acc + (val?.amount || 0), 0);
              return (
                <Space style={{ float: "right" }}>
                  <Statistic title="Amount To Charge" value={totalAmountToCharge} precision={2} />
                  <Input
                    className="ipayfield"
                    data-ipayname="amount"
                    type="hidden"
                    value={totalAmountToCharge?.toFixed(2)}
                  />
                  <Input
                    className="ipayfield"
                    data-ipayname="comment"
                    type="hidden"
                    value={btoa(JSON.stringify(payments))}
                    hidden
                  />
                  <Button
                    type="primary"
                    //  data-ipayname="submit"
                    className="ipayfield"
                    loading={queryLoading || loading}
                    disabled={!(totalAmountToCharge > 0)}
                    onClick={handleIntelliPayCharge}
                  >
                    {t("job_payments.buttons.proceedtopayment")}
                  </Button>
                  <Space orientation="vertical" align="center">
                    <Button
                      type="primary"
                      //  data-ipayname="submit"
                      className="ipayfield"
                      loading={queryLoading || loading}
                      disabled={!(totalAmountToCharge > 0)}
                      onClick={handleIntelliPayChargeShortLink}
                    >
                      {t("job_payments.buttons.create_short_link")}
                    </Button>
                  </Space>
                </Space>
              );
            }}
          </Form.Item>
        </Form>

        {paymentLink && (
          <Space
            style={{ cursor: "pointer", float: "right" }}
            align="end"
            onClick={() => {
              navigator.clipboard.writeText(paymentLink);
              message.success(t("general.actions.copied"));
            }}
          >
            <div>{paymentLink}</div>
            <CopyFilled />
          </Space>
        )}

        {queryError ? (
          <div style={{ marginTop: 12 }}>
            <span style={{ color: "red" }}>{queryError.message}</span>
          </div>
        ) : null}
      </Spin>
    </Card>
  );
};

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

// Poll for window.IntelliPay.fixAmount for 5 seconds. If it doesn't come up, just try anyways to force the possible error.
function pollForIntelliPay(callbackFunction) {
  const timeout = 5000;
  const interval = 150;
  const startTime = Date.now();

  function checkFixAmount() {
    if (window.intellipay?.fixAmount) {
      callbackFunction();
      return;
    }

    if (Date.now() - startTime >= timeout) {
      console.log("Stopped polling IntelliPay after 5 seconds. Attempting to set functions anyways.");
      callbackFunction();
      return;
    }

    setTimeout(checkFixAmount, interval);
  }

  checkFixAmount();
}
