import { generateUuid } from '../../common/utility.js';
import { ZintrustLang } from '../../lang/lang.js';
import { Env } from '../../config/env.js';
import { Logger } from '../../config/logger.js';
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
import { JobStateTracker } from './JobStateTracker.js';
import { QueueTracing } from './QueueTracing.js';
import { RedisKeys } from '../redis/RedisKeyManager.js';
let redis_key_prefix;
/**
 * Resolves the lock prefix for queue operations
 * Uses singleton RedisKeys for consistent key management
 */
export const resolveLockPrefix = () => {
    if (redis_key_prefix !== undefined) {
        return redis_key_prefix;
    }
    redis_key_prefix = RedisKeys.queueLockPrefix;
    return redis_key_prefix;
};
const drivers = new Map();
const resolveDriverName = (name) => {
    const resolved = (name ?? Env.QUEUE_CONNECTION) || Env.QUEUE_DRIVER || ZintrustLang.INMEMORY;
    return (resolved !== null && resolved !== undefined ? String(resolved) : ZintrustLang.INMEMORY)
        .trim()
        .toLowerCase();
};
const shouldPreserveExistingStatus = (queueName, jobId) => {
    const existing = JobStateTracker.get(queueName, jobId);
    return existing?.status === 'pending_recovery';
};
const resolveRequestedUniqueId = (payload) => {
    if (typeof payload?.uniqueId !== 'string')
        return undefined;
    const normalized = payload.uniqueId.trim();
    return normalized.length > 0 ? normalized : undefined;
};
const resolveMaxAttempts = (payload) => {
    if (typeof payload?.attempts !== 'number' || !Number.isFinite(payload.attempts))
        return undefined;
    return payload.attempts > 0 ? Math.floor(payload.attempts) : undefined;
};
const resolveExpectedCompletionAt = () => {
    return new Date(Date.now() + Math.max(1000, Env.getInt('QUEUE_JOB_TIMEOUT', 60) * 1000)).toISOString();
};
const markEnqueued = async (input) => {
    await JobStateTracker.enqueued({
        queueName: input.queueName,
        jobId: input.jobId,
        payload: input.payload,
        maxAttempts: resolveMaxAttempts(input.payload),
        expectedCompletionAt: resolveExpectedCompletionAt(),
        idempotencyKey: input.requestedUniqueId,
    });
};
const createFallbackJobId = (requestedUniqueId) => {
    if (requestedUniqueId !== undefined)
        return requestedUniqueId;
    return `fallback-${generateUuid()}`;
};
const markFailedEnqueue = async (input) => {
    const fallbackJobId = createFallbackJobId(input.requestedUniqueId);
    await markEnqueued({
        queueName: input.queueName,
        jobId: fallbackJobId,
        payload: input.payload,
        requestedUniqueId: input.requestedUniqueId,
    });
    await JobStateTracker.pendingRecovery({
        queueName: input.queueName,
        jobId: fallbackJobId,
        reason: 'Queue enqueue failed; marked pending recovery by core queue layer',
        error: input.error,
    });
    return fallbackJobId;
};
export const Queue = Object.freeze({
    register(name, driver) {
        drivers.set(name.toLowerCase(), driver);
    },
    reset() {
        drivers.clear();
    },
    get(name) {
        const driverName = resolveDriverName(name);
        const driver = drivers.get(driverName);
        if (!driver) {
            throw ErrorFactory.createConfigError(`Queue driver not registered: ${driverName}`);
        }
        return driver;
    },
    async enqueue(queue, payload, driverName) {
        const resolvedDriver = resolveDriverName(driverName);
        const requestedUniqueId = resolveRequestedUniqueId(payload);
        try {
            const jobId = await QueueTracing.traceOperation({
                queueName: queue,
                operation: 'enqueue',
                attributes: {
                    driverName: resolvedDriver,
                    hasUniqueId: requestedUniqueId !== undefined,
                },
                execute: async () => {
                    const driver = Queue.get(driverName);
                    return driver.enqueue(queue, payload);
                },
            });
            Logger.info('Queue enqueue succeeded', {
                queue,
                driver: resolvedDriver,
                jobId,
                requestedUniqueId,
            });
            if (shouldPreserveExistingStatus(queue, jobId)) {
                Logger.warn('Queue enqueue returned job already marked pending recovery; preserving status', {
                    queue,
                    driver: resolvedDriver,
                    jobId,
                    requestedUniqueId,
                });
                return jobId;
            }
            await markEnqueued({
                queueName: queue,
                jobId,
                payload,
                requestedUniqueId,
            });
            return jobId;
        }
        catch (error) {
            const fallbackJobId = await markFailedEnqueue({
                queueName: queue,
                payload,
                requestedUniqueId,
                error,
            });
            Logger.warn('Queue enqueue failed', {
                queue,
                driver: resolvedDriver,
                fallbackJobId,
                requestedUniqueId,
                error: error instanceof Error ? error.message : String(error),
            });
            throw error;
        }
    },
    async dequeue(queue, driverName) {
        return QueueTracing.traceOperation({
            queueName: queue,
            operation: 'dequeue',
            attributes: {
                driverName: driverName ?? null,
            },
            execute: async () => {
                const driver = Queue.get(driverName);
                return driver.dequeue(queue);
            },
        });
    },
    async ack(queue, id, driverName) {
        return QueueTracing.traceOperation({
            queueName: queue,
            operation: 'ack',
            attributes: {
                driverName: driverName ?? null,
            },
            execute: async () => {
                const driver = Queue.get(driverName);
                await driver.ack(queue, id);
            },
        });
    },
    async length(queue, driverName) {
        return QueueTracing.traceOperation({
            queueName: queue,
            operation: 'length',
            attributes: {
                driverName: driverName ?? null,
            },
            execute: async () => {
                const driver = Queue.get(driverName);
                return driver.length(queue);
            },
        });
    },
    async drain(queue, driverName) {
        return QueueTracing.traceOperation({
            queueName: queue,
            operation: 'drain',
            attributes: {
                driverName: driverName ?? null,
            },
            execute: async () => {
                const driver = Queue.get(driverName);
                await driver.drain(queue);
            },
        });
    },
});
export default Queue;
