import { useApolloClient, useQuery, useSubscription } from "@apollo/client/react";
import { useEffect, useRef, useState } from "react";
import {
  QUERY_EXACT_JOB_IN_PRODUCTION,
  QUERY_EXACT_JOBS_IN_PRODUCTION,
  QUERY_JOBS_IN_PRODUCTION,
  SUBSCRIPTION_JOBS_IN_PRODUCTION,
  SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW
} from "../../graphql/jobs.queries";
import ProductionListTable from "./production-list-table.component";
import _ from "lodash";
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
import { useSocket } from "../../contexts/SocketIO/useSocket.js";

export default function ProductionListTableContainer({ bodyshop, subscriptionType = "direct" }) {
  const client = useApolloClient();
  const { socket } = useSocket();
  const [joblist, setJoblist] = useState([]);
  const reconnectTimeout = useRef(null); // To store the reconnect timeout
  const disconnectTime = useRef(null); // To store the time of disconnection

  const acceptableReconnectTime = 2000; // 2 seconds threshold

  // Get Split treatment
  const {
    treatments: { Websocket_Production }
  } = useTreatmentsWithConfig({
    attributes: {},
    names: ["Websocket_Production"],
    splitKey: bodyshop && bodyshop.imexshopid
  });

  // Determine if subscription is enabled
  const subscriptionEnabled = Websocket_Production?.treatment === "off";

  // Use GraphQL query
  const { refetch, loading, data } = useQuery(QUERY_JOBS_IN_PRODUCTION, {
    pollInterval: 3600000,
    fetchPolicy: "network-only",
    nextFetchPolicy: "network-only"
  });

  // Use GraphQL subscription when subscription is enabled
  const { data: updatedJobs } = useSubscription(
    subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION,
    {
      skip: !subscriptionEnabled
    }
  );

  // Update joblist when data changes
  useEffect(() => {
    if (!(data && data.jobs)) return;
    setJoblist(
      data.jobs.map((j) => {
        return { id: j.id, updated_at: j.updated_at };
      })
    );
  }, [data]);

  // Handle updates from GraphQL subscription
  useEffect(() => {
    if (subscriptionEnabled) {
      if (!updatedJobs || joblist.length === 0) return;

      const jobDiff = _.differenceWith(
        joblist,
        updatedJobs.jobs,
        (a, b) => a.id === b.id && a.updated_at === b.updated_at
      );

      if (jobDiff.length > 1) {
        getUpdatedJobsData(jobDiff.map((j) => j.id));
      } else if (jobDiff.length === 1) {
        jobDiff.forEach((job) => {
          getUpdatedJobData(job.id);
        });
      }

      setJoblist(updatedJobs.jobs);
    }
  }, [updatedJobs, subscriptionEnabled]);

  // Handle updates from Socket.IO when subscription is disabled
  useEffect(() => {
    if (subscriptionEnabled || !socket || !bodyshop || !bodyshop.id) {
      return;
    }

    const handleJobUpdates = async (jobChangedData) => {
      const jobId = jobChangedData.id;

      // Access the existing cache for QUERY_JOBS_IN_PRODUCTION
      const existingJobsCache = client.readQuery({
        query: QUERY_JOBS_IN_PRODUCTION
      });

      const existingJobs = existingJobsCache?.jobs || [];

      // Check if the job already exists in the cached jobs
      const existingJob = existingJobs.find((job) => job.id === jobId);

      if (existingJob) {
        // If the job exists, we update the cache without making any additional queries
        client.writeQuery({
          query: QUERY_JOBS_IN_PRODUCTION,
          data: {
            jobs: existingJobs.map((job) =>
              job.id === jobId ? { ...existingJob, ...jobChangedData, __typename: "jobs" } : job
            )
          }
        });
      } else {
        // If the job doesn't exist, fetch it from the server and then add it to the cache
        try {
          const { data: jobData } = await client.query({
            query: QUERY_EXACT_JOB_IN_PRODUCTION,
            variables: { id: jobId },
            fetchPolicy: "network-only"
          });

          // Add the job to the existing cached jobs
          client.writeQuery({
            query: QUERY_JOBS_IN_PRODUCTION,
            data: {
              jobs: [...existingJobs, { ...jobData.job, __typename: "jobs" }]
            }
          });
        } catch (error) {
          console.error(`Error fetching job ${jobId}: ${error.message}`);
        }
      }
    };

    const handleDisconnect = () => {
      // Capture the time when the disconnection happens
      disconnectTime.current = Date.now();
    };

    const handleReconnect = () => {
      // Calculate how long the disconnection lasted
      const reconnectTime = Date.now();
      const disconnectionDuration = reconnectTime - disconnectTime.current;

      // If disconnection lasted less than acceptable reconnect time, do nothing
      if (disconnectionDuration < acceptableReconnectTime) {
        return;
      }

      // Schedule a refetch with a random delay between 10 and 30 seconds
      if (!reconnectTimeout.current) {
        reconnectTimeout.current = setTimeout(() => {
          const randomDelay = Math.floor(Math.random() * (30000 - 10000 + 1)) + 10000; // Random delay between 10 and 30 seconds
          setTimeout(() => {
            if (refetch) refetch();
            reconnectTimeout.current = null; // Clear the timeout reference after refetch
          }, randomDelay);
        }, acceptableReconnectTime);
      }
    };

    // Listen for 'production-job-updated', 'disconnect', and 'connect' events
    socket.on("production-job-updated", handleJobUpdates);
    socket.on("disconnect", handleDisconnect);
    socket.on("connect", handleReconnect);

    // Clean up on unmount or when dependencies change
    return () => {
      socket.off("production-job-updated", handleJobUpdates);
      socket.off("disconnect", handleDisconnect);
      socket.off("connect", handleReconnect);
      if (reconnectTimeout.current) {
        clearTimeout(reconnectTimeout.current);
      }
    };
  }, [subscriptionEnabled, socket, bodyshop, client, refetch]);

  // Functions to fetch updated job data
  const getUpdatedJobData = async (jobId) => {
    await client.query({
      query: QUERY_EXACT_JOB_IN_PRODUCTION,
      variables: { id: jobId },
      fetchPolicy: "network-only"
    });
  };

  const getUpdatedJobsData = (jobIds) => {
    client.query({
      query: QUERY_EXACT_JOBS_IN_PRODUCTION,
      variables: { ids: jobIds }
    });
  };

  return <ProductionListTable loading={loading} data={data ? data.jobs : []} refetch={refetch} />;
}
