import { Types } from 'mongoose';
import {
	IUser, 
	ISecret,
	IServiceAccount,
	Membership,
	IOrganization,
} from '../models';
import { validateMembership } from '../helpers/membership';
import _ from 'lodash';
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
import {
	validateMembershipOrg
} from '../helpers/membershipOrg';
import {
	PERMISSION_READ_SECRETS,
	PERMISSION_WRITE_SECRETS
} from '../variables';

/**
 * Validate that user (client) can access workspace
 * with id [workspaceId] and its environment [environment] with required permissions
 * [requiredPermissions]
 * @param {Object} obj
 * @param {User} obj.user - user client
 * @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
 * @param {String} environment - (optional) environment in workspace to validate against
 * @param {String[]} requiredPermissions - required permissions as part of the endpoint
 */
export const validateUserClientForWorkspace = async ({
	user,
	workspaceId,
	environment,
	acceptedRoles,
	requiredPermissions
}: {
	user: IUser;
	workspaceId: Types.ObjectId;
	environment?: string;
	acceptedRoles: Array<'admin' | 'member'>;
	requiredPermissions?: string[];
}) => {
	
	// validate user membership in workspace
	const membership = await validateMembership({
        userId: user._id,
        workspaceId,
		acceptedRoles
    });
	
	let runningIsDisallowed = false;
	requiredPermissions?.forEach((requiredPermission: string) => {
		switch (requiredPermission) {
			case PERMISSION_READ_SECRETS:
				runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_READ_SECRETS });
				break;
			case PERMISSION_WRITE_SECRETS:
				runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_WRITE_SECRETS });
				break;
			default:
				break;
		}
		
		if (runningIsDisallowed) {
			throw UnauthorizedRequestError({
				message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`
			});	
		}
	});
	
	return membership;
}

/**
 * Validate that user (client) can access secret [secret]
 * with required permissions [requiredPermissions]
 * @param {Object} obj
 * @param {User} obj.user - user client
 * @param {Secret[]} obj.secrets - secrets to validate against
 * @param {String[]} requiredPermissions - required permissions as part of the endpoint
 */
export const validateUserClientForSecret = async ({
	user,
	secret,
	acceptedRoles,
	requiredPermissions
}: {
	user: IUser;
	secret: ISecret;
	acceptedRoles?: Array<'admin' | 'member'>;
	requiredPermissions?: string[];
}) => {
	const membership = await validateMembership({
		userId: user._id,
		workspaceId: secret.workspace,
		acceptedRoles
	});
	
	if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
		const isDisallowed = _.some(membership.deniedPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });

		if (isDisallowed) {
			throw UnauthorizedRequestError({
				message: 'You do not have the required permissions to perform this action' 
			});
		}
	}
}

/**
 * Validate that user (client) can access secrets [secrets]
 * with required permissions [requiredPermissions]
 * @param {Object} obj
 * @param {User} obj.user - user client
 * @param {Secret[]} obj.secrets - secrets to validate against
 * @param {String[]} requiredPermissions - required permissions as part of the endpoint
 */
export const validateUserClientForSecrets = async ({
	user,
	secrets,
	requiredPermissions
}: {
	user: IUser;
	secrets: ISecret[];
	requiredPermissions?: string[];
}) => {
	
	// TODO: add acceptedRoles?

	const userMemberships = await Membership.find({ user: user._id })
	const userMembershipById = _.keyBy(userMemberships, 'workspace');
	const workspaceIdsSet = new Set(userMemberships.map((m) => m.workspace.toString()));

	// for each secret check if the secret belongs to a workspace the user is a member of
	secrets.forEach((secret: ISecret) => {
		if (!workspaceIdsSet.has(secret.workspace.toString())) {
			throw BadRequestError({
				message: 'Failed authorization for the secret'
			});
		}

		if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
			const deniedMembershipPermissions = userMembershipById[secret.workspace.toString()].deniedPermissions;
			const isDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });

			if (isDisallowed) {
				throw UnauthorizedRequestError({
					message: 'You do not have the required permissions to perform this action' 
				});
			}
		}
	});
}

/**
 * Validate that user (client) can access service account [serviceAccount]
 * with required permissions [requiredPermissions]
 * @param {Object} obj
 * @param {User} obj.user - user client
 * @param {ServiceAccount} obj.serviceAccount - service account to validate against
 * @param {String[]} requiredPermissions - required permissions as part of the endpoint
 */
export const validateUserClientForServiceAccount = async ({
	user,
	serviceAccount,
	requiredPermissions
}: {
	user: IUser;
	serviceAccount: IServiceAccount;
	requiredPermissions?: string[];
}) => {
	if (!serviceAccount.user.equals(user._id)) {
		// case: user who created service account is not the
		// same user that is on the request
		await validateMembershipOrg({
			userId: user._id,
			organizationId: serviceAccount.organization,
			acceptedRoles: [],
			acceptedStatuses: []
		});
	}
}

/**
 * Validate that user (client) can access organization [organization]
 * @param {Object} obj
 * @param {User} obj.user - user client
 * @param {Organization} obj.organization - organization to validate against
 */
export const validateUserClientForOrganization = async ({
	user,
	organization,
	acceptedRoles,
	acceptedStatuses
}: {
	user: IUser;
	organization: IOrganization;
	acceptedRoles: Array<'owner' | 'admin' | 'member'>;
	acceptedStatuses: Array<'invited' | 'accepted'>;
}) => {
	const membershipOrg = await validateMembershipOrg({
		userId: user._id,
		organizationId: organization._id,
		acceptedRoles,
		acceptedStatuses
	});
	
	return membershipOrg;
}