// services/meetingService.js
const { v4: uuidv4 } = require('uuid');
const { Op } = require('sequelize');
const sequelize = require('../config/sequelize'); // 트랜잭션 관리를 위해 sequelize 인스턴스 필요
const { Meeting, MeetingParticipant, User, Schedule, Invite, Friend, FcmToken } = require('../models');
const ChatRooms = require('../schemas/chatRooms');
const MeetingResponseDTO = require('../dtos/MeetingResponseDTO');
const MeetingDetailResponseDTO = require('../dtos/MeetingDetailResponseDTO');
const CreateMeetingRequestDTO = require('../dtos/CreateMeetingRequestDTO');
const ScheduleService = require('./scheduleService'); // ScheduleService 임포트
const chatService = require('./chatService');
const amqp = require('amqplib'); // RabbitMQ 연결

class MeetingService {

    async publishToQueue(queue, message) {
        const connection = await amqp.connect(process.env.RABBITMQ_URL || 'amqp://localhost');
        const channel = await connection.createChannel();
        await channel.assertQueue(queue, { durable: true });
        channel.sendToQueue(queue, Buffer.from(JSON.stringify(message)));
        console.log(`Message sent to queue ${queue}:`, message);
        setTimeout(() => connection.close(), 500); // 연결 닫기
    }

    /**
     * 현재 시간을 time_idx로 변환하는 유틸리티 함수
     * 월요일부터 일요일까지 15분 단위로 타임 인덱스를 할당
     * 현재 시간의 타임 인덱스 (0 ~ 671)
     */
    getCurrentTimeIdx() {
        const today = new Date();
        const jsDayOfWeek = today.getDay(); // 0=Sunday, 1=Monday, ..., 6=Saturday
        const adjustedDayOfWeek = (jsDayOfWeek + 6) % 7; // 0=Monday, ..., 6=Sunday
        const hours = today.getHours();
        const minutes = today.getMinutes();
        const timeIdx = hours * 4 + Math.floor(minutes / 15); // 15분 단위 인덱스
        const totalIdx = adjustedDayOfWeek * 96 + timeIdx; // 주 전체 인덱스
        return totalIdx;
    }

    // async sendMeetingPushNotificationRequest(meetingTitle, inviterName, inviteeTokens) {
    //     const event = {
    //         meetingTitle,
    //         inviterName,
    //         inviteeTokens,
    //     };
    //     await this.publishToQueue('meeting_push_notifications', event); // meeting_push_notifications 큐에 메시지 발행
    // }
    async sendMeetingPushNotificationRequest(meetingTitle, inviterName, inviteeTokens, type) {
        const event = {
            meetingTitle,
            inviterName,
            inviteeTokens,
            type, // 이벤트 타입 ('invite' 또는 'join')
        };
        await this.publishToQueue('meeting_push_notifications', event); // 큐에 메시지 발행
    }


