import { OpenViduMeetPermissions, ParticipantOptions, ParticipantRole } from '@typings-ce';
import { Request, Response } from 'express';
import { container } from '../config/index.js';
import INTERNAL_CONFIG from '../config/internal-config.js';
import {
	errorInvalidParticipantToken,
	errorParticipantTokenNotPresent,
	handleError,
	rejectRequestFromMeetError
} from '../models/error.model.js';
import { LoggerService, ParticipantService, RoomService, TokenService } from '../services/index.js';
import { getCookieOptions } from '../utils/cookie-utils.js';

export const generateParticipantToken = async (req: Request, res: Response) => {
	const logger = container.get(LoggerService);
	const participantService = container.get(ParticipantService);
	const participantOptions: ParticipantOptions = req.body;
	const { roomId } = participantOptions;

	// Check if there is a previous token
	const previousToken = req.cookies[INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME];
	let currentRoles: { role: ParticipantRole; permissions: OpenViduMeetPermissions }[] = [];

	if (previousToken) {
		// If there is a previous token, extract the roles from it
		// and use them to generate the new token, aggregating the new role to the current ones
		logger.verbose('Previous participant token found. Extracting roles');
		const tokenService = container.get(TokenService);

		try {
			const claims = tokenService.getClaimsIgnoringExpiration(previousToken);
			const metadata = participantService.parseMetadata(claims.metadata || '{}');
			currentRoles = metadata.roles;
		} catch (error) {
			logger.verbose('Error extracting roles from previous token:', error);
		}
	}

	try {
		logger.verbose(`Generating participant token for room '${roomId}'`);

		const token = await participantService.generateOrRefreshParticipantToken(participantOptions, currentRoles);
		res.cookie(INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME, token, getCookieOptions('/'));
		return res.status(200).json({ token });
	} catch (error) {
		handleError(res, error, `generating participant token for room '${roomId}'`);
	}
};

export const refreshParticipantToken = async (req: Request, res: Response) => {
	const logger = container.get(LoggerService);

	// Check if there is a previous token
	const previousToken = req.cookies[INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME];

	if (!previousToken) {
		logger.verbose('No previous participant token found. Cannot refresh.');
		const error = errorParticipantTokenNotPresent();
		return rejectRequestFromMeetError(res, error);
	}

	// Extract roles from the previous token
	const tokenService = container.get(TokenService);
	const participantService = container.get(ParticipantService);
	let currentRoles: { role: ParticipantRole; permissions: OpenViduMeetPermissions }[] = [];

	try {
		const claims = tokenService.getClaimsIgnoringExpiration(previousToken);
		const metadata = participantService.parseMetadata(claims.metadata || '{}');
		currentRoles = metadata.roles;
	} catch (err) {
		logger.verbose('Error extracting roles from previous token:', err);
		const error = errorInvalidParticipantToken();
		return rejectRequestFromMeetError(res, error);
	}

	const participantOptions: ParticipantOptions = req.body;
	const { roomId } = participantOptions;

	try {
		logger.verbose(`Refreshing participant token for room '${roomId}'`);
		const token = await participantService.generateOrRefreshParticipantToken(
			participantOptions,
			currentRoles,
			true
		);

		res.cookie(INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME, token, getCookieOptions('/'));
		return res.status(200).json({ token });
	} catch (error) {
		handleError(res, error, `refreshing participant token for room '${roomId}'`);
	}
};

export const updateParticipantRole = async (req: Request, res: Response) => {
	const logger = container.get(LoggerService);
	const participantService = container.get(ParticipantService);
	const { roomId, participantIdentity } = req.params;
	const { role } = req.body;

	try {
		logger.verbose(`Changing role of participant '${participantIdentity}' in room '${roomId}' to '${role}'`);
		await participantService.updateParticipantRole(roomId, participantIdentity, role);
		res.status(200).json({ message: `Participant '${participantIdentity}' role updated to '${role}'` });
	} catch (error) {
		handleError(res, error, `changing role for participant '${participantIdentity}' in room '${roomId}'`);
	}
};

export const kickParticipant = async (req: Request, res: Response) => {
	const logger = container.get(LoggerService);
	const roomService = container.get(RoomService);
	const participantService = container.get(ParticipantService);
	const { roomId, participantIdentity } = req.params;

	// Check if the room exists
	try {
		await roomService.getMeetRoom(roomId);
	} catch (error) {
		return handleError(res, error, `getting room '${roomId}'`);
	}

	try {
		logger.verbose(`Kicking participant '${participantIdentity}' from room '${roomId}'`);
		await participantService.kickParticipant(roomId, participantIdentity);
		res.status(200).json({
			message: `Participant '${participantIdentity}' kicked successfully from room '${roomId}'`
		});
	} catch (error) {
		handleError(res, error, `kicking participant '${participantIdentity}' from room '${roomId}'`);
	}
};
