import { getPolicyResponseCode } from "./getPolicyResponseCode";
import { getProp } from "../../objects/get-prop";
import { mapBy } from "../../utils/map-by";
/**
 * Check a specific EntityAssertion
 * Exported purely for testing. Not exported from the package.
 * @param assertion
 * @param entity
 * @param context
 * @returns
 */
export function checkAssertion(assertion, entity, context) {
    let response = "granted";
    // construct a hash to look up properties in
    const lookupHash = { entity, context };
    // get the property value
    const propertyLookup = parseProperty(assertion.property);
    const propValue = getProp(lookupHash, propertyLookup.path);
    // If prop is undefined, then the assertion fails
    if (propValue === undefined) {
        response = "assertion-property-not-found";
    }
    let val = assertion.value;
    if (typeof val === "string" && val.indexOf(":") > -1) {
        const valueLookup = parseProperty(assertion.value);
        val = getProp(lookupHash, valueLookup.path);
        if (val === undefined) {
            response = "assertion-property-not-found";
        }
    }
    // if we have the two values, we can make the assertion
    if (propValue !== undefined && val !== undefined) {
        // TODO: Should these fns return IPolicyCheck? or just the response?
        // TODO: Should these fns inspect the type internally? or switch case here?
        switch (assertion.type) {
            case "eq":
            case "neq":
                response = equalityAssertions(assertion, propValue, val);
                break;
            case "starts-with":
            case "ends-with":
            case "not-starts-with":
            case "not-ends-with":
                response = stringAssertions(assertion, propValue, val);
                break;
            case "contains":
            case "contains-some":
            case "without":
                response = arrayAssertions(assertion, propValue, val);
                break;
            case "included-in":
                response = includeAssertions(assertion, propValue, val);
                break;
            case "gt":
            case "lt":
                response = rangeAssertions(assertion, propValue, val);
                break;
            case "length-gt":
            case "length-lt":
                response = lengthAssertions(assertion, propValue, val);
                break;
            case "is-group-admin":
            case "is-not-group-admin":
            case "is-group-member":
            case "is-not-group-member":
            case "is-group-owner":
            case "is-not-group-owner":
                response = groupAssertions(assertion, propValue, val, context);
                break;
        }
    }
    const result = {
        name: `assertion: ${assertion.property} ${assertion.type} ${JSON.stringify(assertion.value)}`,
        value: propValue,
        code: getPolicyResponseCode(response),
        response,
    };
    return result;
}
function groupAssertions(assertion, propValue, val, context) {
    let response = "granted";
    const userGroups = context.currentUser.groups || [];
    // Default the groups to all groups the user is a member of
    let groups = mapBy("id", userGroups);
    let failResponse = "user-not-group-member";
    if (assertion.type === "is-group-member") {
        failResponse = "user-not-group-member";
        // no need to filter - this is anyone who's a member
        groups = filterByMembershipType(userGroups, ["admin", "owner", "member"]);
    }
    if (assertion.type === "is-not-group-member") {
        failResponse = "user-is-group-member";
        groups = filterByMembershipType(userGroups, ["admin", "owner", "member"]);
    }
    if (assertion.type === "is-group-admin") {
        failResponse = "user-not-group-manager";
        groups = filterByMembershipType(userGroups, ["admin", "owner"]);
    }
    if (assertion.type === "is-not-group-admin") {
        failResponse = "user-is-group-manager";
        groups = filterByMembershipType(userGroups, ["admin", "owner"]);
    }
    if (assertion.type === "is-group-owner") {
        failResponse = "user-not-group-owner";
        groups = filterByMembershipType(userGroups, ["owner"]);
    }
    if (assertion.type === "is-not-group-owner") {
        failResponse = "user-is-group-owner";
        groups = filterByMembershipType(userGroups, ["owner"]);
    }
    // now, see if the val is in the groups array
    const isInverseAssertion = assertion.type.includes("not");
    if (isInverseAssertion ? groups.includes(val) : !groups.includes(val)) {
        // send a specific response
        response = failResponse;
    }
    return response;
}
function filterByMembershipType(groups, types) {
    return groups.reduce((acc, grp) => {
        if (types.includes(grp.userMembership.memberType)) {
            acc.push(grp.id);
        }
        return acc;
    }, []);
}
/**
 * Is the propValue included in the val array?
 * @param assertion
 * @param propValue
 * @param val
 * @returns
 */
