import { EntityWithRoles, Role } from "@/interfaces/entity";
import { PermissionType } from "@/interfaces/permissions";
import { User, Type, userTypes } from "@/interfaces/user";
import { RolePermission } from "@/resources/entityPermissions";
import { findBy, mapBy } from ".";
import { isAdmin } from "./users";

export function checkAccess(user: User, types: Type[], permissions?: PermissionType[], permission?: PermissionType) {
	if (!user) {
		return false;
	}

	// if(user.type === '') {
	if (!user?.type) {
		return false;
	}

	if (types.length === 0) {
		return false;
	}

	if (!types.includes(user.type)) {
		return false;
	}

	// we may not want a permission check as most screens dont even havr a specific permission
	if (permission) {
		// this works more like a blacklist
		// therefore if we don't find the permission here, he can't do it
		if (!(permissions || []).includes(permission)) {
			return false;
		}
	}

	return true;
}

export function groupAllowedEntitiesByPermissions(
	user: User,
	entities: EntityWithRoles[],
	permissions: RolePermission[]
): { [key: string]: EntityWithRoles[] } {
	if (["admin", "developer"].includes(user?.type)) {
		return permissions.reduce((acc, permission) => {
			acc[permission] = entities;
			return acc;
		}, {} as { [key: string]: EntityWithRoles[] });
	}

	const userEntityMap = new Map(user.entities.map(e => [e.id, e]));
	const roleCache = new Map<string, Role | null>();

	return entities.reduce((acc, entity) => {
		const userEntity = userEntityMap.get(entity.id);
		const role = userEntity
			? roleCache.get(userEntity.role) ??
			(() => {
				const foundRole = entity.roles.find(r => r.id === userEntity.role) || null;
				roleCache.set(userEntity.role, foundRole);
				return foundRole;
			})()
			: null;

		permissions.forEach(permission => {
			if (!acc[permission]) acc[permission] = [];
			if (role && role.permissions.includes(permission)) {
				acc[permission].push(entity);
			}
		});

		return acc;
	}, {} as { [key: string]: EntityWithRoles[] });
}

export function findAllowedEntities(user: User, entities: EntityWithRoles[], permission: RolePermission) {
	if (["admin", "developer"].includes(user?.type)) return entities

	const allowedEntities = (entities ?? []).filter((e) => doesEntityAllow(user, e, permission))
	return allowedEntities
}

export function findAllowedEntitiesSomePermissions(user: User, entities: EntityWithRoles[], permissions: RolePermission[]) {
	if (["admin", "developer"].includes(user?.type)) return entities

	const allowedEntities = entities.filter((e) => permissions.some((p) => doesEntityAllow(user, e, p)))
	return allowedEntities
}

export function doesEntityAllow(user: User, entity: EntityWithRoles, permission: RolePermission) {
	if (isAdmin(user)) return true
	const userEntity = findBy(user.entities, 'id', entity?.id)
	if (!userEntity) return false

	const role = findBy(entity.roles, 'id', userEntity.role)
	if (!role) return false

	return role.permissions.includes(permission)
}

export function getTypesOfUser(types: Type[]) {
	// basicly generate a list of all types except the excluded ones
	return userTypes.filter((userType) => {
		return !types.includes(userType);
	});
}
