import React, {PropsWithChildren, useContext, useEffect, useMemo, useState} from "react";
import * as readAPI from "../API/notifications";
import {toastInfo} from "../services/toaster";
import {EgovNotification} from "../models/EgovNotifications";
import authContext from "./auth-context";
import * as PusherPushNotifications from "@pusher/push-notifications-web";
import {beamsClient, pusher} from "../services/pusher";
import {toast} from "react-toastify";


interface NotificationsContextInterface {
    unread: number                  //count of unread notification
    updateUnread: () => void   //decrease unread count
    resetUnread: () => void      //set unread count to 0
    resetUnreadMessages: () => void    //clears the cached unread notification
    newMessage: { tag: string }    //the tag of the last notification received (used to dynamically demand ui changes)
    getUnreadMessages: () => Promise<EgovNotification[]> //gets the notification messages (cached in browser or from api)
    subscribeToBeams: () => void
    unSubscribeBeams: () => void
}

/**
 * HANDLES INFO RELATED TO notification.ms
 *
 * 1. stores number of unread egov-notifications (showed in header/sidebar)
 * and caches unread notifications showed in dropdown
 *
 * 2. Handles the subscriptions of user to push notifications (beams)
 * and live notifications/events (channels)
 * The instances are initialised in services/pusher
 * This context is about specific user subscription
 *
 * 3. Events are used to make realtime changes to the app
 * like updating the unread count
 * or fetching updated data
 *
 */

const NotificationsContext = React.createContext({} as NotificationsContextInterface);
export const NotificationsContextProvider: React.FC<PropsWithChildren> = (props) => {

    /**   unread messages count - used in bell */
    const [unread, setUnread] = useState(0);
    /**  cached unread messages */
    const [unreadMessages, setUnreadMessages] = useState<EgovNotification[] | null>(null)
    const {currentUser} = useContext(authContext)

    /** stores the category of the last new message, to dynamically demand updates, when we receive a specific live notification - This is used in ActiveVisitsWidget to render it when our visitor checks in */
    const [newMessage, setNewMessage] = useState<{ tag: string }>({tag: ''})


    /**
     * Pusher subscription allows live notifications, with a toaster
     * Each user subscribes to a personal channel and a broadcast channel
     * When a live notification arrives an event triggers and we catch it to show the toaster
     *
     */
    const subscribeToPusher = async () => {


        if (!pusher) {

            // console.warn('Pusher instance missing')
            // throw Error('Pusher instance is not set')
            return

        }
        if (!currentUser) return

        /**
         * subscribing to specific user channel
         */
        const channelName = currentUser.email.split("@")[0]
        const personalChannel = pusher.subscribe(channelName);

        personalChannel.bind('my-event', (data: { notification: EgovNotification }) => {


            if (!currentUser) return

            //set unread messages as null, since we need to fetch them again
            setUnreadMessages(null)
            setUnread((unread) => unread + 1)
            setNewMessage({tag: data.notification?.tag})
            toastInfo(<><b>{data.notification?.title}:</b> {data.notification?.body}</>)


        });

        const broadcastChannel = pusher.subscribe('broadcast-channel');


        broadcastChannel.bind('my-event', (data: { notification: EgovNotification }) => {


            if (!currentUser) return

            //set unread messages as null, since we need to fetch them again
            setUnreadMessages(null)
            setUnread((unread) => unread + 1)
            setNewMessage({tag: data.notification?.tag})
            // setNewMessage(dat)
            toastInfo('Έχετε ένα νέο μήνυμα')


        });

    }

    /**
     * Beams Subscriptions (Push Notifications) are tied to the browser (or mobile device)
     * User must allow them per browser
     * As long as you subscribed you will receive them even if the browsers closed
     * It also means that the same user can subscribe from different browsers in the same device
     *
     * You have to manually log out to unsubscribe
     */
    const subscribeToBeams = async (client: PusherPushNotifications.Client | null) => {


        try {
        if (client === null) {
            // console.warn('Beams client not set')
            throw Error('Unable to Subscribe to Beams. Client is unset')
        }

        if (!currentUser || !currentUser.id) return

        if (Notification.permission === 'denied') {
            toast.warn('This device has pushed notification disabled, please manually enable them and refresh the page')
        }

            /**
             * get current subbed user and check if it matches with current
             */
            const subUser = await client.getUserId()

            // eslint-disable-next-line eqeqeq
            if (subUser != currentUser.id.toString()) {
                /**
                 * get token for auth user
                 */
                const beamsTokenProvider = new PusherPushNotifications.TokenProvider({
                    url: process.env.REACT_APP_BFF + "/api/pusher/beams-auth",
                    credentials: 'include'
                });
                await client.stop()
                await client.start()
                await client.setUserId(currentUser.id.toString(), beamsTokenProvider)

            } else {
                /**
                 * if a device is already subscribed to beams but notification permissions have been revoked unsub the device
                 */
                // console.warn('Already subscribed to beams...')
                if (Notification.permission === 'denied') await client.stop()

            }
        } catch (e: any) {

            // console.error('Trying to sub to new beams...')
            // throw new Error(e);


        }

    }

    const unSubscribeBeams = async (client: PusherPushNotifications.Client | null) => {
        if (!client) return
        await client.stop()
    }

    const getUnreadMessages = async () => {
        if (unreadMessages) return unreadMessages
        const res = await readAPI.getUnreadMessages();
        // @ts-ignore
        setUnreadMessages(res.data.records);
        // @ts-ignore
        return res.data.records
    }

    useEffect(() => {

        /***
         *GET UNREAD MESSAGES COUNT
         */
        (async () => {
            const response = await readAPI.getUnreadMessages();
            // @ts-ignore
            setUnread(response.data.metadata.total_count);
            // @ts-ignore
            setUnreadMessages(response.data.records)

        })();


        /***
         * SETUP PUSHER
         */
        subscribeToPusher()
        //
        // beamsClient.stop()

        /**
         * SUBSRCIBE TO BEAMS IF USER HAS PUSH ENABLED
         */
        const isSubbed = currentUser?.beams_enabled
        if (isSubbed) {
            subscribeToBeams(beamsClient)
        }
        // else {
        //     if (beamsClient) beamsClient.stop()
        // }

// eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    const memoValue = useMemo(() => ({
            getUnreadMessages: getUnreadMessages,
            unread: unread,
            resetUnreadMessages: () => {
                setUnreadMessages(null)
            },
            newMessage: newMessage,
            subscribeToBeams: () => subscribeToBeams(beamsClient),
            unSubscribeBeams: () => unSubscribeBeams(beamsClient),
            updateUnread: () => setUnread((unread) => unread - 1), //marks message as undread =>   decreases counter
            resetUnread: () => setUnread(0) //mark all as read => unread count == 0
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    ), [unread])

    return (
        <NotificationsContext.Provider
            value={memoValue}>
            {props.children}
        </NotificationsContext.Provider>
    )
};

export default NotificationsContext;