    async createMeeting(meetingData) {
        const createMeetingDTO = new CreateMeetingRequestDTO(meetingData);
        createMeetingDTO.validate();

        const {
            title,
            description,
            time_idx_start,
            time_idx_end,
            location,
            time_idx_deadline,
            type,
            created_by,
            max_num,
        } = meetingData;

        // 사용자와 FCM 토큰 조회
        const user = await this._findUserWithFcmTokens(created_by);
        console.log("user", user);
        const userFcmTokens = user.fcmTokenList.map((fcmToken) => fcmToken.token);


        const hasConflict = await ScheduleService.checkScheduleOverlapByTime(
            created_by,
            time_idx_start,
            time_idx_end
        );
        if (hasConflict) {
            throw new Error('해당 시간에 이미 다른 스케줄이 있습니다.');
        }

        // 트랜잭션을 사용하여 모임 생성과 스케줄 추가를 원자적으로 처리
        return await Meeting.sequelize.transaction(async (transaction) => {
            const chatRoomData = this._constructChatRoomData(title, user, userFcmTokens);
            const chatRoomResponse = await chatService.createChatRoom(chatRoomData);

            if (!chatRoomResponse.success) {
                throw new Error('채팅방 생성 실패');
            }

            const chatRoomId = chatRoomResponse.chatRoomId;

            // 모임 생성
            const newMeeting = await Meeting.create(
                {
                    title,
                    description,
                    time_idx_start,
                    time_idx_end,
                    location,
                    time_idx_deadline,
                    type,
                    created_by,
                    chatRoomId,
                    max_num, // max_num 추가
                    cur_num: 1, // 생성자 자신 포함
                },
                { transaction }
            );

            // 모임 참가자 추가 (생성자 자신)
            await MeetingParticipant.create(
                {
                    meeting_id: newMeeting.id,
                    user_id: created_by,
                },
                { transaction }
            );

            const time_indices = Array.from(
                { length: parseInt(time_idx_end) - parseInt(time_idx_start) + 1 },
                (_, i) => (parseInt(time_idx_start) + i).toString()
            );
            await ScheduleService.createSchedules({
                userId: created_by,
                title: `번개 모임: ${title}`,
                is_fixed: false,
                time_indices: time_indices,
            }, transaction);

            // 친구 초대 로직 호출
            const invitedFriendIds = await this.sendInvites({
                meetingId: newMeeting.id,
                creatorId: created_by,
                time_idx_start,
                time_idx_end,
            }, transaction);

            // 친구 목록에서 FCM 토큰 추출
            const inviteeTokens = await FcmToken.findAll({
                where: {
                    userId: { [Op.in]: invitedFriendIds },
                },
                attributes: ['token'],
            }).then(tokens => tokens.map(token => token.token));

            // RabbitMQ 메시지 발행 (푸시 알림 요청)
            if (inviteeTokens.length > 0) {
                await this.sendMeetingPushNotificationRequest(
                    title,
                    user.name,
                    inviteeTokens,
                    'invite'
                );
            }

            const chatRoom = await ChatRooms.findOne({ chatRoomId: chatRoomId });

            if (chatRoom) {
                console.log("채팅방 찾음");
                this._addParticipantToChatRoom(chatRoom, user, userFcmTokens);
            }

            return { meeting_id: newMeeting.id, chatRoomId, invitedFriendIds };
        });
    }

    async sendInvites({ meetingId, creatorId, time_idx_start, time_idx_end }, transaction) {
        // 1. 친구 목록 가져오기 (ACCEPTED 상태)
        const friends = await Friend.findAll({
            where: {
                [Op.or]: [
                    { requester_id: creatorId, status: 'ACCEPTED' },
                    { receiver_id: creatorId, status: 'ACCEPTED' },
                ],
            },
            transaction,
        });

        const friendIds = friends.map(friend =>
            friend.requester_id === creatorId ? friend.receiver_id : friend.requester_id
        );

        if (friendIds.length === 0) {
            // 친구가 없거나 모든 친구가 초대받지 못함
            return [];
        }
        const schedules = await Schedule.findAll({
            where: {
                user_id: { [Op.in]: friendIds },
                time_idx: {
                    [Op.between]: [time_idx_start, time_idx_end],
                },
            },
            transaction,
        });

        // 스케줄이 겹치는 친구 ID를 추출
        const conflictedFriendIds = schedules.map(schedule => schedule.user_id);

        // 스케줄이 겹치지 않는 친구 ID 필터링
        const availableFriendIds = friendIds.filter(friendId => !conflictedFriendIds.includes(friendId));

        if (availableFriendIds.length === 0) {
            // 스케줄이 겹치는 친구가 모두 있음
            return [];
        }
        const invitePromises = availableFriendIds.map(inviteeId => {
            return Invite.create({
                meeting_id: meetingId,
                inviter_id: creatorId,
                invitee_id: inviteeId,
                status: 'PENDING',
            }, { transaction });
        });

        await Promise.all(invitePromises);

        return availableFriendIds;
    }


