import { afterEach, beforeAll, describe, expect, it } from '@jest/globals';
import ms from 'ms';
import { expectValidRoom } from '../../../helpers/assertion-helpers.js';
import {
	createRoom,
	deleteAllRooms,
	deleteRoom,
	disconnectFakeParticipants,
	getRoom,
	joinFakeParticipant,
	sleep,
	startTestServer
} from '../../../helpers/request-helpers.js';
import { RoomService } from '../../../../src/services/room.service.js';
import { container } from '../../../../src/config/dependency-injector.config.js';
import { setInternalConfig } from '../../../../src/config/internal-config.js';

describe('Room API Tests', () => {
	beforeAll(() => {
		startTestServer();
	});

	afterEach(async () => {
		// Remove all rooms created
		await disconnectFakeParticipants();
		await deleteAllRooms();
	});

	describe('Delete Room Tests', () => {
		it('should return 204 when room does not exist (idempotent deletion)', async () => {
			const response = await deleteRoom('non-existent-room-id');

			expect(response.status).toBe(204);
		});

		it('should default to force=false when force parameter is invalid', async () => {
			// Create a room first
			const { roomId } = await createRoom({
				roomName: 'test-room'
			});
			const response = await deleteRoom(roomId, { force: 'not-a-boolean' });

			expect(response.status).toBe(204);
			// Verify it's deleted
			const getResponse = await getRoom(roomId);
			expect(getResponse.status).toBe(404);
		});

		it('should mark room for deletion when participants exist and force parameter is invalid', async () => {
			// Create a room first
			const { roomId } = await createRoom({
				roomName: 'test-room'
			});

			await joinFakeParticipant(roomId, 'test-participant');

			// The force parameter is not a boolean so it should be defined as false
			// and the room should be marked for deletion
			const response = await deleteRoom(roomId, { force: 'not-a-boolean' });

			// Check operation accepted
			expect(response.status).toBe(202);

			// The room should be marked for deletion
			const roomResponse = await getRoom(roomId);
			expect(roomResponse.body).toBeDefined();
			expect(roomResponse.body.roomId).toBe(roomId);
			expect(roomResponse.body.markedForDeletion).toBeDefined();
			expect(roomResponse.body.markedForDeletion).toBe(true);
		});

		it('should delete an empty room completely (204)', async () => {
			const { roomId } = await createRoom({ roomName: 'test-room' });

			const response = await deleteRoom(roomId);

			expect(response.status).toBe(204);

			// Try to retrieve the room again
			const responseAfterDelete = await getRoom(roomId);
			expect(responseAfterDelete.status).toBe(404);
		});

		it('should sanitize roomId with spaces and special characters before deletion', async () => {
			// Create a room first
			const createdRoom = await createRoom({
				roomName: 'test-mixed'
			});

			// Add some spaces and special chars to the valid roomId
			const modifiedId = ` ${createdRoom.roomId}!@# `;
			const response = await deleteRoom(modifiedId);

			// The validation should sanitize the ID and successfully delete
			expect(response.status).toBe(204);

			// Verify it's deleted
			const getResponse = await getRoom(createdRoom.roomId);
			expect(getResponse.status).toBe(404);
		});

		it('should handle explicit force=true for room with no participants', async () => {
			const createdRoom = await createRoom({
				roomName: 'test-room'
			});

			const response = await deleteRoom(createdRoom.roomId, { force: true });

			expect(response.status).toBe(204);

			// Try to retrieve the room again
			const responseAfterDelete = await getRoom(createdRoom.roomId);
			expect(responseAfterDelete.status).toBe(404);
		});

		it('should mark room for deletion (202) when participants exist and force=false', async () => {
			const autoDeletionDate = Date.now() + ms('5h');
			const { roomId } = await createRoom({
				roomName: 'test-room',
				autoDeletionDate
			});

			await joinFakeParticipant(roomId, 'test-participant');

			const response = await deleteRoom(roomId, { force: false });

			expect(response.status).toBe(202);

			const roomResponse = await getRoom(roomId);
			expectValidRoom(roomResponse.body, 'test-room', autoDeletionDate, undefined, true);
		});

		it('should delete a room marked for deletion when the webhook room_finished is received', async () => {
			const autoDeletionDate = Date.now() + ms('5h');
			const { roomId } = await createRoom({
				roomName: 'test-room',
				autoDeletionDate
			});

			const roomService = container.get(RoomService);
			// Set MEETING_DEPARTURE_TIMEOUT to 1s to force the room to be closed immediately
			setInternalConfig({
				MEETING_DEPARTURE_TIMEOUT: '1s'
			});
			// Create livekit room with custom departure timeout
			// This is needed to trigger the room_finished event
			await roomService.createLivekitRoom(roomId);

			// Join a participant to the room
			await joinFakeParticipant(roomId, 'test-participant');

			const response = await deleteRoom(roomId, { force: false });

			expect(response.status).toBe(202);

			const roomResponse = await getRoom(roomId);
			expectValidRoom(roomResponse.body, 'test-room', autoDeletionDate, undefined, true);

			await disconnectFakeParticipants();

			// Wait for room deletion
			await sleep('2s');
			const responseAfterDelete = await getRoom(roomId);
			expect(responseAfterDelete.status).toBe(404);
		});

		it('should force delete (204) room with active participants when force=true', async () => {
			const { roomId } = await createRoom({
				roomName: 'test-room'
			});

			await joinFakeParticipant(roomId, 'test-participant');

			const response = await deleteRoom(roomId, { force: true });

			expect(response.status).toBe(204);

			// Try to retrieve the room again
			await sleep('1s'); // Wait a bit for the meeting to be closed and the room deleted
			const responseAfterDelete = await getRoom(roomId);
			expect(responseAfterDelete.status).toBe(404);
		});

		it('should successfully delete a room already marked for deletion', async () => {
			const { roomId } = await createRoom({ roomName: 'test-marked' });

			// First mark it for deletion
			await joinFakeParticipant(roomId, 'test-participant');

			await deleteRoom(roomId, { force: false });

			// Then try to delete it again
			const response = await deleteRoom(roomId, { force: true });
			expect(response.status).toBe(204);
		});

		it('should handle repeated deletion of the same room gracefully', async () => {
			const { roomId } = await createRoom({ roomName: 'test-idempotent' });

			// Delete first time
			const response1 = await deleteRoom(roomId);
			expect(response1.status).toBe(204);

			// Delete second time - should still return 204 (no error)
			const response2 = await deleteRoom(roomId);
			expect(response2.status).toBe(204);
		});
	});

	describe('Delete Room Validation failures', () => {
		it('should fail when roomId becomes empty after sanitization', async () => {
			const response = await deleteRoom('!!-*!@#$%^&*()_+{}|:"<>?');

			expect(response.status).toBe(422);
			// Expect an error message indicating the resulting roomId is empty.
			expect(response.body.error).toContain('Unprocessable Entity');
			expect(JSON.stringify(response.body.details)).toContain('roomId cannot be empty after sanitization');
		});

		it('should fail when force parameter is a number instead of boolean', async () => {
			const response = await deleteRoom('testRoom', { force: { value: 123 } });

			expect(response.status).toBe(422);
			expect(response.body.error).toContain('Unprocessable Entity');
			expect(JSON.stringify(response.body.details)).toContain('Expected boolean, received object');
		});
	});
});
