import { Application } from 'express';
import { Resource } from 'express-automatic-routes';
import { RolePermissionProvider } from '#providers/RolePermissionProvider';
import { RoleProvider } from '#providers/RoleProvider';
import { UserRoleProvider } from '#providers/UserRoleProvider';
import { Req, Res } from '#interfaces/IApi';
import verify, { requireAdmin, invalidatePermissionsCache } from '#middlewares/auth';
import { validateZod } from '#middlewares/validators';
import { RolePermissionBulkAssignBodySchema } from '#contracts/user/schema';
import { sendSuccess } from '#utils/responseUtils';

export default (_express: Application) => {
	const rolePermissionProvider = new RolePermissionProvider();
	const userRoleProvider = new UserRoleProvider();

	return <Resource>{
		post: {
			middleware: [verify, requireAdmin, validateZod(RolePermissionBulkAssignBodySchema)],
			handler: async (req: Req, res: Res) => {
				try {
					const roleId = req.params.role_id as string;
					const permissionIds = req.body.permission_ids as string[];

					// Check if role exists
					const roleProvider = new RoleProvider();
					await roleProvider.getById({ id: roleId });

					// Get all existing assignments for this role to filter duplicates
					const existing = await rolePermissionProvider.getAll({
						filters: { role_id: roleId },
						pageSize: 1000,
						page: 1,
					});
					const existingPermissionIds = new Set((existing.rows ?? []).map((r: any) => r.permission_id as string));

					// Only create assignments that don't already exist
					const newRecords = permissionIds
						.filter((pid) => !existingPermissionIds.has(pid))
						.map((permissionId) => ({
							id: crypto.randomUUID(),
							role_id: roleId,
							permission_id: permissionId,
							created_at: new Date(),
						}));

					const created =
						newRecords.length > 0
							? await rolePermissionProvider.bulkCreate(newRecords, { ignoreDuplicates: true })
							: [];

					// Invalidate permission cache for all users with this role
					if (created.length > 0) {
						const usersWithRole = await userRoleProvider.getAll({
							filters: { role_id: roleId },
							pageSize: 1000,
							page: 1,
						});
						const invalidationPromises = (usersWithRole.rows ?? []).map((ur: any) =>
							invalidatePermissionsCache(ur.user_id).catch(() => {}),
						);
						await Promise.all(invalidationPromises);
					}

					return sendSuccess(
						res,
						created.map((c: any) => c.toJSON()),
						`Successfully assigned ${created.length} permission(s) to role`,
						`Successfully assigned ${created.length} permission(s) to role`,
					);
				} catch (error) {
					return res.error(error);
				}
			},
		},
		put: {
			middleware: [verify, requireAdmin, validateZod(RolePermissionBulkAssignBodySchema)],
			handler: async (req: Req, res: Res) => {
				try {
					const roleId = req.params.role_id as string;
					const permissionIds = req.body.permission_ids as string[];

					// Check if role exists
					const roleProvider = new RoleProvider();
					await roleProvider.getById({ id: roleId });

					// Delete all existing role-permission assignments for this role in one call
					await rolePermissionProvider.bulkDelete({ role_id: roleId } as any);

					// Bulk create all new assignments
					const newRecords = permissionIds.map((permissionId) => ({
						id: crypto.randomUUID(),
						role_id: roleId,
						permission_id: permissionId,
						created_at: new Date(),
					}));

					const created =
						newRecords.length > 0
							? await rolePermissionProvider.bulkCreate(newRecords, { ignoreDuplicates: true })
							: [];

					// Invalidate permission cache for all users with this role
					const usersWithRole = await userRoleProvider.getAll({
						filters: { role_id: roleId },
						pageSize: 1000,
						page: 1,
					});
					const invalidationPromises = (usersWithRole.rows ?? []).map((ur: any) =>
						invalidatePermissionsCache(ur.user_id).catch(() => {}),
					);
					await Promise.all(invalidationPromises);

					return sendSuccess(
						res,
						created.map((c: any) => c.toJSON()),
						`Successfully updated role permissions. ${created.length} permission(s) assigned`,
						`Successfully updated role permissions. ${created.length} permission(s) assigned`,
					);
				} catch (error) {
					return res.error(error);
				}
			},
		},
	};
};
