// test/meetingService.test.js

const { sequelize, User, Friend, Schedule, Meeting, MeetingParticipant, ChatRoom } = require('../models'); // models/index.js를 통해 임포트
const MeetingService = require('../services/meetingService');
const ScheduleService = require('../services/scheduleService'); // ScheduleService 임포트
const chatController = require('../controllers/chatController');

// Jest를 사용하여 chatController 모킹
jest.mock('../controllers/chatController', () => ({
    createChatRoomInternal: jest.fn()
}));

describe('MeetingService', () => {
    beforeAll(async () => {
        await sequelize.sync({ force: true });

        // 더미 사용자 생성
        await User.bulkCreate([
            { id: 1, name: 'Alice', email: 'alice@example.com' },
            { id: 2, name: 'Bob', email: 'bob@example.com' },
        ]);

        // 더미 친구 관계 생성
        await Friend.create({
            id: 1,
            requester_id: 1,
            receiver_id: 2,
            status: 'ACCEPTED',
        });

        // 더미 스케줄 생성
        await Schedule.bulkCreate([
            {
                id: 1,
                user_id: 1,
                title: "Alice's Fixed Schedule",
                start_time: new Date('2024-05-01T09:00:00Z'),
                end_time: new Date('2024-05-01T10:00:00Z'),
                is_fixed: true,
                expiry_date: null,
            },
            {
                id: 2,
                user_id: 1,
                title: "Alice's Flexible Schedule",
                start_time: new Date('2024-05-02T11:00:00Z'),
                end_time: new Date('2024-05-02T12:00:00Z'),
                is_fixed: false,
                expiry_date: new Date('2024-05-08T00:00:00Z'), // 다음 월요일
            },
        ]);

        // 기본적인 채팅방 생성 모킹 설정 (성공)
        chatController.createChatRoomInternal.mockResolvedValue({
            success: true,
            chatRoomId: 'chatroom-1234'
        });
    });

    afterAll(async () => {
        // 데이터베이스 연결 종료
        await sequelize.close();
    });

    beforeEach(() => {
        // 각 테스트 전에 mock 호출 이력 초기화
        jest.clearAllMocks();
    });

    describe('createMeeting', () => {
        test('번개 모임을 성공적으로 생성해야 한다', async () => {
            const meetingData = {
                title: 'Tech Talk',
                description: 'A discussion on the latest tech trends.',
                start_time: '2024-05-10T10:00:00Z',
                end_time: '2024-05-10T12:00:00Z',
                location: 'Online',
                deadline: '2024-05-09T23:59:59Z',
                type: 'OPEN',
                created_by: 1,
            };

            const result = await MeetingService.createMeeting(meetingData);
            expect(result).toHaveProperty('meeting_id');
            expect(result).toHaveProperty('chatRoomId');
            expect(result.chatRoomId).toBe('chatroom-1234');

            // 모임이 DB에 생성되었는지 확인
            const meeting = await Meeting.findByPk(result.meeting_id);
            expect(meeting).toBeDefined();
            expect(meeting.title).toBe('Tech Talk');

            // 참가자가 추가되었는지 확인
            const participant = await MeetingParticipant.findOne({
                where: { meeting_id: result.meeting_id, user_id: 1 }
            });
            expect(participant).toBeDefined();

            // 스케줄이 추가되었는지 확인
            const schedule = await Schedule.findOne({
                where: { user_id: 1, title: '번개 모임: Tech Talk' }
            });
            expect(schedule).toBeDefined();
            expect(schedule.start_time.toISOString()).toBe('2024-05-10T10:00:00.000Z');
            expect(schedule.end_time.toISOString()).toBe('2024-05-10T12:00:00.000Z');
            expect(schedule.is_fixed).toBe(true);
            expect(schedule.expiry_date).toBeNull();

            // chatController.createChatRoomInternal이 호출되었는지 확인
            expect(chatController.createChatRoomInternal).toHaveBeenCalledTimes(1);
            expect(chatController.createChatRoomInternal).toHaveBeenCalledWith({
                participants: ['Alice']
            });
        });

        test('사용자의 스케줄이 겹치는 경우 모임 생성을 실패해야 한다', async () => {
            // Alice의 기존 스케줄 생성 (이미 beforeAll에서 생성됨)
            const overlappingMeetingData = {
                title: 'Overlap Meeting',
                description: 'This meeting overlaps with Alice\'s fixed schedule.',
                start_time: '2024-05-01T09:30:00Z', // Alice's Fixed Schedule과 겹침
                end_time: '2024-05-01T11:00:00Z',
                location: 'Office',
                deadline: '2024-04-30T23:59:59Z',
                type: 'OPEN',
                created_by: 1,
            };

            await expect(MeetingService.createMeeting(overlappingMeetingData))
                .rejects
                .toThrow('스케줄이 겹칩니다. 다른 시간을 선택해주세요.');
        });

        test('모임 생성 시 스케줄의 유동 스케줄이 만료되면 스케줄 충돌이 발생하지 않아야 한다', async () => {
            const meetingData = {
                title: 'Morning Meeting',
                description: 'Meeting after flexible schedule expiry.',
                start_time: '2024-05-09T09:00:00Z', // Flexible Schedule의 expiry_date가 지난 시점
                end_time: '2024-05-09T10:00:00Z',
                location: 'Conference Room',
                deadline: '2024-05-08T23:59:59Z',
                type: 'OPEN',
                created_by: 1,
            };

            const result = await MeetingService.createMeeting(meetingData);
            expect(result).toHaveProperty('meeting_id');
            expect(result).toHaveProperty('chatRoomId');
            expect(result.chatRoomId).toBe('chatroom-1234');

            // 스케줄이 추가되었는지 확인
            const schedule = await Schedule.findOne({
                where: { user_id: 1, title: '번개 모임: Morning Meeting' }
            });
            expect(schedule).toBeDefined();
            expect(schedule.start_time.toISOString()).toBe('2024-05-09T09:00:00.000Z');
            expect(schedule.end_time.toISOString()).toBe('2024-05-09T10:00:00.000Z');
            expect(schedule.is_fixed).toBe(true);
            expect(schedule.expiry_date).toBeNull();
        });

        test('모임 생성 시 채팅방 생성 실패하면 에러를 던져야 한다', async () => {
            // chatController.createChatRoomInternal을 실패하도록 모킹
            chatController.createChatRoomInternal.mockResolvedValueOnce({
                success: false
            });

            const meetingData = {
                title: 'Failed ChatRoom Meeting',
                description: 'This meeting will fail to create chat room.',
                start_time: '2024-05-11T10:00:00Z',
                end_time: '2024-05-11T12:00:00Z',
                location: 'Online',
                deadline: '2024-05-10T23:59:59Z',
                type: 'OPEN',
                created_by: 1,
            };

            await expect(MeetingService.createMeeting(meetingData))
                .rejects
                .toThrow('채팅방 생성 실패');
        });

        test('모임 생성 시 유효하지 않은 데이터는 검증 에러를 던져야 한다', async () => {
            const invalidMeetingData = {
                title: '', // 빈 제목
                start_time: 'invalid-date',
                end_time: '2024-05-10T09:00:00Z', // start_time보다 이전
                type: 'INVALID_TYPE',
                created_by: -1, // 음수 ID
            };

            await expect(MeetingService.createMeeting(invalidMeetingData))
                .rejects
                .toThrow('Validation error');
        });
    });

    describe('joinMeeting', () => {
        test('번개 모임에 성공적으로 참가해야 한다', async () => {
            // Alice가 모임 생성
            const meetingData = {
                title: 'Networking Event',
                description: 'An event to network with professionals.',
                start_time: '2024-06-15T10:00:00Z',
                end_time: '2024-06-15T12:00:00Z',
                location: 'Conference Hall',
                deadline: '2024-06-14T23:59:59Z',
                type: 'OPEN',
                created_by: 1,
            };

            const { meeting_id } = await MeetingService.createMeeting(meetingData);

            // Bob가 모임에 참가
            await MeetingService.joinMeeting(meeting_id, 2);

            // 참가자가 추가되었는지 확인
            const participant = await MeetingParticipant.findOne({
                where: { meeting_id: meeting_id, user_id: 2 }
            });
            expect(participant).toBeDefined();

            // 스케줄이 추가되었는지 확인
            const schedule = await Schedule.findOne({
                where: { user_id: 2, title: '번개 모임: Networking Event' }
            });
            expect(schedule).toBeDefined();
            expect(schedule.start_time.toISOString()).toBe('2024-06-15T10:00:00.000Z');
            expect(schedule.end_time.toISOString()).toBe('2024-06-15T12:00:00.000Z');
            expect(schedule.is_fixed).toBe(true);
            expect(schedule.expiry_date).toBeNull();

            // chatController.createChatRoomInternal이 호출되지 않았는지 확인 (이미 모임 생성 시 호출됨)
            expect(chatController.createChatRoomInternal).toHaveBeenCalledTimes(1);
        });

        test('모임 참가 시 스케줄이 겹치는 경우 참가를 실패해야 한다', async () => {
            // Alice가 모임 생성
            const meetingData1 = {
                title: 'Morning Yoga',
                description: 'Start your day with yoga.',
                start_time: '2024-07-01T06:00:00Z',
                end_time: '2024-07-01T07:00:00Z',
                location: 'Gym',
                deadline: '2024-06-30T23:59:59Z',
                type: 'OPEN',
                created_by: 1,
            };

            const { meeting_id: meetingId1 } = await MeetingService.createMeeting(meetingData1);

            // Bob의 기존 스케줄 생성
            await ScheduleService.createSchedule({
                userId: 2,
                title: 'Work',
                start_time: new Date('2024-07-01T06:30:00Z'),
                end_time: new Date('2024-07-01T08:30:00Z'),
                is_fixed: true,
            }, null); // 트랜잭션 없이 생성

            // Bob가 모임에 참가 시도
            await expect(MeetingService.joinMeeting(meetingId1, 2))
                .rejects
                .toThrow('스케줄이 겹칩니다. 다른 모임에 참가하세요.');
        });

        test('모임 참가 시 이미 참가한 사용자는 다시 참가할 수 없어야 한다', async () => {
            // Alice가 모임 생성
            const meetingData = {
                title: 'Evening Run',
                description: 'Join us for an evening run.',
                start_time: '2024-08-20T18:00:00Z',
                end_time: '2024-08-20T19:00:00Z',
                location: 'Park',
                deadline: '2024-08-19T23:59:59Z',
                type: 'OPEN',
                created_by: 1,
            };

            const { meeting_id } = await MeetingService.createMeeting(meetingData);

            // Bob가 모임에 참가
            await MeetingService.joinMeeting(meeting_id, 2);

            // Bob가 다시 모임에 참가하려 시도
            await expect(MeetingService.joinMeeting(meeting_id, 2))
                .rejects
                .toThrow('이미 참가한 사용자입니다.');
        });

        test('모임 마감된 경우 참가를 실패해야 한다', async () => {
            // Alice가 모임 생성 (이미 마감됨)
            const meetingData = {
                title: 'Afternoon Workshop',
                description: 'A workshop on web development.',
                start_time: '2024-09-10T14:00:00Z',
                end_time: '2024-09-10T16:00:00Z',
                location: 'Office',
                deadline: '2024-09-09T23:59:59Z',
                type: 'CLOSE',
                created_by: 1,
            };

            const { meeting_id } = await MeetingService.createMeeting(meetingData);

            // Bob가 모임에 참가 시도
            await expect(MeetingService.joinMeeting(meeting_id, 2))
                .rejects
                .toThrow('이미 마감된 모임입니다.');
        });
    });
});