import { useEffect, useMemo, useRef, useState } from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { connect } from "react-redux";
import { useTranslation } from "react-i18next";
import { createStructuredSelector } from "reselect";
import queryString from "query-string";
import { useQuery } from "@apollo/client/react";
import { Button, Card, Col, Result, Row, Select, Space, Switch } from "antd";
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";

import { QUERY_JOB_EXPORT_DMS } from "../../graphql/jobs.queries";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { insertAuditTrail, setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";

import InstanceRenderManager from "../../utils/instanceRenderMgr";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { bodyshopHasDmsKey, DMS_MAP, getDmsMode, isWssMode } from "../../utils/dmsUtils.js";
import legacySocket from "../../utils/legacySocket";

import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
import AlertComponent from "../../components/alert/alert.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import DmsPostForm from "../../components/dms-post-form/dms-post-form.component";
import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component";
import DmsCustomerSelector from "../../components/dms-customer-selector/dms-customer-selector.component";
import DmsAllocationsSummary from "../../components/dms-allocations-summary/dms-allocations-summary.component";
import RrAllocationsSummary from "../../components/dms-allocations-summary/rr-dms-allocations-summary.component";

const mapStateToProps = createStructuredSelector({
  bodyshop: selectBodyshop,
  currentUser: selectCurrentUser
});

const mapDispatchToProps = (dispatch) => ({
  setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
  setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
  insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
});

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

const DMS_SOCKET_EVENTS = {
  [DMS_MAP.reynolds]: {
    log: "rr-log-event",
    partialResult: "rr-export-job:result",
    validationNeeded: "rr-validation-required",
    exportSuccess: "export-success",
    exportFailed: "export-failed"
  },
  [DMS_MAP.fortellis]: {
    log: "fortellis-log-event",
    exportSuccess: "export-success",
    exportFailed: "export-failed"
  },
  [DMS_MAP.cdk]: {
    log: "log-event",
    exportSuccess: "export-success",
    exportFailed: "export-failed"
  },
  [DMS_MAP.pbs]: {
    log: "log-event",
    exportSuccess: "export-success",
    exportFailed: "export-failed"
  }
};

const stripRrXmlFromPayload = (input) => {
  if (input == null || typeof input !== "object") return input;

  let target = null;
  try {
    target = JSON.parse(JSON.stringify(input));
  } catch {
    // Fallback to in-place scrub if cloning fails.
    target = input;
  }

  const scrub = (node) => {
    if (node == null || typeof node !== "object") return;
    if (Array.isArray(node)) {
      node.forEach(scrub);
      return;
    }

    delete node.requestXml;
    delete node.responseXml;

    if (node.xml && typeof node.xml === "object") {
      delete node.xml.request;
      delete node.xml.response;
      if (Object.keys(node.xml).length === 0) delete node.xml;
    }

    Object.values(node).forEach(scrub);
  };

  scrub(target);
  return target;
};

export function DmsContainer({ bodyshop, currentUser, setBreadcrumbs, setSelectedHeader, insertAuditTrail }) {
  const {
    treatments: { Fortellis }
  } = useTreatmentsWithConfig({
    attributes: {},
    names: ["Fortellis"],
    splitKey: bodyshop.imexshopid
  });

  const { t } = useTranslation();
  const [resetAfterReconnect, setResetAfterReconnect] = useState(false);
  const [allocationsSummary, setAllocationsSummary] = useState(null);
  const [reconnectNonce, setReconnectNonce] = useState(0);

  const isDevEnv = import.meta.env.DEV;
  const isProdEnv = import.meta.env.PROD;
  const userEmail = (currentUser?.email || "").toLowerCase();

  const devEmails = ["imex.dev", "rome.dev"];
  const prodEmails = ["imex.prod", "rome.prod", "imex.test", "rome.test"];
  const hasValidEmail = (emails) => emails.some((email) => userEmail.endsWith(email));
  const canViewSensitiveRrXml = (isDevEnv && hasValidEmail(devEmails)) || (isProdEnv && hasValidEmail(prodEmails));

  // Compute a single normalized mode and pick the proper socket
  const mode = getDmsMode(bodyshop, Fortellis.treatment); // "rr" | "fortellis" | "cdk" | "pbs" | "none"

  // RR-only: derive default OpCode parts from bodyshop RR configuration
  const isRrMode = mode === DMS_MAP.reynolds;

  const deriveDefaultRrOpCodeParts = () => {
    if (!isRrMode) return null;

    const cfg = bodyshop?.rr_configuration || {};

    // Adjust these paths to match your real schema.
    const defaults =
      cfg.opCodeDefault ||
      cfg.op_code_default ||
      cfg.op_codes?.default ||
      cfg.defaults?.opCode ||
      cfg.defaults ||
      cfg.default ||
      {};

    const prefix = defaults.prefix ?? defaults.opCodePrefix ?? "";
    const base = defaults.base ?? defaults.opCodeBase ?? "";
    const suffix = defaults.suffix ?? defaults.opCodeSuffix ?? "";

    return { prefix, base, suffix };
  };

  const [rrOpCodeParts, setRrOpCodeParts] = useState(() => deriveDefaultRrOpCodeParts());

  const history = useNavigate();
  const search = queryString.parse(useLocation().search);
  const { jobId } = search;

  const notification = useNotification();

  const { socket: wsssocket, reconnectSocket } = useSocket();
  const activeSocket = useMemo(() => (isWssMode(mode) ? wsssocket : legacySocket), [mode, wsssocket]);

  const [isConnected, setIsConnected] = useState(!!activeSocket?.connected);

  // One place to set log level
  const [logLevel, setLogLevel] = useState(mode === DMS_MAP.pbs ? "INFO" : "DEBUG");

  const rrOpCodeCombined = useMemo(() => {
    if (!rrOpCodeParts || !rrOpCodeParts.base) return "";
    const { prefix, base, suffix } = rrOpCodeParts;
    return `${prefix || ""}${base}${suffix || ""}`;
  }, [rrOpCodeParts]);

  const setActiveLogLevel = (level) => {
    if (!activeSocket) return;
    activeSocket.emit("set-log-level", level);
  };

  const [logs, setLogs] = useState([]);
  const [detailsOpen, setDetailsOpen] = useState(false);
  const [detailsNonce, setDetailsNonce] = useState(0);
  const [colorizeJson, setColorizeJson] = useState(false);

  const [rrOpenRoLimit, setRrOpenRoLimit] = useState(false);
  const clearRrOpenRoLimit = () => setRrOpenRoLimit(false);

  const [rrValidationPending, setrrValidationPending] = useState(false);

  const { loading, error, data } = useQuery(QUERY_JOB_EXPORT_DMS, {
    variables: { id: jobId },
    skip: !jobId,
    fetchPolicy: "network-only",
    nextFetchPolicy: "network-only"
  });

  const logsRef = useRef(null);

  const toggleDetailsAll = () => {
    setDetailsOpen((v) => !v);
    setDetailsNonce((n) => n + 1);
  };

  // Channel names per mode to avoid branching everywhere
  const channels = useMemo(() => DMS_SOCKET_EVENTS[mode] || {}, [mode]);

  const providerLabel = useMemo(
    () =>
      ({
        [DMS_MAP.reynolds]: t("dms.labels.provider_reynolds"),
        [DMS_MAP.fortellis]: t("dms.labels.provider_fortellis"),
        [DMS_MAP.cdk]: t("dms.labels.provider_cdk"),
        [DMS_MAP.pbs]: t("dms.labels.provider_pbs")
      })[mode] || t("dms.labels.provider_dms"),
    [mode, t]
  );

  const transportLabel = isWssMode(mode) ? t("dms.labels.transport_wss") : t("dms.labels.transport_ws");

  const bannerMessage = t("dms.labels.banner_message", {
    provider: providerLabel,
    transport: transportLabel,
    status: isConnected ? t("dms.labels.banner_status_connected") : t("dms.labels.banner_status_disconnected")
  });

  const resetKey = useMemo(() => `${mode || "none"}-${jobId || "none"}`, [mode, jobId]);
  const customerSelectorKey = useMemo(() => `${resetKey}-${reconnectNonce}`, [resetKey, reconnectNonce]);

  const handleReconnectClick = async () => {
    setResetAfterReconnect(true);
    setReconnectNonce((n) => n + 1);

    if (!activeSocket) return;

    if (isWssMode(mode)) {
      setActiveLogLevel(logLevel);
      const didReconnect = await reconnectSocket?.({ forceRefreshToken: true });
      if (!didReconnect) {
        activeSocket.disconnect();
        setTimeout(() => activeSocket.connect(), 100);
      }
      return;
    }

    activeSocket.disconnect();
    setTimeout(() => activeSocket.connect(), 100);
  };

  // 🔄 Hard reset of local + server-side DMS context when the page/job loads
  useEffect(() => {
    // Clear any local ephemeral state that might be stale
    setLogs([]);
    setRrOpenRoLimit(false);
    setrrValidationPending(false);
    setAllocationsSummary(null);

    // RR OpCode parts: reset to config defaults when job/mode changes
    setRrOpCodeParts(deriveDefaultRrOpCodeParts());

    if (!activeSocket) return;

    const emitReset = () => {
      // Generic reset; server can branch on `mode` if needed
      activeSocket.emit("dms-reset-context", { jobId, mode });
    };

    if (activeSocket.connected) {
      // WSS usually lands here
      emitReset();
      return;
    }

    // Legacy WS: wait for the connect before emitting reset
    const handleConnectOnce = () => {
      emitReset();
      activeSocket.off("connect", handleConnectOnce);
    };

    activeSocket.on("connect", handleConnectOnce);

    return () => {
      activeSocket.off("connect", handleConnectOnce);
    };
  }, [jobId, mode, activeSocket]);

  const handleExportFailed = (payload = {}) => {
    const safePayload = canViewSensitiveRrXml ? payload : stripRrXmlFromPayload(payload);
    const { title, friendlyMessage, error: errText, severity, errorCode, vendorStatusCode } = payload;

    const msg =
      friendlyMessage ||
      errText ||
      t("dms.errors.exportfailedgeneric", "We couldn't complete the export. Please try again.");

    const vendorTitle = title || (isRrMode ? t("dms.labels.provider_reynolds") : t("dms.labels.provider_dms"));

    const isRrOpenRoLimit =
      isRrMode &&
      (vendorStatusCode === 507 ||
        /MAX_OPEN_ROS/i.test(String(errorCode || "")) ||
        /maximum number of open repair orders/i.test(String(msg || "").toLowerCase()));

    const sev = severity || (isRrOpenRoLimit ? "warning" : "error");

    if (!isRrOpenRoLimit) {
      const type = sev === "warning" && typeof notification.warning === "function" ? "warning" : "error";
      notification.open({ type, tile: vendorTitle, description: msg, duration: 10 });
    } else {
      setRrOpenRoLimit(true);
    }

    setLogs((prev) => [
      ...prev,
      {
        timestamp: new Date(),
        level: (sev || "error").toUpperCase(),
        message: `${vendorTitle}: ${msg}`,
        meta: { errorCode, vendorStatusCode, raw: safePayload, blockedByOpenRoLimit: !!isRrOpenRoLimit }
      }
    ]);
  };

  // keep this in sync if mode/socket flips
  useEffect(() => {
    setIsConnected(!!activeSocket?.connected);
  }, [activeSocket]);

  useEffect(() => {
    document.title = t("titles.dms", {
      app: InstanceRenderManager({ imex: "$t(titles.imexonline)", rome: "$t(titles.romeonline)" })
    });
    setSelectedHeader("dms");
    setBreadcrumbs([
      { link: "/manage/accounting/receivables", label: t("titles.bc.accounting-receivables") }
      // { link: "/manage/dms", label: t("titles.bc.dms") }
    ]);
  }, [t, setBreadcrumbs, setSelectedHeader]);

  // Socket wiring (mode-aware)
  useEffect(() => {
    if (!activeSocket) return;

    // Connect legacy socket if needed
    if (!isWssMode(mode)) {
      if (activeSocket.disconnected) activeSocket.connect();
    }

    // Set log level now and on connect/reconnect
    setActiveLogLevel(logLevel);

    const onConnect = () => {
      setIsConnected(true);
      setActiveLogLevel(logLevel);

      if (resetAfterReconnect) {
        activeSocket.emit("dms-reset-context", { jobId, mode });
        setResetAfterReconnect(false);
      }
    };

    const onDisconnect = () => setIsConnected(false);

    const onReconnect = () => {
      setIsConnected(true);
      setLogs((prev) => [
        ...prev,
        {
          timestamp: new Date(),
          level: "warn",
          message: t("dms.labels.reconnected_export_service", {
            provider: isRrMode ? t("dms.labels.provider_reynolds") : providerLabel
          })
        }
      ]);
    };

    const onConnectError = (err) => {
      // Legacy and WSS both emit this
      console.log(`connect_error due to ${err}`, err);
      notification.error({ title: err.message });
    };

    activeSocket.on("disconnect", onDisconnect);
    activeSocket.on("connect", onConnect);
    activeSocket.on("reconnect", onReconnect);
    activeSocket.on("connect_error", onConnectError);

    // Logs
    const onLog = isRrMode
      ? (payload = {}) => {
          const safePayload = canViewSensitiveRrXml ? payload : stripRrXmlFromPayload(payload);
          const normalized = {
            timestamp: safePayload.timestamp
              ? new Date(safePayload.timestamp)
              : safePayload.ts
                ? new Date(safePayload.ts)
                : new Date(),
            level: (safePayload.level || "INFO").toUpperCase(),
            message: safePayload.message || safePayload.msg || "",
            meta: safePayload.meta ?? safePayload.ctx ?? safePayload.details ?? null
          };
          setLogs((prev) => [...prev, normalized]);
        }
      : (payload) => setLogs((prev) => [...prev, payload]);

    if (channels.log) activeSocket.on(channels.log, onLog);

    // Success / Failed
    const onExportSuccess = (payload) => {
      const jobIdResolved = payload?.jobId ?? payload;
      notification.success({ title: t("jobs.successes.exported") });

      // Clear RR Validation flag if any
      setrrValidationPending(false);

      insertAuditTrail({
        jobid: jobIdResolved,
        operation: AuditTrailMapping.jobexported(),
        type: "jobexported"
      });
      history("/manage/accounting/receivables");
    };

    if (channels.exportSuccess) activeSocket.on(channels.exportSuccess, onExportSuccess);
    if (channels.exportFailed) activeSocket.on(channels.exportFailed, handleExportFailed);

    // RR-only extras

    const onPartialResult = () => {
      setrrValidationPending(true);
      setLogs((prev) => [
        ...prev,
        {
          timestamp: new Date(),
          level: "INFO",
          message: t("dms.labels.rr_validation_message")
        }
      ]);
      notification.info({
        title: t("dms.labels.rr_validation_notice_title"),
        description: t("dms.labels.rr_validation_notice_description"),
        duration: 8
      });
    };

    const onValidationRequired = (payload) => {
      setrrValidationPending(true);
      setLogs((prev) => [
        ...prev,
        {
          timestamp: new Date(),
          level: "INFO",
          message: t("dms.labels.rr_validation_message"),
          meta: { payload }
        }
      ]);
    };

    if (isRrMode && channels.partialResult) activeSocket.on(channels.partialResult, onPartialResult);
    if (isRrMode && channels.validationNeeded) activeSocket.on(channels.validationNeeded, onValidationRequired);

    return () => {
      activeSocket.off("connect", onConnect);
      activeSocket.off("reconnect", onReconnect);
      activeSocket.off("connect_error", onConnectError);
      activeSocket.off("disconnect", onDisconnect);

      if (channels.log) activeSocket.off(channels.log, onLog);
      if (channels.exportSuccess) activeSocket.off(channels.exportSuccess, onExportSuccess);
      if (channels.exportFailed) activeSocket.off(channels.exportFailed, handleExportFailed);

      if (isRrMode && channels.partialResult) activeSocket.off(channels.partialResult, onPartialResult);
      if (isRrMode && channels.validationNeeded) activeSocket.off(channels.validationNeeded, onValidationRequired);

      // Only tear down legacy socket listeners; don't disconnect WSS from here
      if (!isWssMode(mode)) {
        activeSocket.removeAllListeners();
        activeSocket.disconnect();
      }
    };
  }, [
    mode,
    activeSocket,
    channels,
    logLevel,
    notification,
    t,
    insertAuditTrail,
    history,
    isRrMode,
    providerLabel,
    canViewSensitiveRrXml
  ]);

  // RR finalize callback (unchanged public behavior)
  const handleRrValidationFinished = () => {
    if (!jobId) return;
    if (!isWssMode(mode)) return; // RR is WSS-only
    activeSocket.emit("rr-finalize-repair-order", { jobId }, (ack) => {
      if (ack?.ok) return;
      if (ack?.error) notification.error({ title: ack.error });
    });
  };

  if (loading) return <LoadingSpinner />;
  if (error) return <AlertComponent title={error.message} type="error" />;

  if (!jobId || !bodyshopHasDmsKey(bodyshop) || !data?.jobs_by_pk)
    return <Result status="404" title={t("general.errors.notfound")} />;

  if (data.jobs_by_pk?.date_exported) return <Result status="warning" title={t("dms.errors.alreadyexported")} />;

  // Check if Reynolds mode requires early RO
  const hasEarlyRO = !!(data.jobs_by_pk?.dms_id && data.jobs_by_pk?.dms_customer_id && data.jobs_by_pk?.dms_advisor_id);

  if (isRrMode && !hasEarlyRO) {
    return (
      <Result
        status="warning"
        title={t("dms.errors.earlyrorequired")}
        subTitle={t("dms.errors.earlyrorequired.message")}
        extra={
          <Link to={`/manage/jobs/${jobId}/admin`}>
            <Button type="primary">{t("general.actions.gotoadmin")}</Button>
          </Link>
        }
      />
    );
  }

  return (
    <div>
      <AlertComponent style={{ marginBottom: 10 }} title={bannerMessage} type="warning" showIcon closable />

      <Row gutter={[16, 16]}>
        <Col xs={24} xxl={10} className="dms-equal-height-col dms-top-panel-col">
          {!isRrMode ? (
            <DmsAllocationsSummary
              key={resetKey}
              onAllocationsChange={setAllocationsSummary}
              title={
                <span>
                  <Link
                    to={`/manage/jobs/${data && data.jobs_by_pk.id}`}
                  >{`${data?.jobs_by_pk && data.jobs_by_pk.ro_number}`}</Link>
                  {` | ${OwnerNameDisplayFunction(data.jobs_by_pk)} | ${data.jobs_by_pk.v_model_yr || ""} ${
                    data.jobs_by_pk.v_make_desc || ""
                  } ${data.jobs_by_pk.v_model_desc || ""}`}
                </span>
              }
              socket={activeSocket}
              jobId={jobId}
              mode={mode}
            />
          ) : (
            <RrAllocationsSummary
              key={resetKey}
              onAllocationsChange={setAllocationsSummary}
              title={
                <span>
                  <Link to={`/manage/jobs/${data && data.jobs_by_pk.id}`}>
                    {data?.jobs_by_pk && data.jobs_by_pk.ro_number}
                  </Link>
                  {` | ${OwnerNameDisplayFunction(data.jobs_by_pk)} | ${
                    data.jobs_by_pk.v_model_yr || ""
                  } ${data.jobs_by_pk.v_make_desc || ""} ${data.jobs_by_pk.v_model_desc || ""}`}
                </span>
              }
              socket={activeSocket}
              jobId={jobId}
              opCode={rrOpCodeCombined}
            />
          )}
        </Col>

        <Col xs={24} xxl={14} className="dms-equal-height-col dms-top-panel-col">
          <DmsPostForm
            key={resetKey}
            socket={activeSocket}
            job={data?.jobs_by_pk}
            logsRef={logsRef}
            mode={mode}
            allocationsSummary={allocationsSummary}
            rrOpCodeParts={rrOpCodeParts}
            onChangeRrOpCodeParts={setRrOpCodeParts}
          />
        </Col>

        <DmsCustomerSelector
          key={customerSelectorKey}
          jobid={jobId}
          job={data?.jobs_by_pk}
          bodyshop={bodyshop}
          socket={activeSocket}
          mode={mode}
          rrOptions={{
            openRoLimit: rrOpenRoLimit,
            onOpenRoFinished: clearRrOpenRoLimit,
            validationPending: rrValidationPending,
            onValidationFinished: handleRrValidationFinished
          }}
        />

        <Col span={24}>
          <div ref={logsRef}>
            <Card
              title={t("jobs.labels.dms.logs")}
              extra={
                <Space wrap>
                  {isRrMode && (
                    <>
                      <Switch
                        checked={colorizeJson}
                        onChange={setColorizeJson}
                        checkedChildren={t("dms.labels.color_json")}
                        unCheckedChildren={t("dms.labels.plain_json")}
                      />
                      <Button onClick={toggleDetailsAll}>
                        {detailsOpen ? t("dms.labels.collapse_all") : t("dms.labels.expand_all")}
                      </Button>
                    </>
                  )}

                  <Select
                    placeholder={t("dms.labels.log_level")}
                    value={logLevel}
                    onChange={(value) => {
                      setLogLevel(value);
                      setActiveLogLevel(value);
                    }}
                    options={[
                      { key: "SILLY", value: "SILLY", label: "SILLY" },
                      { key: "DEBUG", value: "DEBUG", label: "DEBUG" },
                      { key: "INFO", value: "INFO", label: "INFO" },
                      { key: "WARN", value: "WARN", label: "WARN" },
                      { key: "ERROR", value: "ERROR", label: "ERROR" }
                    ]}
                  />
                  <Button onClick={() => setLogs([])}>{t("dms.labels.clear_logs")}</Button>
                  <Button onClick={handleReconnectClick}> {t("dms.labels.reconnect")}</Button>
                </Space>
              }
            >
              <DmsLogEvents
                logs={logs}
                // Only honour details/colorized JSON in RR mode;
                // in other modes DmsLogEvents can render a simple, flat list.
                detailsOpen={isRrMode ? detailsOpen : false}
                detailsNonce={detailsNonce}
                colorizeJson={isRrMode ? colorizeJson : false}
                showDetails={isRrMode}
                allowXmlPayload={canViewSensitiveRrXml}
              />
            </Card>
          </div>
        </Col>
      </Row>
    </div>
  );
}
