// src/hooks/useChatSocket.js

import { useApi } from '@/api';
import { IMessage } from '@/api/resources/ChatMessages';
// import { useAuth } from '@/auth';
import routes from '@/routes';
// import { getZodiacSignByDate } from '@/services/zodiac-sign';
import { actions, selectors } from '@/store';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { io, Socket } from 'socket.io-client';
import { ClientToServerEvents, IBalanceUpdated, ICurrentBalance, IResponse, ISessionStarted, ServerToClientEvents } from './data';
import { sleep } from '@/services/date';

const compareDates = (date1: string, date2: string) => {
    return new Date(date1).getTime() - new Date(date2).getTime();
}

const isAvailableDateUsing = (dateEnd?: string) => {
    if (!dateEnd) {
        return false
    }
    return compareDates(dateEnd, new Date().toString()) > 0
    // return (new Date(dateEnd).getTime() - new Date().getTime()) > 0
}


const useChatSocket = (userId: string, chatId: string) => {
    const dispatch = useDispatch();
    const token = useSelector(selectors.selectToken)
    const api = useApi();
    const [socket, setSocket] = useState<Socket<ServerToClientEvents, ClientToServerEvents> | null>(null);
    const [messages, setMessages] = useState<IMessage[]>([]);
    const [balance, setBalance] = useState<ICurrentBalance>();
    const [session, setSession] = useState<ISessionStarted | null>();
    // const [dateEnded, setDateEnded] = useState<string>("");
    const [isLoading, setIsLoading] = useState(true);

    // For first modal shown
    const [initialBalance, setInitialBalance] = useState<number | null>(null);

    const [isLoadingSelfMessage, setIsLoadingSelfMessage] = useState(false);
    const [isLoadingAdvisorMessage, setIsLoadingAdvisorMessage] = useState(false);
    const [isAvailableChatting, setIsAvailableChatting] = useState(true);

    const dateEndChattingFromStore = useSelector(selectors.selectDateEndChatting);

    const dateEndChatting = useMemo(() => {
        // return "2024-11-17T00:26:19.329Z"
        if (dateEndChattingFromStore?.length) return dateEndChattingFromStore;
        if (balance?.maxFinishedAt) return balance.maxFinishedAt;
        if (session?.maxFinishedAt) return session.maxFinishedAt;
        return "2000-01-01T00:00:00.290Z"
    }, [balance, dateEndChattingFromStore, session]);

    const messagesAfterEnd = useMemo(() => {
        return messages.filter((message) => compareDates(message.createdDate, dateEndChatting) >= 0)
    }, [dateEndChatting, messages])

    const isAvailableChattingByMessages = useMemo(() => {
        return !messagesAfterEnd.find((message) => message.role === "assistant") || isAvailableChatting
    }, [isAvailableChatting, messagesAfterEnd])

    // send
    const joinChat = useCallback((chatId: string) => {
        if (chatId && socket) {
            socket.emit('join_chat', {
                chatId
            });
        }
    }, [socket]);

    const startSession = useCallback((chatId: string) => {
        if (chatId && socket && !session) {
            socket.emit('start_session', { chatId });
        }
    }, [session, socket]);

    const sendMessage = useCallback((message: string) => {
        if (chatId && socket) {
            setIsLoadingAdvisorMessage(true);
            setIsLoadingSelfMessage(true);
            socket.emit('send_message', { chatId, message });
        }
    }, [chatId, socket]);

    const readMessage = useCallback((messagesId: string[]) => {
        if (chatId && socket) {
            socket.emit('read_message', { messages: messagesId });
        }
    }, [chatId, socket]);

    const endSession = useCallback((chatId: string) => {
        if (chatId && socket) {
            socket.emit('end_session', { chatId });
        }
    }, [socket]);

    const leaveChat = useCallback((chatId: string) => {
        if (chatId && socket) {
            socket.emit('leave_chat', { chatId });
        }
    }, [socket]);

    const fetchBalance = useCallback((chatId: string) => {
        if (chatId && socket) {
            socket.emit('fetch_balance', { chatId });
        }
    }, [socket]);


    // receive
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const chatJoined = useCallback((_response: IResponse<null>) => {
        console.log("chatJoined");
    }, [])

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const chatLeft = useCallback((_response: IResponse<boolean>) => {
        console.log("chatLeft");
    }, [])

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const balanceUpdated = useCallback((_response: IResponse<IBalanceUpdated>) => {
        fetchBalance(chatId);
    }, [chatId, fetchBalance])

    const receiveMessage = useCallback((message: IMessage[]) => {
        if (!message?.[0]) return;
        
        if (message[0].role === "user") {
            setIsLoadingSelfMessage(false);
        }
        if (message[0].role === "assistant") {
            setIsLoadingAdvisorMessage(false);
        }
        setMessages((prev) => {
            if (prev.some(msg => msg.id === message[0].id)) {
                return prev;
            }
            return [...prev, message[0]];
        });
    }, []);

    const currentBalance = useCallback((balance: IResponse<ICurrentBalance>) => {
        setBalance(balance?.data);
    }, [])

    const sessionStarted = useCallback((response: IResponse<ISessionStarted>) => {
        setSession(response?.data);
    }, [])

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const sessionEnded = useCallback((_response: IResponse<boolean>) => {
        setSession(null);
    }, [])

    // logic
    const init = useCallback(async () => {
        if (!chatId || !userId) {
            return;
        }

        const messages = await api.getChatMessages({
            token,
            chatId
        })

        // routes.server.chatSocket()
        const newSocket = io(routes.server.chatSocket(), {
            query: { userId }
        });

        setSocket(newSocket);

        setMessages(messages?.messages || []);

        newSocket.on('chat_joined', (response: IResponse<null>) => {
            chatJoined(response);
        });

        newSocket.on('receive_message', (message: IMessage[]) => {
            receiveMessage(message);
        });

        newSocket.on('current_balance', (balance: IResponse<ICurrentBalance>) => {
            currentBalance(balance);
        });

        newSocket.on('session_started', (response: IResponse<ISessionStarted>) => {
            sessionStarted(response);
        });

        newSocket.on('session_ended', (response: IResponse<boolean>) => {
            sessionEnded(response);
        });

        newSocket.on('chat_left', (response: IResponse<boolean>) => {
            chatLeft(response);
        });

        newSocket.on('balance_updated', (response: IResponse<IBalanceUpdated>) => {
            balanceUpdated(response);
        });

        return () => {
            newSocket.disconnect();
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    useEffect(() => {
        setIsLoading(true);
        init();
    }, [init]);

    // useEffect(() => {
    //     if (!socket) return setIsAvailableChatting(false);
    //     joinChat(chatId);
    //     fetchBalance(chatId);
    // }, [chatId, fetchBalance, joinChat, socket]);

    useEffect(() => {
        const interval = setInterval(() => {
            fetchBalance(chatId);
        }, 5000);
        return () => clearInterval(interval);
    }, [chatId, fetchBalance, session]);

    useEffect(() => {
        if (!balance) return setIsAvailableChatting(false);
        if (initialBalance === null) setInitialBalance(balance.balance);
        if (balance.balance > 0 && !session) {
            startSession(chatId);
        }
        if (balance.balance <= 0 && session) {
            endSession(chatId);
        }
        setIsLoading(false);
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [balance, chatId, endSession, session, startSession]);

    useEffect(() => {
        if (!session) return setIsAvailableChatting(false);
        setIsLoading(true);
        const checkIsAvailableChatting: () => Promise<void> = async () => {
            const isAvailableDate = isAvailableDateUsing(session?.maxFinishedAt);
            if (isAvailableDate) {
                setIsAvailableChatting(true);
                setIsLoading(false);
                await sleep(1000);
                return checkIsAvailableChatting();
            }
            setIsAvailableChatting(false);
            setIsLoading(false);
        }

        checkIsAvailableChatting();
    }, [chatId, endSession, session])

    useEffect(() => {
        if (session?.maxFinishedAt?.length) {
            dispatch(actions.chat.updateDateEndChatting(session.maxFinishedAt));
        }
    }, [dispatch, session]);

    useEffect(() => {
        if (!socket) return;

        const handleReconnect = () => {
            setIsLoadingSelfMessage(false);
            setIsLoadingAdvisorMessage(false);
            joinChat(chatId);
            fetchBalance(chatId);
        };

        socket.on('connect', handleReconnect);
        socket.on('disconnect', () => {
            setIsLoadingSelfMessage(false);
            setIsLoadingAdvisorMessage(false);
        });

        return () => {
            socket.off('connect', handleReconnect);
            socket.off('disconnect');
        };
    }, [socket, chatId, joinChat, fetchBalance]);

    // clean up
    useEffect(() => {
        const handleBeforeUnload = () => {
            if (socket) {
                endSession(chatId);
                leaveChat(chatId);
                socket.disconnect();
            }
        };

        window.addEventListener('beforeunload', handleBeforeUnload);

        return () => {
            window.removeEventListener('beforeunload', handleBeforeUnload);
            handleBeforeUnload();
        };
    }, [socket, chatId, endSession, leaveChat]);

    return useMemo(() => ({
        isLoading,
        isLoadingSelfMessage,
        isLoadingAdvisorMessage,
        socket,
        messages,
        balance,
        session,
        isAvailableChatting,
        messagesAfterEnd,
        isAvailableChattingByMessages,
        initialBalance,
        sendMessage,
        readMessage
    }), [
        isLoading,
        isLoadingSelfMessage,
        isLoadingAdvisorMessage,
        socket,
        messages,
        balance,
        session,
        isAvailableChatting,
        messagesAfterEnd,
        isAvailableChattingByMessages,
        initialBalance,
        sendMessage,
        readMessage
    ]);
};

export default useChatSocket;
