import { useReducer, useRef } from 'react';
import useWebSocket from '@/hook/useWebSocket.tsx';
import {
    ChatMessage,
    JoinQuizZoneResponse,
    NextQuizResponse,
    Player,
    QuizZone,
    QuizZoneResultState,
    SomeoneSubmitResponse,
    SubmitResponse,
} from '@/types/quizZone.types.ts';
import atob from '@/utils/atob';

export type QuizZoneAction =
    | { type: 'init'; payload: QuizZone }
    | { type: 'join'; payload: JoinQuizZoneResponse }
    | { type: 'someone_join'; payload: Player }
    | { type: 'someone_leave'; payload: string }
    | { type: 'start'; payload: undefined }
    | { type: 'submit'; payload: SubmitResponse }
    | { type: 'someone_submit'; payload: SomeoneSubmitResponse }
    | { type: 'nextQuiz'; payload: NextQuizResponse }
    | { type: 'playQuiz'; payload: undefined }
    | { type: 'quizTimeout'; payload: undefined }
    | { type: 'finish'; payload: undefined }
    | { type: 'summary'; payload: QuizZoneResultState }
    | { type: 'chat'; payload: ChatMessage }
    | { type: 'leave'; payload: undefined };

export type chatAction = {
    type: 'chat';
    payload: ChatMessage;
};

type Reducer<S, A> = (state: S, action: A) => S;

const quizZoneReducer: Reducer<QuizZone, QuizZoneAction> = (state, action) => {
    const { type, payload } = action;

    switch (type) {
        case 'init':
            return {
                ...state,
                stage: payload.stage,
                title: payload.title,
                description: payload.description,
                quizCount: payload.quizCount,
                hostId: payload.hostId,
                currentPlayer: payload.currentPlayer,
                chatMessages: payload.chatMessages,
                currentQuiz:
                    payload.currentQuiz !== undefined
                        ? {
                              ...payload.currentQuiz,
                              question: atob(payload.currentQuiz?.question ?? ''),
                          }
                        : undefined,
                maxPlayers: payload.maxPlayers,
                players: [],
            };
        case 'join':
            return { ...state, players: payload.Players, offset: payload.offset };
        case 'someone_join':
            const isPlayerExist = state.players?.some((player) => player.id === payload.id);
            if (isPlayerExist) {
                return state; // 이미 존재하는 플레이어라면 상태 변경 없음
            }
            return { ...state, players: [...(state.players ?? []), payload] };
        case 'someone_leave':
            return {
                ...state,
                players: state.players?.filter((player) => player.id !== payload) ?? [],
            };
        case 'start':
            return {
                ...state,
                stage: 'LOBBY',
            };
        case 'submit':
            return {
                ...state,
                state: 'IN_PROGRESS',
                currentPlayer: {
                    ...state.currentPlayer,
                    state: 'SUBMIT',
                },
                chatMessages: payload.chatMessages,
                currentQuizResult: {
                    fastestPlayers: payload.fastestPlayerIds
                        .map((id) => state.players?.find((p) => p.id === id))
                        .filter((p) => !!p),
                    submittedCount: payload.submittedCount,
                    totalPlayerCount: payload.totalPlayerCount,
                },
            };
        case 'someone_submit':
            const { clientId, submittedCount } = payload;
            const player = state.players?.find((p) => p.id === clientId);
            const fastestPlayers = state.currentQuizResult?.fastestPlayers ?? [];

            return {
                ...state,
                currentQuizResult: {
                    ...state.currentQuizResult!,
                    fastestPlayers: [...fastestPlayers, player].slice(0, 3).filter((p) => !!p),
                    submittedCount,
                },
            };
        case 'nextQuiz':
            const { nextQuiz } = payload;

            return {
                ...state,
                stage: 'IN_PROGRESS',
                currentPlayer: {
                    ...state.currentPlayer,
                    state: 'WAIT',
                },
                currentQuiz: {
                    ...state.currentQuiz,
                    question: atob(nextQuiz.question),
                    currentIndex: nextQuiz.currentIndex,
                    playTime: nextQuiz.playTime,
                    startTime: nextQuiz.startTime,
                    deadlineTime: nextQuiz.deadlineTime,
                    quizType: 'SHORT',
                },
                currentQuizResult: {
                    ...state.currentQuizResult,
                    ...payload.currentQuizResult,
                },
            };
        case 'playQuiz':
            return {
                ...state,
                stage: 'IN_PROGRESS',
                currentPlayer: {
                    ...state.currentPlayer,
                    state: 'PLAY',
                },
            };
        case 'quizTimeout':
            return {
                ...state,
                state: 'IN_PROGRESS',
                currentPlayer: {
                    ...state.currentPlayer,
                    state: 'WAIT',
                },
            };
        case 'finish':
            return {
                ...state,
                stage: 'RESULT',
                isLastQuiz: true,
            };
        case 'summary':
            return {
                ...state,
                stage: 'RESULT',
                score: payload.score,
                submits: payload.submits,
                quizzes: payload.quizzes,
                ranks: payload.ranks,
                endSocketTime: payload.endSocketTime,
            };
        case 'chat':
            return {
                ...state,
                chatMessages: [...(state.chatMessages || []), payload],
            };
        case 'leave':
            return {
                isQuizZoneEnd: true,
                ...state,
            };

        default:
            return state;
    }
};

export const chatMessagesReducer: Reducer<ChatMessage[], chatAction> = (chatMessages, action) => {
    const { type, payload } = action;

    switch (type) {
        case 'chat':
            return [...chatMessages, payload];
        default:
            return chatMessages;
    }
};

