import { getRunStore } from '../runs/index.js';
import { createLogger } from '@4runr/shared';
import { publishSentinelEvent, } from '../adapters/redis-sentinel-publisher.js';
import { applyRunKill } from '../runs/run-kill.js';
import { appendRunLog, extractSentinelKillReason, formatSentinelKillError, } from '../security/sentinel-run-failure.js';
import { executeWithSentinelWatch } from './sentinel-execute-watch.js';
import { runsStarted, runsCompleted, runsDuration, runsActive, queueJobsCompleted, queueJobsFailed, queueJobDuration, shieldDecisionsTotal, shieldBlocksTotal, shieldMasksTotal, shieldRewritesTotal, } from '../metrics/index.js';
const logger = createLogger('Gateway:Queue:Processor');
export let sentinelEventStreams = null;
export let sentinel = null;
export let shield = null;
export let sseConnections = null;
export let sseMessagesTotal = null;
export function setProcessorContext(eventStreams, sentinelInstance, shieldInstance, connections, messagesCounter) {
    sentinelEventStreams = eventStreams;
    sentinel = sentinelInstance;
    shield = shieldInstance;
    sseConnections = connections || null;
    sseMessagesTotal = messagesCounter || null;
}
export async function processRunExecutionJob(job) {
    const { runId } = job.data;
    const runStartTime = Date.now();
    logger.info('Processing run execution job', {
        runId,
        jobId: job.id,
        attemptsMade: job.attemptsMade,
    });
    try {
        const store = getRunStore();
        const run = await store.getRunById(runId);
        if (!run) {
            throw new Error(`Run not found: ${runId}`);
        }
        if (run.status === 'completed' || run.status === 'failed' || run.status === 'killed') {
            logger.info('Run already finished, skipping execution', {
                runId,
                status: run.status
            });
            queueJobsCompleted.inc({ queue: 'run-execution' });
            return { success: true, runId };
        }
        await job.updateProgress(10);
        if (run.status !== 'running') {
            const store = getRunStore();
            await store.updateRunStatus(runId, 'running', {
                startedAt: new Date().toISOString(),
            });
            runsStarted.inc();
            logger.info('Run status updated to running', { runId });
            const connections = sseConnections?.get(runId);
            if (connections) {
                connections.forEach(connection => {
                    connection.write(`event: log\ndata: ${JSON.stringify({
                        type: 'run_started',
                        runId,
                        status: 'running',
                        timestamp: new Date().toISOString()
                    })}\n\n`);
                    sseMessagesTotal?.inc();
                });
            }
        }
        await job.updateProgress(30);
        const eventStream = sentinelEventStreams?.get(runId);
        if (eventStream && sentinel?.isEnabled()) {
            sentinel.watchRun(runId, eventStream);
            eventStream.emitStarted(runId);
            logger.info('Sentinel watching run', { runId });
        }
        await job.updateProgress(50);
        if (shield && shield.isEnabled()) {
            const inputCheck = shield.checkInput(run.input);
            const shieldMode = shield.getConfig().mode;
            shieldDecisionsTotal.inc({ stage: 'input', action: inputCheck.action, mode: shieldMode });
            await publishSentinelEvent({
                type: 'shield.decision',
                runId,
                timestamp: Date.now(),
                data: {
                    stage: 'input',
                    action: inputCheck.action,
                    reason: inputCheck.reason,
                    mode: shieldMode,
                    details: inputCheck.details,
                },
            });
            if (inputCheck.action === 'block') {
                shieldBlocksTotal.inc({ stage: 'input', reason: inputCheck.details.injection ? 'injection' : 'other' });
                logger.warn('Run blocked by Shield - input check failed', {
                    runId,
                    reason: inputCheck.reason,
                    details: inputCheck.details
                });
                await store.updateRunStatus(runId, 'failed', {
                    completedAt: new Date().toISOString(),
                    output: {
                        error: 'Input blocked by Shield',
                        reason: inputCheck.reason,
                        details: inputCheck.details
                    },
                    logs: [{
                            timestamp: new Date().toISOString(),
                            level: 'error',
                            message: `Shield blocked input: ${inputCheck.reason}`
                        }]
                });
                runsCompleted.inc({ status: 'failed' });
                runsActive.dec();
                queueJobsCompleted.inc({ queue: 'run-execution' });
                if (eventStream) {
                    eventStream.emitError(runId, `Shield blocked: ${inputCheck.reason}`);
                }
                return { success: false, runId };
            }
            if (inputCheck.action === 'mask' && inputCheck.sanitizedOutput) {
                shieldMasksTotal.inc({ stage: 'input', type: 'injection' });
                logger.info('Shield masked input', { runId });
                run.input = inputCheck.sanitizedOutput;
            }
            if (inputCheck.action === 'flag') {
                logger.info('Shield flagged input (monitoring mode)', {
                    runId,
                    reason: inputCheck.reason,
                    details: inputCheck.details
                });
            }
        }
        let output;
        try {
            const { getAgent } = await import('../agents/definitions-simple.js');
            const agentId = run.input?.agent_id || 'test-agent';
            const isLocalModel = agentId.startsWith('local:');
            let actualAgentId = agentId;
            if (isLocalModel) {
                actualAgentId = 'gpt-3.5-turbo';
            }
            const agent = getAgent(actualAgentId);
            if (!agent) {
                const availableAgents = ['test', 'echo', 'gpt4-assistant', 'gpt35-assistant', 'gpt-4', 'gpt-3.5-turbo'];
                throw new Error(`Agent not found: ${agentId}. Available agents: ${availableAgents.join(', ')}`);
            }
            console.log(`[Processor] Executing agent: ${agentId}${isLocalModel ? ' (local model)' : ''}`);
            const runExecute = () => agent.execute(run.input);
            output = sentinel && eventStream
                ? await executeWithSentinelWatch(runId, runExecute, (id) => sentinel.getRunStatus(id))
                : await runExecute();
            if (sentinel && eventStream) {
                const runState = sentinel.getRunStatus(runId);
                if (runState?.status === 'killed') {
                    logger.warn('Run was killed by Sentinel during execution', {
                        runId,
                        reason: runState.killReason
                    });
                    throw new Error(formatSentinelKillError(runState.killReason ?? 'unknown'));
                }
            }
            if (eventStream && output?.usage) {
                const usage = output.usage;
                const totalTokens = usage.totalTokens || 0;
                const cost = output.cost || 0;
                if (totalTokens > 0) {
                    eventStream.emitToken(runId, totalTokens, undefined, cost);
                    logger.info('Token usage emitted to Sentinel', {
                        runId,
                        tokens: totalTokens,
                        cost
                    });
                }
            }
            console.log(`[Processor] Agent execution completed successfully`);
        }
        catch (error) {
            console.error(`[Processor] Agent execution failed:`, error);
            throw error;
        }
        await job.updateProgress(75);
        if (shield && shield.isEnabled()) {
            const outputCheck = shield.checkOutput(run.input, output);
            const shieldMode = shield.getConfig().mode;
            shieldDecisionsTotal.inc({ stage: 'output', action: outputCheck.action, mode: shieldMode });
            await publishSentinelEvent({
                type: 'shield.decision',
                runId,
                timestamp: Date.now(),
                data: {
                    stage: 'output',
                    action: outputCheck.action,
                    reason: outputCheck.reason,
                    mode: shieldMode,
                    details: outputCheck.details,
                },
            });
            if (outputCheck.action === 'block') {
                shieldBlocksTotal.inc({ stage: 'output', reason: outputCheck.details.hallucination ? 'hallucination' : 'other' });
                logger.warn('Run output blocked by Shield', {
                    runId,
                    reason: outputCheck.reason,
                    details: outputCheck.details
                });
                await store.updateRunStatus(runId, 'failed', {
                    completedAt: new Date().toISOString(),
                    output: {
                        error: 'Output blocked by Shield',
                        reason: outputCheck.reason,
                        details: outputCheck.details
                    },
                    logs: [{
                            timestamp: new Date().toISOString(),
                            level: 'error',
                            message: `Shield blocked output: ${outputCheck.reason}`
                        }]
                });
                runsCompleted.inc({ status: 'failed' });
                runsActive.dec();
                queueJobsCompleted.inc({ queue: 'run-execution' });
                if (eventStream) {
                    eventStream.emitError(runId, `Shield blocked output: ${outputCheck.reason}`);
                }
                return { success: false, runId };
            }
            if (outputCheck.action === 'mask' && outputCheck.sanitizedOutput) {
                shieldMasksTotal.inc({ stage: 'output', type: outputCheck.details.pii ? 'pii' : 'other' });
                logger.info('Shield masked output', { runId });
                output = outputCheck.sanitizedOutput;
            }
            if (outputCheck.action === 'rewrite' && outputCheck.details.hallucination) {
                logger.info('Shield attempting to rewrite output', {
                    runId,
                    reason: outputCheck.reason,
                    details: outputCheck.details
                });
                try {
                    const rewriteResult = await shield.attemptRewrite(output, outputCheck.details.hallucination, run.input);
                    if (rewriteResult.success && rewriteResult.rewrittenOutput) {
                        shieldRewritesTotal.inc({ success: 'true' });
                        logger.info('Shield successfully rewrote output', { runId });
                        output = rewriteResult.rewrittenOutput;
                    }
                    else {
                        shieldRewritesTotal.inc({ success: 'false' });
                        logger.warn('Shield rewrite failed, using original output', {
                            runId,
                            error: rewriteResult.error
                        });
                    }
                }
                catch (error) {
                    shieldRewritesTotal.inc({ success: 'false' });
                    logger.error('Shield rewrite error', {
                        runId,
                        error: error.message
                    });
                }
            }
            if (outputCheck.action === 'flag') {
                logger.info('Shield flagged output (monitoring mode)', {
                    runId,
                    reason: outputCheck.reason,
                    details: outputCheck.details
                });
            }
        }
        await job.updateProgress(80);
        const runDuration = (Date.now() - runStartTime) / 1000;
        await store.updateRunStatus(runId, 'completed', {
            completedAt: new Date().toISOString(),
            output,
            logs: [
                ...(run.logs || []),
                {
                    timestamp: new Date().toISOString(),
                    level: 'info',
                    message: 'Run completed',
                },
            ],
        });
        await job.updateProgress(100);
        runsCompleted.inc({ status: 'completed' });
        runsDuration.observe({ status: 'completed' }, runDuration);
        runsActive.dec();
        queueJobsCompleted.inc({ queue: 'run-execution' });
        queueJobDuration.observe({ queue: 'run-execution' }, runDuration);
        logger.info('Run execution completed', {
            runId,
            duration: runDuration
        });
        if (eventStream) {
            eventStream.emitFinish(runId, 'ok');
        }
        const connections = sseConnections?.get(runId);
        if (connections) {
            connections.forEach(connection => {
                connection.write(`event: end\ndata: ${JSON.stringify({
                    type: 'run_completed',
                    runId,
                    status: 'completed',
                    timestamp: new Date().toISOString()
                })}\n\n`);
                sseMessagesTotal?.inc();
            });
        }
        sentinelEventStreams?.delete(runId);
        return { success: true, runId };
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        const runDuration = (Date.now() - runStartTime) / 1000;
        const sentinelKillReason = extractSentinelKillReason(errorMessage);
        const isSentinelDeny = sentinelKillReason !== null;
        if (isSentinelDeny) {
            queueJobsCompleted.inc({ queue: 'run-execution' });
        }
        else {
            queueJobsFailed.inc({ queue: 'run-execution' });
        }
        queueJobDuration.observe({ queue: 'run-execution' }, runDuration);
        logger.error('Run execution failed', {
            runId,
            error: errorMessage,
            attemptsMade: job.attemptsMade,
            sentinelDeny: isSentinelDeny,
        });
        try {
            if (isSentinelDeny) {
                const killResult = await applyRunKill(runId, sentinelKillReason);
                if (killResult.applied) {
                    runsCompleted.inc({ status: 'killed' });
                    runsDuration.observe({ status: 'killed' }, runDuration);
                }
                sentinelEventStreams?.delete(runId);
                return { success: true, runId };
            }
            const store = getRunStore();
            const existingRun = await store.getRunById(runId);
            await store.updateRunStatus(runId, 'failed', {
                completedAt: new Date().toISOString(),
                output: { error: errorMessage },
                logs: appendRunLog(existingRun?.logs, {
                    timestamp: new Date().toISOString(),
                    level: 'error',
                    message: `Run failed: ${errorMessage}`,
                }),
            });
            runsCompleted.inc({ status: 'failed' });
            if (existingRun && !['killed', 'completed', 'failed'].includes(existingRun.status)) {
                runsActive.dec();
            }
            runsDuration.observe({ status: 'failed' }, runDuration);
            const eventStream = sentinelEventStreams?.get(runId);
            if (eventStream) {
                eventStream.emitError(runId, errorMessage);
            }
            const connections = sseConnections?.get(runId);
            if (connections) {
                connections.forEach(connection => {
                    connection.write(`event: end\ndata: ${JSON.stringify({
                        type: 'run_failed',
                        runId,
                        status: 'failed',
                        error: errorMessage,
                        timestamp: new Date().toISOString()
                    })}\n\n`);
                    sseMessagesTotal?.inc();
                });
            }
            sentinelEventStreams?.delete(runId);
        }
        catch (updateError) {
            logger.error('Failed to update run status after execution error', {
                runId,
                error: updateError instanceof Error ? updateError.message : String(updateError),
            });
        }
        throw error;
    }
}
//# sourceMappingURL=processor.js.map