import { Logger, queueConfig, resolveLockPrefix, Router, } from '../../../src/index.js';
import { createRedisConnection } from './connection.js';
import { getDashboardHtml } from './dashboard-ui.js';
import { createBullMQDriver } from './driver.js';
import { createMetrics } from './metrics.js';
import { getRecentJobsForQueue, QueueMonitoringStream } from './QueueMonitoringService.js';
export { createWorker as createQueueWorker } from './worker.js';
const DEFAULTS = {
    enabled: true,
    basePath: '/queue-monitor',
    middleware: [],
    autoRefresh: true,
    refreshIntervalMs: 5000,
};
function extractQueueParam(req) {
    if (typeof req.getParam === 'function') {
        return (req.getParam('queue') || (req && req.params !== undefined ? req?.params['queue'] : undefined));
    }
    return req && req.params !== undefined ? req?.params['queue'] : undefined;
}
function fieldError(key, message) {
    return { error: `[${key}] ${message}` };
}
const METRICS_KEYS = {
    attempts: 'metrics:attempts',
    acquired: 'metrics:acquired',
    collisions: 'metrics:collisions',
};
const HISTOGRAM_BUCKETS = [
    { label: '<30s', max: 30_000 },
    { label: '30s-2m', max: 120_000 },
    { label: '2-10m', max: 600_000 },
    { label: '10-60m', max: 3_600_000 },
    { label: '>60m', min: 3_600_000 },
];
const MAX_LOCK_KEYS = 10_000;
// Helper function to scan lock keys with pagination
const scanLockKeys = async (client, searchPattern, maxKeys) => {
    const keys = [];
    let cursor = '0';
    do {
        // Redis scan must be sequential
        // eslint-disable-next-line no-await-in-loop
        const [nextCursor, batch] = await client.scan(cursor, 'MATCH', searchPattern, 'COUNT', '200');
        cursor = nextCursor;
        keys.push(...batch);
        if (keys.length >= maxKeys) {
            Logger.warn('Lock scan limit reached', {
                pattern: searchPattern,
                keysFound: keys.length,
            });
            break;
        }
    } while (cursor !== '0');
    return keys;
};
// Helper function to get TTL statuses for keys
const getLockStatuses = async (client, keys) => {
    return Promise.all(keys.map((key) => client.pttl(key)));
};
// Helper function to build lock objects from keys and statuses
const buildLockObjects = (keys, statuses, prefixLock) => {
    return keys.map((key, index) => {
        const ttl = statuses[index];
        const exists = typeof ttl === 'number' && ttl > 0;
        return {
            key: key.replace(prefixLock, ''),
            ttl: exists ? ttl : undefined,
            expires: exists ? new Date(Date.now() + ttl).toISOString() : undefined,
        };
    });
};
// Helper function to calculate lock metrics
const calculateLockMetrics = async (client, prefixLock) => {
    const metricsKeys = [
        `${prefixLock}${METRICS_KEYS.attempts}`,
        `${prefixLock}${METRICS_KEYS.acquired}`,
        `${prefixLock}${METRICS_KEYS.collisions}`,
    ];
    const [attemptsRaw, acquiredRaw, collisionsRaw] = await client.mget(...metricsKeys);
    const parseMetric = (value) => Number.isFinite(Number(value)) ? Number(value) : 0;
    const attempts = parseMetric(attemptsRaw);
    const acquired = parseMetric(acquiredRaw);
    const collisions = parseMetric(collisionsRaw);
    const collisionRate = attempts > 0 ? collisions / attempts : 0;
    return { attempts, acquired, collisions, collisionRate };
};
// Helper function to build histogram from locks
const buildLockHistogram = (locks) => {
    const histogram = HISTOGRAM_BUCKETS.map((bucket) => ({
        label: bucket.label,
        count: 0,
    }));
    locks.forEach((lock) => {
        if (typeof lock.ttl !== 'number')
            return;
        const ttl = lock.ttl;
        const idx = HISTOGRAM_BUCKETS.findIndex((bucket) => {
            if (typeof bucket.min === 'number')
                return ttl >= bucket.min;
            if (typeof bucket.max === 'number')
                return ttl < bucket.max;
            return false;
        });
        if (idx >= 0)
            histogram[idx].count += 1;
    });
    return histogram;
};
function createGetLocks(redisConfig) {
    return async (pattern = '*') => {
        const client = createRedisConnection(redisConfig);
        const prefix_lock = resolveLockPrefix();
        const searchPattern = `${prefix_lock}${pattern}`;
        try {
            // Scan for lock keys
            const keys = await scanLockKeys(client, searchPattern, MAX_LOCK_KEYS);
            // Get TTL statuses
            const statuses = await getLockStatuses(client, keys);
            // Build lock objects
            const locks = buildLockObjects(keys, statuses, prefix_lock);
            // Calculate metrics
            const metrics = await calculateLockMetrics(client, prefix_lock);
            // Build histogram
            const histogram = buildLockHistogram(locks);
            return {
                locks,
                metrics: {
                    active: locks.length,
                    ...metrics,
                },
                histogram,
            };
        }
        finally {
            if (typeof client.quit === 'function') {
                await client.quit();
            }
            else if (typeof client.disconnect === 'function') {
                client.disconnect();
            }
        }
    };
}
async function handleJobsEndpoint(req, res, metrics, driver) {
    const queueName = extractQueueParam(req);
    if (!queueName) {
        res.status(400).json(fieldError('queue_name', 'Queue name must be provided'));
        return;
    }
    const jobs = await getRecentJobsForQueue(queueName, metrics, driver);
    res.json(jobs);
}
async function handleRetryEndpoint(req, res, driver) {
    const queueName = extractQueueParam(req);
    const jobId = typeof req.getParam === 'function' ? req.getParam?.('jobId') : req.params?.['jobId'];
    if (!queueName || !jobId) {
        res.status(400).json(fieldError('queue_name,job_id', 'Queue name and job ID must be provided'));
        return;
    }
    const success = await driver.retryJob(queueName, jobId);
    if (success) {
        res.json({ ok: true, message: `Job ${jobId} queued for retry` });
    }
    else {
        res.status(404).json({ error: 'Job not found or cannot be retried' });
    }
}
function buildSettings(config) {
    return {
        enabled: config.enabled ?? DEFAULTS.enabled,
        basePath: config.basePath ?? DEFAULTS.basePath,
        middleware: config.middleware ?? DEFAULTS.middleware,
        autoRefresh: config.autoRefresh ?? DEFAULTS.autoRefresh,
        refreshIntervalMs: typeof config.refreshIntervalMs === 'number' && Number.isFinite(config.refreshIntervalMs)
            ? Math.max(1000, Math.floor(config.refreshIntervalMs))
            : DEFAULTS.refreshIntervalMs,
    };
}
function createGetSnapshot(driver, startedAt) {
    return async () => {
        const queues = await driver.getQueues();
        const stats = await Promise.all(queues.map(async (name) => {
            const counts = await driver.getJobCounts(name);
            return { name, counts: counts };
        }));
        return {
            status: 'ok',
            startedAt,
            queues: stats,
        };
    };
}
function createRegisterRoutes(settings, metrics, driver, getSnapshot, getLocks) {
    return (router) => {
        if (!settings.enabled)
            return;
        const routeOptions = settings.middleware.length > 0
            ? { middleware: settings.middleware }
            : { ...queueConfig.monitor };
        registerDashboardRoutes(router, settings, routeOptions);
        registerApiRoutes(router, settings, routeOptions, metrics, driver, getSnapshot, getLocks);
    };
}
function registerDashboardRoutes(router, settings, routeOptions) {
    const renderDashboard = (_req, res) => {
        res.html(getDashboardHtml({
            basePath: settings.basePath,
            autoRefresh: settings.autoRefresh,
            refreshIntervalMs: settings.refreshIntervalMs,
        }));
    };
    // Dashboard HTML
    Router.get(router, settings.basePath, renderDashboard, routeOptions);
    Router.get(router, `${settings.basePath}/`, renderDashboard, routeOptions);
}
function registerApiRoutes(router, settings, routeOptions, metrics, driver, getSnapshot, getLocks) {
    registerSnapshotApi(router, settings, routeOptions, getSnapshot);
    registerJobsApi(router, settings, routeOptions, metrics, driver);
    registerLocksApi(router, settings, routeOptions, getLocks);
    registerRetryApi(router, settings, routeOptions, driver);
    registerEventsApi(router, settings, routeOptions, getSnapshot, getLocks, metrics, driver);
}
function registerSnapshotApi(router, settings, routeOptions, getSnapshot) {
    Router.get(router, `${settings.basePath}/api/snapshot`, async (_req, res) => {
        const data = await getSnapshot();
        res.json(data);
    }, routeOptions);
}
function registerJobsApi(router, settings, routeOptions, metrics, driver) {
    Router.get(router, `${settings.basePath}/api/jobs/:queue`, async (req, res) => {
        await handleJobsEndpoint(req, res, metrics, driver);
    }, routeOptions);
}
function registerLocksApi(router, settings, routeOptions, getLocks) {
    Router.get(router, `${settings.basePath}/api/locks`, async (req, res) => {
        const query = typeof req.getQuery === 'function'
            ? req.getQuery()
            : (req.query ?? {});
        const pattern = query['pattern'] ?? '*';
        const locks = await getLocks(pattern);
        res.json(locks);
    }, routeOptions);
}
function registerRetryApi(router, settings, routeOptions, driver) {
    Router.post(router, `${settings.basePath}/api/retry/:queue/:jobId`, async (req, res) => {
        await handleRetryEndpoint(req, res, driver);
    }, routeOptions);
}
function registerEventsApi(router, settings, routeOptions, getSnapshot, getLocks, metrics, driver) {
    Router.get(router, `${settings.basePath}/api/events`, async (req, res) => {
        QueueMonitoringStream(res, req, getSnapshot, getLocks, metrics, driver, settings);
    }, routeOptions);
}
export const QueueMonitor = Object.freeze({
    create(config) {
        const settings = buildSettings(config);
        let redisConfig;
        if (config?.redis) {
            redisConfig = config?.redis;
        }
        else {
            redisConfig = {
                host: queueConfig.drivers.redis.host,
                port: queueConfig.drivers.redis.port,
                password: queueConfig.drivers.redis.password ?? '',
                db: queueConfig.drivers.redis.database,
            };
        }
        const driver = createBullMQDriver(redisConfig);
        const metrics = createMetrics(redisConfig);
        const startedAt = new Date().toISOString();
        const getSnapshot = createGetSnapshot(driver, startedAt);
        const getLocks = createGetLocks(redisConfig);
        const registerRoutes = createRegisterRoutes(settings, metrics, driver, getSnapshot, getLocks);
        const close = async () => {
            await Promise.all([driver.close(), metrics.close()]);
        };
        return Object.freeze({
            registerRoutes,
            getSnapshot,
            getLocks,
            driver,
            metrics,
            close,
        });
    },
});
export default QueueMonitor;
export { createBullMQDriver } from './driver.js';
/**
 * Package version and build metadata
 * Available at runtime for debugging and health checks
 */
export const _ZINTRUST_QUEUE_MONITOR_VERSION = '0.1.0';
export const _ZINTRUST_QUEUE_MONITOR_BUILD_DATE = '__BUILD_DATE__';