function includeAssertions(assertion, propValue, val) {
    let response = "granted";
    if (!Array.isArray(val)) {
        response = "property-not-array";
    }
    else {
        const arrayVal = val;
        if (!arrayVal.includes(propValue)) {
            response = "array-missing-required-value";
        }
    }
    return response;
}
/**
 * Is the propValue "eq" or "neq" to the val?
 * @param assertion
 * @param propValue
 * @param val
 * @returns
 */
function equalityAssertions(assertion, propValue, val) {
    let response = "granted";
    if (assertion.type === "eq" && propValue !== val) {
        response = "assertion-failed";
    }
    else if (assertion.type === "neq" && propValue === val) {
        response = "assertion-failed";
    }
    return response;
}
/**
 * Does the propValue "starts-with" or "ends-with" the val?
 * @param assertion
 * @param propValue
 * @param val
 * @returns
 */
function stringAssertions(assertion, propValue, // what we found
val // what we're looking for
) {
    let response = "granted";
    if (assertion.type === "starts-with" && !propValue.startsWith(val)) {
        response = "assertion-failed";
    }
    else if (assertion.type === "not-starts-with" &&
        propValue.startsWith(val)) {
        response = "assertion-failed";
    }
    else if (assertion.type === "ends-with" && !propValue.endsWith(val)) {
        response = "assertion-failed";
    }
    else if (assertion.type === "not-ends-with" && propValue.endsWith(val)) {
        response = "assertion-failed";
    }
    return response;
}
/**
 * Is the propValue "gt" or "lt" to the val?
 * @param assertion
 * @param propValue
 * @param val
 * @returns
 */
function rangeAssertions(assertion, propValue, val) {
    let response = "granted";
    if (typeof propValue !== "number" || typeof val !== "number") {
        response = "assertion-requires-numeric-values";
    }
    if (assertion.type === "gt" && !(propValue > val)) {
        response = "assertion-failed";
    }
    else if (assertion.type === "lt" && !(propValue < val)) {
        response = "assertion-failed";
    }
    return response;
}
/**
 * Is the propValue length "gt" or "lt" to the val?
 * @param assertion
 * @param propValue
 * @param val
 * @returns
 */
function lengthAssertions(assertion, propValue, val) {
    let response = "granted";
    if (typeof propValue !== "string" && !Array.isArray(propValue)) {
        response = "property-has-no-length";
    }
    else if (typeof val !== "number") {
        response = "assertion-requires-numeric-values";
    }
    else {
        if (assertion.type === "length-gt" && !(propValue.length > val)) {
            response = "assertion-failed";
        }
        else if (assertion.type === "length-lt" && !(propValue.length < val)) {
            response = "assertion-failed";
        }
    }
    return response;
}
/**
 * Does the propValue array "contain" or "without" the val?
 * @param assertion
 * @param propValue
 * @param val
 * @returns
 */
function arrayAssertions(assertion, propValue, val) {
    let response = "granted";
    if (!Array.isArray(propValue)) {
        response = "property-not-array";
    }
    else {
        const arrayProp = propValue;
        if (assertion.type === "contains") {
            if (Array.isArray(val)) {
                const containsAll = val.every((v) => arrayProp.includes(v));
                if (!containsAll) {
                    response = "array-missing-required-value";
                }
            }
            else {
                const containsVal = arrayProp.includes(val);
                if (!containsVal) {
                    response = "array-missing-required-value";
                }
            }
        }
        if (assertion.type === "contains-some") {
            if (!Array.isArray(val)) {
                response = "assertion-requires-array-value";
            }
            else {
                const containsSome = val.some((v) => arrayProp.includes(v));
                if (!containsSome) {
                    response = "array-missing-required-value";
                }
            }
        }
        if (assertion.type === "without") {
            if (Array.isArray(val)) {
                const containsSome = val.some((v) => arrayProp.includes(v));
                if (containsSome) {
                    response = "array-contains-invalid-value";
                }
            }
            else {
                const containsVal = arrayProp.includes(val);
                if (containsVal) {
                    response = "array-contains-invalid-value";
                }
            }
        }
    }
    return response;
}
function parseProperty(property) {
    let root = "entity";
    let path = `entity.${property}`;
    if (property.indexOf(":") > -1) {
        root = property.split(":")[0];
        path = `${root}.${property.split(":")[1]}`;
    }
    return { root, path };
}
//# sourceMappingURL=checkAssertion.js.map