    async joinMeeting(meetingId, userId) {
        const meeting = await Meeting.findByPk(meetingId);
        console.log(`참여하려는 모임: ${JSON.stringify(meeting)}`);
        if (!meeting) {
            throw new Error('모임을 찾을 수 없습니다.');
        }
        if (meeting.type === 'CLOSE') {
            throw new Error('이미 마감된 모임입니다.');
        }
        if (meeting.time_idx_deadline !== undefined) {
            const currentTimeIdx = this.getCurrentTimeIdx(); // 현재 시간 인덱스
            if (currentTimeIdx >= meeting.time_idx_deadline) {
                throw new Error('참가 신청이 마감되었습니다.');
            }
        }
        const existingParticipant = await MeetingParticipant.findOne({
            where: { meeting_id: meetingId, user_id: userId },
        });
        if (existingParticipant) {
            throw new Error('이미 참가한 사용자입니다.');
        }

        // 트랜잭션을 사용하여 참가자 추가 및 스케줄 업데이트를 원자적으로 처리
        await sequelize.transaction(async (transaction) => {
            if (meeting.cur_num >= meeting.max_num) {
                throw new Error("모임 인원이 모두 찼습니다.");
            }
            // 스케줄 충돌 확인
            const hasConflict = await ScheduleService.checkScheduleOverlapByTime(
                userId,
                meeting.time_idx_start,
                meeting.time_idx_end,
                transaction
            );
            console.log(`스케줄 충돌 결과: ${hasConflict}`);
            if (hasConflict) {
                throw new Error("스케줄이 겹칩니다. 다른 모임에 참가하세요.");
            }

            await MeetingParticipant.create(
                { meeting_id: meetingId, user_id: userId },
                { transaction }
            );

            const time_indices = Array.from(
                { length: meeting.time_idx_end - meeting.time_idx_start + 1 },
                (_, i) => meeting.time_idx_start + i
            );

            await ScheduleService.createSchedules({
                userId: userId,
                title: `번개 모임: ${meeting.title}`,
                is_fixed: false,
                time_indices: time_indices,
            }, transaction);

            // 채팅방 참가 (MongoDB)
            const user = await User.findOne({
                where: { id: userId },
                include: [
                    {
                        model: FcmToken,
                        as: 'fcmTokenList', // FCM 토큰 가져오기
                        attributes: ['token'],
                    },
                ],
                transaction
            });

            const userFcmTokens = user.fcmTokenList.map((token) => token.token);

            const chatRoom = await ChatRooms.findOne({
                chatRoomId: meeting.chatRoomId,
            });

            console.log("여기까지");
            console.log("user.name", user.name);
            console.log("참가하는 유저 fcm", userFcmTokens);
            if (chatRoom && !chatRoom.participants.includes(user.name)) {
                // 참가자 추가
                chatRoom.participants.push({
                    name: user.name,
                    fcmTokens: userFcmTokens, // FCM 토큰 추가
                });
                chatRoom.isOnline.set(user.name, false);
                chatRoom.lastReadAt.set(user.name, new Date());
                chatRoom.lastReadLogId.set(user.name, null);

                const joinMessage = {
                    message: `${nickname}님이 참가했습니다.`,
                    timestamp: new Date(),
                    type: 'join'
                };

                chatRoom.messages.push(joinMessage);

                // 기존 참가자 FCM 토큰 가져오기
                const otherParticipants = chatRoom.participants.filter(participant => participant.name !== user.name);
                const otherParticipantTokens = otherParticipants.flatMap(participant => participant.fcmTokens);

                if (otherParticipantTokens.length > 0) {
                    // RabbitMQ 메시지 발행
                    await this.sendMeetingPushNotificationRequest(
                        meeting.title,
                        user.name,
                        otherParticipantTokens,
                        'join'
                    );
                }

                await chatRoom.save();
            }

            // 현재 인원 수 증가
            await meeting.increment("cur_num", { by: 1, transaction });
        });
    }