/**
 * @description 다중 사용자 퀴즈 게임 환경에서 퀴즈존 상태와 상호작용을 관리하는 커스텀 훅입니다.
 *
 * @example
 * ```tsx
 * const QuizComponent = () => {
 *   const {
 *     quizZoneState,
 *     initQuizZoneData,
 *     submitQuiz,
 *     startQuiz,
 *     playQuiz
 *   } = useQuizZone();
 *
 *   // 퀴즈 초기화
 *   useEffect(() => {
 *     initQuizZoneData(initialData);
 *   }, []);
 *
 *   // 답안 제출
 *   const handleSubmit = (answer: string) => {
 *     submitQuiz(answer);
 *   };
 * ```
 *
 * @returns {Object} 퀴즈존 상태와 제어 함수들을 포함하는 객체
 * @returns {QuizZone} .quizZoneState - 현재 퀴즈존의 상태
 * @returns {Function} .initQuizZoneData - 초기 데이터로 퀴즈존을 초기화하는 함수
 * @returns {Function} .submitQuiz - 현재 퀴즈에 대한 답안을 제출하는 함수
 * @returns {Function} .startQuiz - 퀴즈 세션을 시작하는 함수
 * @returns {Function} .playQuiz - 퀴즈 상태를 플레이 모드로 변경하는 함수
 */

const useQuizZone = (quizZoneId: string, handleReconnect?: () => void) => {
    const initialQuizZoneState: QuizZone = {
        stage: 'LOBBY',
        currentPlayer: {
            id: '',
            nickname: '',
        },
        title: '',
        description: '',
        hostId: '',
        quizCount: 0,
        players: [],
        score: 0,
        submits: [],
        quizzes: [],
        chatMessages: [],
        maxPlayers: 0,
        offset: 0,
    };

    const joinRequestTimeRef = useRef(0);
    const [quizZoneState, dispatch] = useReducer(quizZoneReducer, initialQuizZoneState);

    const messageHandler = (event: MessageEvent) => {
        const { event: QuizZoneEvent, data } = JSON.parse(event.data);
        if (QuizZoneEvent === 'join') {
            // console.log(data);
            const receiveTime = new Date().getTime();
            console.log(receiveTime);
            const serverTime = data.serverTime;
            console.log('serverTime', serverTime);
            // offset = 서버시간 - (요청시간 + 응답시간)/2

            const offset = serverTime - (joinRequestTimeRef.current + receiveTime) / 2;
            console.log('offset', offset);
            const newData: JoinQuizZoneResponse = { Players: data.data, offset };
            dispatch({
                type: QuizZoneEvent,
                payload: newData,
            });
            return;
        }
        dispatch({
            type: QuizZoneEvent,
            payload: data,
        });
    };

    const getServerTime = () => {
        return new Date().getTime() + quizZoneState.offset;
    };

    const handleFinish = () => {
        dispatch({ type: 'leave', payload: undefined });
    };

    const wsUrl = `${import.meta.env.VITE_WS_URL}/play`;
    const { beginConnection, sendMessage, closeConnection } = useWebSocket({
        wsUrl,
        messageHandler,
        handleFinish,
        handleReconnect,
    });

    //initialize QuizZOne
    const initQuizZoneData = async (quizZone: QuizZone) => {
        dispatch({ type: 'init', payload: quizZone });
        beginConnection();
        joinQuizZone({ quizZoneId });
    };

    //퀴즈 시작 함수
    const startQuiz = () => {
        const message = JSON.stringify({ event: 'start' });
        sendMessage(message);
    };

    //퀴즈존 나가기 함수
    const exitQuiz = () => {
        const message = JSON.stringify({ event: 'leave' });
        sendMessage(message);
    };

    // 퀴즈 제출 함수
    const submitQuiz = (answer: string) => {
        const message = JSON.stringify({
            event: 'submit',
            data: {
                answer,
                index: quizZoneState.currentQuiz?.currentIndex,
                submittedAt: getServerTime(),
            },
        });
        sendMessage(message);
    };

    const playQuiz = () => {
        dispatch({ type: 'playQuiz', payload: undefined });
    };

    const joinQuizZone = ({ quizZoneId }: any) => {
        const requestTime = new Date().getTime();
        joinRequestTimeRef.current = requestTime;
        const message = JSON.stringify({ event: 'join', data: { quizZoneId } });
        sendMessage(message);
    };

    const sendChat = (chatMessage: any) => {
        sendMessage(JSON.stringify({ event: 'chat', data: chatMessage }));
    };

    return {
        quizZoneState,
        initQuizZoneData,
        submitQuiz,
        startQuiz,
        playQuiz,
        closeConnection,
        exitQuiz,
        joinQuizZone,
        sendChat,
        getServerTime,
    };
};

export default useQuizZone;


//play.gateway.ts

@SubscribeMessage('join')
    async join(
        @ConnectedSocket() client: WebSocketWithSession,
        @MessageBody() quizJoinDto: QuizJoinDto,
    ) {
        const sessionId = client.session.id;
        const { quizZoneId } = quizJoinDto;

        const { currentPlayer, players } = await this.playService.joinQuizZone(
            quizZoneId,
            sessionId,
        );

        const { id, nickname } = currentPlayer;
        const playerIds = players.map((player) => player.id);
        const data = players.map(({ id, nickname }) => ({
            id,
            nickname,
        }));

        if (this.clients.has(sessionId) && this.clients.get(sessionId).quizZoneId === quizZoneId) {
            this.clients.set(sessionId, { quizZoneId, socket: client });
            return {
                event: 'join',
                data: { data, serverTime: Date.now() },
            };
        }
        this.clients.set(sessionId, { quizZoneId, socket: client });

        this.broadcast(playerIds, 'someone_join', { id, nickname });

        return {
            event: 'join',
            data: { data, serverTime: Date.now() },
        };
    }