import { createContext, useContext, useEffect, useRef, useState } from "react";
import SocketIO from "socket.io-client";
import { auth } from "../../firebase/firebase.utils";
import { store } from "../../redux/store";
import { addAlerts, setWssStatus } from "../../redux/application/application.actions";
import client from "../../utils/GraphQLClient";
import { useNotification } from "../Notifications/notificationContext.jsx";
import {
  GET_NOTIFICATIONS,
  GET_UNREAD_COUNT,
  MARK_ALL_NOTIFICATIONS_READ,
  MARK_NOTIFICATION_READ,
  UPDATE_NOTIFICATIONS_READ_FRAGMENT
} from "../../graphql/notifications.queries.js";
import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next";
import { useSplitTreatments } from "@splitsoftware/splitio-react";

const SocketContext = createContext(null);

const INITIAL_NOTIFICATIONS = 10;

/**
 * Socket Provider - Scenario Notifications / Web Socket related items
 * @param children
 * @param bodyshop
 * @param navigate
 * @param currentUser
 * @returns {JSX.Element}
 * @constructor
 */
const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
  const socketRef = useRef(null);
  const [clientId, setClientId] = useState(null);
  const [isConnected, setIsConnected] = useState(false);
  const notification = useNotification();
  const userAssociationId = bodyshop?.associations?.[0]?.id;
  const { t } = useTranslation();

  const {
    treatments: { Realtime_Notifications_UI }
  } = useSplitTreatments({
    attributes: {},
    names: ["Realtime_Notifications_UI"],
    splitKey: bodyshop?.imexshopid
  });

  const [markNotificationRead] = useMutation(MARK_NOTIFICATION_READ, {
    update: (cache, { data: { update_notifications } }) => {
      const timestamp = new Date().toISOString();
      const updatedNotification = update_notifications.returning[0];

      cache.modify({
        fields: {
          notifications(existing = [], { readField }) {
            return existing.map((notif) =>
              readField("id", notif) === updatedNotification.id
                ? {
                    ...notif,
                    read: timestamp
                  }
                : notif
            );
          }
        }
      });

      const unreadCountQuery = cache.readQuery({
        query: GET_UNREAD_COUNT,
        variables: { associationid: userAssociationId }
      });

      if (unreadCountQuery?.notifications_aggregate?.aggregate?.count > 0) {
        cache.writeQuery({
          query: GET_UNREAD_COUNT,
          variables: { associationid: userAssociationId },
          data: {
            notifications_aggregate: {
              ...unreadCountQuery.notifications_aggregate,
              aggregate: {
                ...unreadCountQuery.notifications_aggregate.aggregate,
                count: unreadCountQuery.notifications_aggregate.aggregate.count - 1
              }
            }
          }
        });
      }

      if (socketRef.current && isConnected) {
        socketRef.current.emit("sync-notification-read", {
          email: currentUser?.email,
          bodyshopId: bodyshop.id,
          notificationId: updatedNotification.id
        });
      }
    },
    onError: (err) =>
      console.error("MARK_NOTIFICATION_READ error:", {
        message: err?.message,
        stack: err?.stack
      })
  });

  const [markAllNotificationsRead] = useMutation(MARK_ALL_NOTIFICATIONS_READ, {
    variables: { associationid: userAssociationId },
    update: (cache) => {
      const timestamp = new Date().toISOString();
      cache.modify({
        fields: {
          notifications(existing = [], { readField }) {
            return existing.map((notif) =>
              readField("read", notif) === null && readField("associationid", notif) === userAssociationId
                ? { ...notif, read: timestamp }
                : notif
            );
          },
          notifications_aggregate() {
            return { aggregate: { count: 0, __typename: "notifications_aggregate_fields" } };
          }
        }
      });

      const baseWhereClause = { associationid: { _eq: userAssociationId } };
      const cachedNotifications = cache.readQuery({
        query: GET_NOTIFICATIONS,
        variables: { limit: INITIAL_NOTIFICATIONS, offset: 0, where: baseWhereClause }
      });

      if (cachedNotifications?.notifications) {
        cache.writeQuery({
          query: GET_NOTIFICATIONS,
          variables: { limit: INITIAL_NOTIFICATIONS, offset: 0, where: baseWhereClause },
          data: {
            notifications: cachedNotifications.notifications.map((notif) =>
              notif.read === null ? { ...notif, read: timestamp } : notif
            )
          }
        });
      }

      if (socketRef.current && isConnected) {
        socketRef.current.emit("sync-all-notifications-read", {
          email: currentUser?.email,
          bodyshopId: bodyshop.id
        });
      }
    },
    onError: (err) => console.error("MARK_ALL_NOTIFICATIONS_READ error:", err)
  });

  useEffect(() => {
    const initializeSocket = async (token) => {
      if (!bodyshop || !bodyshop.id || socketRef.current) return;

      const endpoint = import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "";
      const socketInstance = SocketIO(endpoint, {
        path: "/wss",
        withCredentials: true,
        auth: { token, bodyshopId: bodyshop.id },
        reconnectionAttempts: Infinity,
        reconnectionDelay: 2000,
        reconnectionDelayMax: 10000
      });

      socketRef.current = socketInstance;

      const handleBodyshopMessage = (message) => {
        if (!message || !message.type) return;
        switch (message.type) {
          case "alert-update":
            store.dispatch(addAlerts(message.payload));
            break;
          default:
            break;
        }
      };

      const handleConnect = () => {
        socketInstance.emit("join-bodyshop-room", bodyshop.id);
        setClientId(socketInstance.id);
        setIsConnected(true);
        store.dispatch(setWssStatus("connected"));
      };

      const handleReconnect = () => {
        setIsConnected(true);
        store.dispatch(setWssStatus("connected"));
      };

      const handleConnectionError = (err) => {
        console.error("Socket connection error:", err);
        setIsConnected(false);
        if (err.message.includes("auth/id-token-expired")) {
          console.warn("Token expired, refreshing...");
          auth.currentUser?.getIdToken(true).then((newToken) => {
            socketInstance.auth = { token: newToken };
            socketInstance.connect();
          });
        } else {
          store.dispatch(setWssStatus("error"));
        }
      };

      const handleDisconnect = (reason) => {
        console.warn("Socket disconnected:", reason);
        setIsConnected(false);
        store.dispatch(setWssStatus("disconnected"));
        if (!socketInstance.connected && reason !== "io server disconnect") {
          setTimeout(() => {
            if (socketInstance.disconnected) {
              console.log("Manually triggering reconnection...");
              socketInstance.connect();
            }
          }, 2000);
        }
      };

      const handleNotification = (data) => {
        // Scenario Notifications have been disabled, bail.
        if (Realtime_Notifications_UI?.treatment !== "on") {
          return;
        }

        const { jobId, jobRoNumber, notificationId, associationId, notifications } = data;
        if (associationId !== userAssociationId) return;

        const newNotification = {
          __typename: "notifications",
          id: notificationId,
          jobid: jobId,
          associationid: associationId,
          scenario_text: JSON.stringify(notifications.map((notif) => notif.body)),
          fcm_text: notifications.map((notif) => notif.body).join(". ") + ".",
          scenario_meta: JSON.stringify(notifications.map((notif) => notif.variables || {})),
          created_at: new Date(notifications[0].timestamp).toISOString(),
          read: null,
          job: { ro_number: jobRoNumber }
        };

        const baseVariables = {
          limit: INITIAL_NOTIFICATIONS,
          offset: 0,
          where: { associationid: { _eq: userAssociationId } }
        };

        try {
          const existingNotifications =
            client.cache.readQuery({
              query: GET_NOTIFICATIONS,
              variables: baseVariables
            })?.notifications || [];
          if (!existingNotifications.some((n) => n.id === newNotification.id)) {
            client.cache.writeQuery({
              query: GET_NOTIFICATIONS,
              variables: baseVariables,
              data: {
                notifications: [newNotification, ...existingNotifications].sort(
                  (a, b) => new Date(b.created_at) - new Date(a.created_at)
                )
              },
              broadcast: true
            });

            const unreadVariables = {
              ...baseVariables,
              where: { ...baseVariables.where, read: { _is_null: true } }
            };
            const unreadNotifications =
              client.cache.readQuery({
                query: GET_NOTIFICATIONS,
                variables: unreadVariables
              })?.notifications || [];
            if (newNotification.read === null && !unreadNotifications.some((n) => n.id === newNotification.id)) {
              client.cache.writeQuery({
                query: GET_NOTIFICATIONS,
                variables: unreadVariables,
                data: {
                  notifications: [newNotification, ...unreadNotifications].sort(
                    (a, b) => new Date(b.created_at) - new Date(a.created_at)
                  )
                },
                broadcast: true
              });
            }

            client.cache.modify({
              id: "ROOT_QUERY",
              fields: {
                notifications_aggregate(existing = { aggregate: { count: 0 } }) {
                  return {
                    ...existing,
                    aggregate: {
                      ...existing.aggregate,
                      count: existing.aggregate.count + (newNotification.read === null ? 1 : 0)
                    }
                  };
                }
              }
            });

            notification.info({
              message: (
                <div
                  onClick={() => {
                    markNotificationRead({ variables: { id: notificationId } })
                      .then(() => navigate(`/manage/jobs/${jobId}`))
                      .catch((e) => console.error(`Error marking notification read: ${e?.message || ""}`));
                  }}
                >
                  {t("notifications.labels.notification-popup-title", {
                    ro_number: jobRoNumber || t("general.labels.na")
                  })}
                </div>
              ),
              description: (
                <ul
                  className="notification-alert-unordered-list"
                  onClick={() => {
                    markNotificationRead({ variables: { id: notificationId } })
                      .then(() => navigate(`/manage/jobs/${jobId}`))
                      .catch((e) => console.error(`Error marking notification read: ${e?.message || ""}`));
                  }}
                >
                  {notifications.map((notif, index) => (
                    <li className="notification-alert-unordered-list-item" key={index}>
                      {notif.body}
                    </li>
                  ))}
                </ul>
              )
            });
          }
        } catch (error) {
          console.error(`Error handling new notification: ${error?.message || ""}`);
        }
      };

      const handleSyncNotificationRead = ({ notificationId, timestamp }) => {
        // Scenario Notifications have been disabled, bail.
        if (Realtime_Notifications_UI?.treatment !== "on") {
          return;
        }

        try {
          const notificationRef = client.cache.identify({
            __typename: "notifications",
            id: notificationId
          });
          client.cache.writeFragment({
            id: notificationRef,
            fragment: UPDATE_NOTIFICATIONS_READ_FRAGMENT,
            data: { read: timestamp }
          });

          const unreadCountData = client.cache.readQuery({
            query: GET_UNREAD_COUNT,
            variables: { associationid: userAssociationId }
          });
          if (unreadCountData?.notifications_aggregate?.aggregate?.count > 0) {
            const newCount = Math.max(unreadCountData.notifications_aggregate.aggregate.count - 1, 0);
            client.cache.writeQuery({
              query: GET_UNREAD_COUNT,
              variables: { associationid: userAssociationId },
              data: {
                notifications_aggregate: {
                  __typename: "notifications_aggregate",
                  aggregate: {
                    __typename: "notifications_aggregate_fields",
                    count: newCount
                  }
                }
              }
            });
          }
        } catch (error) {
          console.error("Error in handleSyncNotificationRead:", error);
        }
      };

      const handleSyncAllNotificationsRead = ({ timestamp }) => {
        // Scenario Notifications have been disabled, bail.
        if (Realtime_Notifications_UI?.treatment !== "on") {
          return;
        }

        try {
          const queryVars = {
            limit: INITIAL_NOTIFICATIONS,
            offset: 0,
            where: { associationid: { _eq: userAssociationId } }
          };
          const cachedData = client.cache.readQuery({
            query: GET_NOTIFICATIONS,
            variables: queryVars
          });

          if (cachedData?.notifications) {
            cachedData.notifications.forEach((notif) => {
              if (!notif.read) {
                const notifRef = client.cache.identify({ __typename: "notifications", id: notif.id });
                client.cache.writeFragment({
                  id: notifRef,
                  fragment: UPDATE_NOTIFICATIONS_READ_FRAGMENT,
                  data: { read: timestamp }
                });
              }
            });
          }

          client.cache.writeQuery({
            query: GET_UNREAD_COUNT,
            variables: { associationid: userAssociationId },
            data: {
              notifications_aggregate: {
                __typename: "notifications_aggregate",
                aggregate: {
                  __typename: "notifications_aggregate_fields",
                  count: 0
                }
              }
            }
          });
        } catch (error) {
          console.error(`Error In HandleSyncAllNotificationsRead: ${error?.message || ""}`);
        }
      };

      socketInstance.on("connect", handleConnect);
      socketInstance.on("reconnect", handleReconnect);
      socketInstance.on("connect_error", handleConnectionError);
      socketInstance.on("disconnect", handleDisconnect);
      socketInstance.on("bodyshop-message", handleBodyshopMessage);
      socketInstance.on("notification", handleNotification);
      socketInstance.on("sync-notification-read", handleSyncNotificationRead);
      socketInstance.on("sync-all-notifications-read", handleSyncAllNotificationsRead);
    };

    const unsubscribe = auth.onIdTokenChanged(async (user) => {
      if (user) {
        const token = await user.getIdToken();
        if (socketRef.current) {
          socketRef.current.emit("update-token", { token, bodyshopId: bodyshop.id });
        } else {
          initializeSocket(token).catch((err) =>
            console.error(`Something went wrong Initializing Sockets: ${err?.message || ""}`)
          );
        }
      } else {
        if (socketRef.current) {
          socketRef.current.disconnect();
          socketRef.current = null;
          setIsConnected(false);
        }
      }
    });

    return () => {
      unsubscribe();
      if (socketRef.current) {
        socketRef.current.disconnect();
        socketRef.current = null;
        setIsConnected(false);
      }
    };
  }, [
    bodyshop,
    notification,
    userAssociationId,
    markNotificationRead,
    markAllNotificationsRead,
    navigate,
    currentUser,
    Realtime_Notifications_UI,
    t
  ]);

  return (
    <SocketContext.Provider
      value={{
        socket: socketRef.current,
        clientId,
        isConnected,
        markNotificationRead,
        markAllNotificationsRead,
        scenarioNotificationsOn: Realtime_Notifications_UI?.treatment === "on"
      }}
    >
      {children}
    </SocketContext.Provider>
  );
};

const useSocket = () => {
  const context = useContext(SocketContext);
  // NOTE: Not sure if we absolutely require this, does cause slipups on dev env
  if (!context) throw new Error("useSocket must be used within a SocketProvider");
  return context;
};

export { SocketContext, SocketProvider, INITIAL_NOTIFICATIONS, useSocket };