    async getMeetings(userId, pagination) {
        const { limit = 20, offset = 0 } = pagination;

        try {
            const meetings = await Meeting.findAll({
                attributes: [
                    'id', 'title', 'description',
                    'time_idx_start', 'time_idx_end',
                    'location', 'time_idx_deadline',
                    'type', 'max_num', 'cur_num',
                    'created_at'
                ],
                include: [
                    {
                        model: User,
                        as: 'creator',
                        attributes: ['name'],
                        required: false
                    }
                ],
                order: [['created_at', 'DESC']],
                limit: limit + 1,
                offset,
                distinct: true
            });

            const hasNext = meetings.length > limit;
            const content = await Promise.all(
                meetings.slice(0, limit).map(async (meeting) => {
                    const isParticipant = await MeetingParticipant.findOne({
                        where: {
                            meeting_id: meeting.id,
                            user_id: userId
                        }
                    });

                    const hasConflict = await ScheduleService.checkScheduleOverlapByTime(
                        userId,
                        meeting.time_idx_start,
                        meeting.time_idx_end
                    );

                    return new MeetingResponseDTO(
                        meeting,
                        !!isParticipant,
                        hasConflict,
                        meeting.creator?.name || 'Unknown'
                    );
                })
            );

            return { content, hasNext };
        } catch (error) {
            console.error('getMeetings error:', error);
            throw new Error('Failed to fetch meetings');
        }
    }

    async getMyMeetings(userId, pagination) {
        const { limit = 20, offset = 0 } = pagination;

        try {
            const meetings = await Meeting.findAll({
                attributes: [
                    'id', 'title', 'description',
                    'time_idx_start', 'time_idx_end',
                    'location', 'time_idx_deadline',
                    'type', 'max_num', 'cur_num',
                    'created_at'
                ],
                include: [
                    {
                        model: MeetingParticipant,
                        as: 'participants',
                        where: { user_id: userId },
                        required: true
                    },
                    {
                        model: User,
                        as: 'creator',
                        attributes: ['name'],
                        required: false
                    }
                ],
                order: [['created_at', 'DESC']],
                limit: limit + 1,
                offset,
                distinct: true
            });

            const hasNext = meetings.length > limit;
            const content = meetings.slice(0, limit).map(meeting => {
                return new MeetingResponseDTO(
                    meeting,
                    true,  // 참여자로 조회했으므로 항상 true
                    false, // 이미 참여 중인 미팅이므로 충돌 체크 불필요
                    meeting.creator?.name || 'Unknown'
                );
            });

            return { content, hasNext };
        } catch (error) {
            console.error('getMyMeetings error:', error);
            throw new Error('Failed to fetch my meetings');
        }
    }

    async getMeetingDetail(meetingId, userId) {
        const meeting = await Meeting.findByPk(meetingId, {
            include: [
                {
                    model: User,
                    as: "creator",
                    attributes: ["name"],
                },
                {
                    model: MeetingParticipant,
                    as: "participants",
                    include: [
                        {
                            model: User,
                            as: "user",
                            attributes: ["name", "email"],
                        }
                    ]
                }
            ]
        });

        if (!meeting) {
            throw new Error("모임을 찾을 수 없습니다.");
        }

        const hasConflict = await ScheduleService.checkScheduleOverlapByTime(
            userId,
            meeting.time_idx_start,
            meeting.time_idx_end
        );

        return new MeetingDetailResponseDTO(meeting, hasConflict);
    }

    /**
     * 번개 모임 마감
     */
    async closeMeeting(meetingId) {
        const meeting = await Meeting.findByPk(meetingId);

        if (!meeting) {
            throw new Error('모임을 찾을 수 없습니다.');
        }

        if (meeting.type === 'CLOSE') {
            throw new Error('이미 마감된 모임입니다.');
        }

        meeting.type = 'CLOSE';
        await meeting.save();
        return meeting;
    }

    // Helper functions
    async _findUserWithFcmTokens(userId) {
        const user = await User.findOne({
            where: { id: userId },
            include: [
                {
                    model: FcmToken,
                    as: 'fcmTokenList',
                    attributes: ['token'],
                },
            ],
        });

        if (!user) {
            throw new Error('사용자를 찾을 수 없습니다.');
        }

        return user;
    }

