import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useQuery } from "@apollo/client";
import { connect } from "react-redux";
import NotificationCenterComponent from "./notification-center.component";
import { GET_NOTIFICATIONS } from "../../graphql/notifications.queries";
import { createStructuredSelector } from "reselect";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
import day from "../../utils/day.js";
import { INITIAL_NOTIFICATIONS, useSocket } from "../../contexts/SocketIO/useSocket.js";
import { useIsEmployee } from "../../utils/useIsEmployee.js";

// This will be used to poll for notifications when the socket is disconnected
const NOTIFICATION_POLL_INTERVAL_SECONDS = 60;

/**
 * Notification Center Container
 * @param visible
 * @param onClose
 * @param bodyshop
 * @param unreadCount
 * @param currentUser
 * @returns {JSX.Element}
 * @constructor
 */
const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount, currentUser }) => {
  const [showUnreadOnly, setShowUnreadOnly] = useState(false);
  const [notifications, setNotifications] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const { isConnected, markNotificationRead, markAllNotificationsRead } = useSocket();
  const notificationRef = useRef(null);
  const userAssociationId = bodyshop?.associations?.[0]?.id;
  const isEmployee = useIsEmployee(bodyshop, currentUser);

  const baseWhereClause = useMemo(() => {
    return { associationid: { _eq: userAssociationId } };
  }, [userAssociationId]);

  const whereClause = useMemo(() => {
    return showUnreadOnly ? { ...baseWhereClause, read: { _is_null: true } } : baseWhereClause;
  }, [baseWhereClause, showUnreadOnly]);

  const {
    data,
    fetchMore,
    loading: queryLoading,
    refetch
  } = useQuery(GET_NOTIFICATIONS, {
    variables: {
      limit: INITIAL_NOTIFICATIONS,
      offset: 0,
      where: whereClause
    },
    fetchPolicy: "cache-and-network",
    notifyOnNetworkStatusChange: true,
    pollInterval: isConnected ? 0 : day.duration(NOTIFICATION_POLL_INTERVAL_SECONDS, "seconds").asMilliseconds(),
    skip: !userAssociationId || !isEmployee,
    onError: (err) => {
      console.error(`Error polling Notifications: ${err?.message || ""}`);
      setTimeout(() => refetch(), day.duration(2, "seconds").asMilliseconds());
    }
  });

  useEffect(() => {
    const handleClickOutside = (event) => {
      // Prevent open + close behavior from the header
      if (event.target.closest("#header-notifications")) return;
      if (visible && notificationRef.current && !notificationRef.current.contains(event.target)) {
        onClose();
      }
    };
    document.addEventListener("mousedown", handleClickOutside);
    return () => document.removeEventListener("mousedown", handleClickOutside);
  }, [visible, onClose]);

  useEffect(() => {
    if (data?.notifications && isEmployee) {
      const processedNotifications = data.notifications
        .map((notif) => {
          let scenarioText;
          let scenarioMeta;
          try {
            scenarioText = notif.scenario_text ? JSON.parse(notif.scenario_text) : [];
            scenarioMeta = notif.scenario_meta ? JSON.parse(notif.scenario_meta) : {};
          } catch (e) {
            console.error("Error parsing JSON for notification:", notif.id, e);
            scenarioText = [notif.fcm_text || "Invalid notification data"];
            scenarioMeta = {};
          }
          if (!Array.isArray(scenarioText)) scenarioText = [scenarioText];
          const roNumber = notif.job.ro_number;
          if (!Array.isArray(scenarioMeta)) scenarioMeta = [scenarioMeta];
          return {
            id: notif.id,
            jobid: notif.jobid,
            associationid: notif.associationid,
            scenarioText,
            scenarioMeta,
            roNumber,
            created_at: notif.created_at,
            read: notif.read,
            __typename: notif.__typename
          };
        })
        .sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
      setNotifications(processedNotifications);
    } else if (!isEmployee) {
      setNotifications([]); // Clear notifications if not an employee
    }
  }, [data, isEmployee]);

  const loadMore = useCallback(() => {
    if (!queryLoading && data?.notifications.length && isEmployee) {
      setIsLoading(true); // Show spinner during fetchMore
      fetchMore({
        variables: { offset: data.notifications.length, where: whereClause },
        updateQuery: (prev, { fetchMoreResult }) => {
          if (!fetchMoreResult) return prev;
          return {
            notifications: [...prev.notifications, ...fetchMoreResult.notifications]
          };
        }
      })
        .catch((err) => {
          console.error("Fetch more error:", err);
        })
        .finally(() => setIsLoading(false)); // Hide spinner when done
    }
  }, [data?.notifications?.length, fetchMore, queryLoading, whereClause, isEmployee]);

  const handleToggleUnreadOnly = (value) => {
    setShowUnreadOnly(value);
  };

  const handleMarkAllRead = useCallback(() => {
    if (!isEmployee) return; // Do nothing if not an employee
    setIsLoading(true);
    markAllNotificationsRead()
      .then(() => {
        const timestamp = new Date().toISOString();
        setNotifications((prev) => {
          const updatedNotifications = prev.map((notif) =>
            notif.read === null && notif.associationid === userAssociationId
              ? {
                  ...notif,
                  read: timestamp
                }
              : notif
          );
          // Filter out read notifications if in unread only mode
          return showUnreadOnly ? updatedNotifications.filter((notif) => !notif.read) : updatedNotifications;
        });
      })
      .catch((e) => console.error(`Error marking all notifications read: ${e?.message || ""}`))
      .finally(() => setIsLoading(false));
  }, [markAllNotificationsRead, userAssociationId, showUnreadOnly, isEmployee]);

  const handleNotificationClick = useCallback(
    (notificationId) => {
      setIsLoading(true);
      markNotificationRead({ variables: { id: notificationId } })
        .then(() => {
          const timestamp = new Date().toISOString();
          setNotifications((prev) => {
            const updatedNotifications = prev.map((notif) =>
              notif.id === notificationId && !notif.read ? { ...notif, read: timestamp } : notif
            );
            // Filter out the read notification if in unread only mode
            return showUnreadOnly ? updatedNotifications.filter((notif) => !notif.read) : updatedNotifications;
          });
        })
        .catch((e) => console.error(`Error marking notification read: ${e?.message || ""}`))
        .finally(() => setIsLoading(false));
    },
    [markNotificationRead, showUnreadOnly]
  );

  useEffect(() => {
    if (visible && !isConnected && isEmployee) {
      setIsLoading(true);
      refetch()
        .catch((err) => console.error(`Error re-fetching notifications: ${err?.message || ""}`))
        .finally(() => setIsLoading(false));
    }
  }, [visible, isConnected, refetch, isEmployee]);

  return (
    <NotificationCenterComponent
      ref={notificationRef}
      isEmployee={isEmployee}
      visible={visible}
      onClose={onClose}
      notifications={notifications}
      loading={isLoading}
      showUnreadOnly={showUnreadOnly}
      toggleUnreadOnly={handleToggleUnreadOnly}
      markAllRead={handleMarkAllRead}
      loadMore={loadMore}
      onNotificationClick={handleNotificationClick}
      unreadCount={unreadCount}
    />
  );
};

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

export default connect(mapStateToProps, null)(NotificationCenterContainer);
