// Pure helpers extracted from index.js. Everything here is state-free: callers
// pass in whatever piece of config, tokens, or env they want to evaluate
// against. That keeps the functions trivially unit-testable and lets the
// server module stay the single place that owns mutable runtime state.

// Default number of days that completed jobs are retained before the daily
// cleanup cron prunes them. Exported so callers and tests share the constant.
export const COMPLETED_RETENTION_DAYS = 30;

// Composite key for the "draft job being built" bucket. Keying by cookieId +
// appName means a user authorized on multiple bots can build one draft per
// bot without them colliding on top of each other.
export function buildingKey(cookieId, appName) {
    return String(cookieId) + ':' + String(appName);
}

// Filters one of the global job arrays (running/scheduled/completed) down to
// the jobs that belong to the requested bot. Null-safe so callers can hand
// in an uninitialized array.
export function jobsForApp(arr, appName) {
    return (arr || []).filter(function (j) { return j && j.appName === appName; });
}

// Returns the bot's raw access token from the botTokens map, honoring the
// enabled flag. Returns null when the bot is unknown, explicitly disabled,
// or missing a token. Tolerates a flat "appName -> tokenString" shape too,
// which is what older configs used before the enabled flag was introduced.
export function getBotToken(botTokens, appName) {
    if (!botTokens) return null;
    var entry = botTokens[appName];
    if (!entry) return null;
    if (typeof entry === 'string') return entry;
    if (entry.enabled === false) return null;
    return entry.token || null;
}

export function isBotEnabled(botTokens, appName) {
    return getBotToken(botTokens, appName) !== null;
}

// Returns the bot's config block only if the bot is defined in config.json
// AND has an enabled token entry. Returns null otherwise. Every caller should
// go through this instead of reaching into config.webex.bot[...] directly,
// so unknown or disabled bots produce a clean 404 rather than a crash.
export function getBotConfig(config, botTokens, appName) {
    if (!appName) return null;
    var cfg = config && config.webex && config.webex.bot && config.webex.bot[appName];
    if (!cfg) return null;
    if (!isBotEnabled(botTokens, appName)) return null;
    return cfg;
}

// True iff `personId` is listed under the bot's `authorized` map AND the bot
// is enabled. Missing personId or missing bot config yields false.
export function isAuthorized(config, botTokens, appName, personId) {
    var botCfg = getBotConfig(config, botTokens, appName);
    if (!botCfg) return false;
    if (!personId) return false;
    return !!(botCfg.authorized && botCfg.authorized[personId]);
}

// Replaces the `:app` placeholder in the OAuth callback URL template with the
// appName. Empty template returns an empty string so callers can detect the
// misconfiguration.
export function getOAuthRedirectUri(template, appName) {
    return (template || '').replace(':app', appName);
}

// Builds the Webex OAuth authorize URL for a bot. Returns null when the
// required inputs (clientId / template) are missing, so callers can serve an
// actionable 500 instead of a malformed URL.
export function buildAuthUrl({ clientId, template, appName }) {
    if (!clientId || !template) return null;
    var params = new URLSearchParams();
    params.append('client_id', clientId);
    params.append('response_type', 'code');
    params.append('redirect_uri', getOAuthRedirectUri(template, appName));
    params.append('scope', 'spark:kms spark:people_read');
    params.append('state', '');
    return 'https://webexapis.com/v1/authorize?' + params.toString();
}

// Drops completed jobs older than `retentionDays` from `jobs.completed`.
// Mutates `jobs` in place (matching the pre-extraction behavior) and returns
// a report so callers can log the counts without importing a logger. Falls
// back through endTime → startTime → created for the age comparison, and
// keeps any job that has no timestamp at all as a safety measure.
export function cleanCompletedJobs(jobs, retentionDays = COMPLETED_RETENTION_DAYS, now = Date.now()) {
    var cutoffDate = new Date(now - (retentionDays * 24 * 60 * 60 * 1000));
    if (!Array.isArray(jobs.completed)) {
        return { jobs, removed: 0, cutoff: cutoffDate, retentionDays };
    }
    var originalCount = jobs.completed.length;
    jobs.completed = jobs.completed.filter(function (job) {
        var jobDateStr = job.endTime || job.startTime || job.created;
        if (!jobDateStr) return true;
        return new Date(jobDateStr) >= cutoffDate;
    });
    return {
        jobs,
        removed: originalCount - jobs.completed.length,
        cutoff: cutoffDate,
        retentionDays,
    };
}

// Formats a millisecond duration as "Nh Nm N.Ns" (or just "N.Ns" for < 1min,
// or "Nm N.Ns" for < 1hr). Matches the original stat display in job cards.
export function msToTime(duration) {
    var milliseconds = parseInt((duration % 1000) / 100)
        , seconds = parseInt((duration / 1000) % 60)
        , minutes = parseInt((duration / (1000 * 60)) % 60)
        , hours = parseInt((duration / (1000 * 60 * 60)) % 24);

    if (hours > 0) return hours + "h " + minutes + "m " + seconds + "." + milliseconds + "s";
    if (minutes > 0) return minutes + "m " + seconds + "." + milliseconds + "s";
    return seconds + "." + milliseconds + "s";
}