    _constructChatRoomData(title, user, userFcmTokens) {
        return {
            meeting_id: null,
            participants: [
                {
                    name: user.name,
                    fcmTokens: userFcmTokens || [],
                    isOnline: true,
                    lastReadAt: new Date(),
                    lastReadLogId: null,
                },
            ],
            chatRoomName: title,
        };
    }

    _addParticipantToChatRoom(chatRoom, user, userFcmTokens) {
        // Map 필드가 초기화되지 않은 경우 기본값 설정
        if (!chatRoom.isOnline) chatRoom.isOnline = new Map();
        if (!chatRoom.lastReadAt) chatRoom.lastReadAt = new Map();
        if (!chatRoom.lastReadLogId) chatRoom.lastReadLogId = new Map();

        // 참가자 추가 로직
        if (!chatRoom.participants.some(participant => participant.name === user.name)) {
            chatRoom.participants.push({ name: user.name, fcmTokens: userFcmTokens });
            chatRoom.isOnline.set(user.name, true);
            chatRoom.lastReadAt.set(user.name, new Date());
            chatRoom.lastReadLogId.set(user.name, null);
        }

        // 저장
        chatRoom.save();
    }

    async leaveMeeting(meetingId, userId) {
        const meeting = await Meeting.findByPk(meetingId);
        if (!meeting) {
            throw new Error('모임을 찾을 수 없습니다.');
        }

        await sequelize.transaction(async (transaction) => {
            // 참가자 확인
            const participant = await MeetingParticipant.findOne({
                where: {
                    meeting_id: meetingId,
                    user_id: userId
                },
                transaction
            });

            if (!participant) {
                throw new Error('참가하지 않은 모임입니다.');
            }

            // 생성자는 탈퇴할 수 없음
            if (meeting.created_by === userId) {
                throw new Error('모임 생성자는 탈퇴할 수 없습니다.');
            }

            // 참가자 제거
            await MeetingParticipant.destroy({
                where: {
                    meeting_id: meetingId,
                    user_id: userId
                },
                transaction
            });

            // 관련 스케줄 삭제
            await Schedule.destroy({
                where: {
                    user_id: userId,
                    title: `번개 모임: ${meeting.title}`,
                    time_idx: {
                        [Op.between]: [meeting.time_idx_start, meeting.time_idx_end]
                    }
                },
                transaction
            });

            // 채팅방에서 제거
            const chatRoom = await ChatRooms.findOne({
                chatRoomId: meeting.chatRoomId
            });
            if (chatRoom) {
                const user = await User.findByPk(userId);
                chatRoom.participants = chatRoom.participants.filter(p => p.name !== user.name);
                chatRoom.isOnline.delete(user.name);
                chatRoom.lastReadAt.delete(user.name);
                chatRoom.lastReadLogId.delete(user.name);
                await chatRoom.save();
            }

            // 현재 인원 수 감소
            await meeting.decrement('cur_num', { by: 1, transaction });
        });
    }

    async deleteMeeting(meetingId, userId) {
        const meeting = await Meeting.findByPk(meetingId);

        if (!meeting) {
            throw new Error('모임을 찾을 수 없습니다.');
        }

        if (meeting.created_by !== userId) {
            throw new Error('모임 생성자만 삭제할 수 있습니다.');
        }

        return await sequelize.transaction(async (transaction) => {
            const participants = await MeetingParticipant.findAll({
                where: { meeting_id: meetingId },
                attributes: ['user_id'],
                transaction
            });

            const participantIds = participants.map(p => p.user_id);

            // 모든 참가자의 스케줄 삭제
            await Schedule.destroy({
                where: {
                    user_id: { [Op.in]: participantIds },
                    title: `번개 모임: ${meeting.title}`,
                    time_idx: {
                        [Op.between]: [meeting.time_idx_start, meeting.time_idx_end]
                    }
                },
                transaction
            });

            await ChatRooms.deleteOne({ chatRoomId: meeting.chatRoomId });

            // 모임 관련 데이터 삭제
            await meeting.destroy({ transaction });
        });
    }
}

module.exports = new MeetingService();