<cfscript>
/**
 * Grant utility functions for SP-SM enforcement.
 * Include this file where grant time/eligibility checks are needed.
 */

/**
 * Check if a grant's time policy is currently active.
 * @timePolicyType  always | schedule | date_range | event
 * @timePolicyData  JSON string or struct with policy parameters
 * @return boolean
 */
function isGrantTimeActive(required string timePolicyType, any timePolicyData = "") {
    if (arguments.timePolicyType == "always") return true;

    var policy = {};
    if (isSimpleValue(arguments.timePolicyData) && len(trim(arguments.timePolicyData))) {
        try { policy = deserializeJSON(arguments.timePolicyData); } catch (any e) { return false; }
    } else if (isStruct(arguments.timePolicyData)) {
        policy = arguments.timePolicyData;
    } else {
        return false;
    }

    var nowDt = now();

    switch (arguments.timePolicyType) {
        case "schedule":
            // policy: { days: [1,2,3,4,5], startTime: "09:00", endTime: "17:00" }
            // days: 1=Sunday, 2=Monday, ... 7=Saturday (ColdFusion dayOfWeek)
            if (!structKeyExists(policy, "days") || !isArray(policy.days)) return false;
            var todayDow = dayOfWeek(nowDt);
            if (!arrayFind(policy.days, todayDow)) return false;
            if (structKeyExists(policy, "startTime") && structKeyExists(policy, "endTime")) {
                var currentTime = timeFormat(nowDt, "HH:mm");
                if (currentTime < policy.startTime || currentTime > policy.endTime) return false;
            }
            return true;

        case "date_range":
            // policy: { start: "2026-03-01", end: "2026-06-30" }
            if (!structKeyExists(policy, "start") || !structKeyExists(policy, "end")) return false;
            var today = dateFormat(nowDt, "yyyy-mm-dd");
            return (today >= policy.start && today <= policy.end);

        case "event":
            // policy: { name: "Summer Festival", start: "2026-07-04 10:00", end: "2026-07-04 22:00" }
            if (!structKeyExists(policy, "start") || !structKeyExists(policy, "end")) return false;
            var nowStr = dateTimeFormat(nowDt, "yyyy-mm-dd HH:nn");
            return (nowStr >= policy.start && nowStr <= policy.end);

        default:
            return false;
    }
}

/**
 * Check if a user meets the eligibility scope for a grant.
 * @eligibilityScope  public | employees | guests | internal
 * @userID            The ordering user's ID
 * @ownerBusinessID   The SP-owning business
 * @guestBusinessID   The granted business
 * @return boolean
 */
function checkGrantEligibility(
    required string eligibilityScope,
    required numeric userID,
    required numeric ownerBusinessID,
    required numeric guestBusinessID
) {
    if (arguments.eligibilityScope == "public") return true;

    // Check if user is employee of a given business
    var isGuestEmployee = queryTimed(
        "SELECT 1 FROM Employees WHERE BusinessID = ? AND UserID = ? AND IsActive = 1 LIMIT 1",
        [
            { value = arguments.guestBusinessID, cfsqltype = "cf_sql_integer" },
            { value = arguments.userID, cfsqltype = "cf_sql_integer" }
        ],
        { datasource = "payfrit" }
    ).recordCount > 0;

    var isOwnerEmployee = queryTimed(
        "SELECT 1 FROM Employees WHERE BusinessID = ? AND UserID = ? AND IsActive = 1 LIMIT 1",
        [
            { value = arguments.ownerBusinessID, cfsqltype = "cf_sql_integer" },
            { value = arguments.userID, cfsqltype = "cf_sql_integer" }
        ],
        { datasource = "payfrit" }
    ).recordCount > 0;

    switch (arguments.eligibilityScope) {
        case "employees":
            return isGuestEmployee;
        case "guests":
            return (!isGuestEmployee && !isOwnerEmployee);
        case "internal":
            return isOwnerEmployee;
        default:
            return false;
    }
}

/**
 * Record an entry in the immutable grant history table.
 */
function recordGrantHistory(
    required numeric grantID,
    required string action,
    required numeric actorUserID,
    required numeric actorBusinessID,
    any previousData = "",
    any newData = ""
) {
    var prevJson = isStruct(arguments.previousData) ? serializeJSON(arguments.previousData) : (len(trim(arguments.previousData)) ? arguments.previousData : javaCast("null", ""));
    var newJson = isStruct(arguments.newData) ? serializeJSON(arguments.newData) : (len(trim(arguments.newData)) ? arguments.newData : javaCast("null", ""));

    var ip = "";
    try { ip = cgi.REMOTE_ADDR; } catch (any e) {}

    queryTimed(
        "INSERT INTO ServicePointGrantHistory (GrantID, Action, ActorUserID, ActorBusinessID, PreviousData, NewData, IPAddress)
         VALUES (?, ?, ?, ?, ?, ?, ?)",
        [
            { value = arguments.grantID, cfsqltype = "cf_sql_integer" },
            { value = arguments.action, cfsqltype = "cf_sql_varchar" },
            { value = arguments.actorUserID, cfsqltype = "cf_sql_integer" },
            { value = arguments.actorBusinessID, cfsqltype = "cf_sql_integer" },
            { value = prevJson, cfsqltype = "cf_sql_varchar", null = isNull(prevJson) },
            { value = newJson, cfsqltype = "cf_sql_varchar", null = isNull(newJson) },
            { value = ip, cfsqltype = "cf_sql_varchar" }
        ],
        { datasource = "payfrit" }
    );
}
</cfscript>
