import { Env, ErrorFactory, JobStateTracker, Logger, SignedRequest, TimeoutManager, generateUuid, } from '../../../src/index.js';
const DEFAULT_PROXY_URL = 'http://127.0.0.1:7772';
const DEFAULT_ROUTE_PATH = '/api/_sys/queue/rpc';
const createTimeoutSignal = (timeoutMs) => {
    if (timeoutMs <= 0)
        return undefined;
    const timeout = AbortSignal.timeout;
    return typeof timeout === 'function' ? timeout(timeoutMs) : undefined;
};
const toBodyText = (payload) => JSON.stringify(payload);
const normalizeBaseUrl = (value) => {
    const raw = value.trim();
    return raw === '' ? DEFAULT_PROXY_URL : raw;
};
const normalizeRoutePath = (value) => {
    const trimmed = value.trim();
    if (trimmed === '')
        return DEFAULT_ROUTE_PATH;
    if (trimmed.startsWith('/'))
        return trimmed;
    return `/${trimmed}`;
};
const resolveSigningPrefix = (baseUrl) => {
    try {
        const parsed = new URL(baseUrl);
        const path = parsed.pathname.endsWith('/') ? parsed.pathname.slice(0, -1) : parsed.pathname;
        if (path === '' || path === '/')
            return undefined;
        return path;
    }
    catch {
        return undefined;
    }
};
const buildSigningUrl = (requestUrl, baseUrl) => {
    const prefix = resolveSigningPrefix(baseUrl);
    if (!prefix)
        return requestUrl;
    if (requestUrl.pathname === prefix || requestUrl.pathname.startsWith(`${prefix}/`)) {
        const signingUrl = new URL(requestUrl.toString());
        const stripped = requestUrl.pathname.slice(prefix.length);
        signingUrl.pathname = stripped.startsWith('/') ? stripped : `/${stripped}`;
        return signingUrl;
    }
    return requestUrl;
};
const resolveSettings = () => {
    const baseUrl = normalizeBaseUrl(Env.get('QUEUE_HTTP_PROXY_URL', DEFAULT_PROXY_URL));
    const routePath = normalizeRoutePath(Env.get('QUEUE_HTTP_PROXY_PATH', DEFAULT_ROUTE_PATH));
    const keyId = Env.get('QUEUE_HTTP_PROXY_KEY_ID', Env.APP_NAME || 'zintrust').trim();
    const configuredSecret = Env.get('QUEUE_HTTP_PROXY_KEY', '').trim();
    const secret = configuredSecret === '' ? Env.APP_KEY : configuredSecret;
    const timeoutMs = Env.getInt('QUEUE_HTTP_PROXY_TIMEOUT_MS', 10000);
    if (secret.trim() === '') {
        throw ErrorFactory.createConfigError('QUEUE_HTTP_PROXY_KEY or APP_KEY is required');
    }
    return {
        baseUrl,
        routePath,
        keyId,
        secret,
        timeoutMs,
    };
};
const buildRpcUrl = (settings) => {
    const url = new URL(settings.baseUrl);
    const basePath = url.pathname.endsWith('/') ? url.pathname.slice(0, -1) : url.pathname;
    const routePath = settings.routePath.startsWith('/')
        ? settings.routePath
        : `/${settings.routePath}`;
    url.pathname = `${basePath}${routePath}`;
    url.search = '';
    return url;
};
const parseJsonResponse = async (response) => {
    const text = await response.text();
    if (text.trim() === '') {
        return {
            ok: false,
            error: { code: 'EMPTY_RESPONSE', message: 'Empty response from queue gateway' },
        };
    }
    try {
        return JSON.parse(text);
    }
    catch {
        return {
            ok: false,
            error: { code: 'INVALID_JSON', message: text },
        };
    }
};
const ensureSuccessfulResponse = (response, requestId) => {
    if (!response.ok) {
        const code = response.error?.code || 'QUEUE_HTTP_PROXY_ERROR';
        const message = response.error?.message || 'Queue gateway returned an error';
        const details = {
            code,
            requestId,
            gatewayRequestId: response.requestId,
            details: response.error?.details,
        };
        throw ErrorFactory.createTryCatchError(message, details);
    }
    return response.result;
};
const callGateway = async (action, payload) => {
    const settings = resolveSettings();
    const url = buildRpcUrl(settings);
    const requestId = generateUuid();
    const requestBody = {
        action,
        requestId,
        payload,
    };
    const bodyText = toBodyText(requestBody);
    const signingUrl = buildSigningUrl(url, settings.baseUrl);
    const params = {
        method: 'POST',
        url: signingUrl,
        body: bodyText,
        keyId: settings.keyId,
        secret: settings.secret,
    };
    const signedHeaders = await SignedRequest.createHeaders(params);
    try {
        const response = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                ...signedHeaders,
            },
            body: bodyText,
            signal: createTimeoutSignal(settings.timeoutMs),
        });
        const parsed = await parseJsonResponse(response);
        if (!response.ok && parsed.ok === false) {
            throw ErrorFactory.createConnectionError(parsed.error?.message || `Queue gateway HTTP error (${response.status})`, {
                status: response.status,
                requestId,
                error: parsed.error,
            });
        }
        return ensureSuccessfulResponse(parsed, requestId);
    }
    catch (error) {
        if (error instanceof Error && error.name === 'AbortError') {
            throw ErrorFactory.createConnectionError('Queue gateway request timed out', {
                timeoutMs: settings.timeoutMs,
                requestId,
            });
        }
        throw error;
    }
};
const resolveFallbackJobId = (payload) => {
    if (typeof payload.uniqueId === 'string' && payload.uniqueId.trim().length > 0) {
        return payload.uniqueId.trim();
    }
    return generateUuid();
};
const resolveCurrentAttempts = (payload) => {
    const raw = payload['_currentAttempts'];
    if (typeof raw !== 'number' || Number.isFinite(raw) === false)
        return 0;
    return Math.max(0, Math.floor(raw));
};
const resolveMaxAttempts = (payload) => {
    if (typeof payload.attempts === 'number' && Number.isFinite(payload.attempts)) {
        return Math.max(1, Math.floor(payload.attempts));
    }
    return undefined;
};
const resolveIdempotencyKey = (payload) => {
    if (typeof payload.uniqueId !== 'string')
        return undefined;
    const trimmed = payload.uniqueId.trim();
    return trimmed.length > 0 ? trimmed : undefined;
};
const markPendingRecoveryFallback = async (input) => {
    const currentAttempts = resolveCurrentAttempts(input.payload);
    const maxAttempts = resolveMaxAttempts(input.payload);
    const idempotencyKey = resolveIdempotencyKey(input.payload);
    // Compatibility shim: consumers of `@zintrust/core` may have a narrower
    // `JobStateTracker.enqueued` type than the runtime implementation.
    const enqueuedApi = JobStateTracker;
    await enqueuedApi.enqueued({
        queueName: input.queue,
        jobId: input.fallbackJobId,
        payload: input.payload,
        attempts: currentAttempts,
        maxAttempts,
        idempotencyKey,
    });
    const pendingRecoveryApi = JobStateTracker;
    if (typeof pendingRecoveryApi.pendingRecovery === 'function') {
        await pendingRecoveryApi.pendingRecovery({
            queueName: input.queue,
            jobId: input.fallbackJobId,
            reason: 'HTTP queue proxy enqueue failed; marked pending recovery',
            error: input.error,
        });
    }
};
export const HttpQueueDriver = Object.freeze({
    async enqueue(queue, payload) {
        const fallbackJobId = resolveFallbackJobId(payload);
        const timeoutMs = Env.getInt('QUEUE_HTTP_PROXY_TIMEOUT_MS', 10000);
        try {
            return await TimeoutManager.withTimeoutRetry(async () => callGateway('enqueue', { queue, payload }), {
                timeoutMs,
                maxRetries: Math.max(0, Env.getInt('QUEUE_HTTP_PROXY_RETRY_MAX', 2)),
                retryDelayMs: Math.max(0, Env.getInt('QUEUE_HTTP_PROXY_RETRY_DELAY_MS', 500)),
                operationName: `http-queue-enqueue:${queue}`,
            });
        }
        catch (error) {
            Logger.warn('HTTP queue enqueue failed; storing tracker fallback in memory', {
                queue,
                fallbackJobId,
                error: error instanceof Error ? error.message : String(error),
            });
            await markPendingRecoveryFallback({
                queue,
                fallbackJobId,
                payload,
                error,
            });
            Logger.warn('Job marked pending recovery in tracker', {
                queue,
                jobId: fallbackJobId,
            });
            return fallbackJobId;
        }
    },
    async dequeue(queue) {
        return callGateway('dequeue', { queue });
    },
    async ack(queue, id) {
        await callGateway('ack', { queue, id });
    },
    async length(queue) {
        return callGateway('length', { queue });
    },
    async drain(queue) {
        await callGateway('drain', { queue });
    },
});
export default HttpQueueDriver;
