/**
 * applyRunKill unit tests
 */

import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import { MemoryRunStore } from '../runs/memoryRunStore.js';
import {
  applyRunKill,
  setRunKillSideEffects,
  isTerminalRunStatus,
} from '../runs/run-kill.js';
import { resetMetrics, sentinelKillsTotal } from '../metrics/index.js';

const mockPublish = jest.fn<() => Promise<void>>().mockResolvedValue(undefined);

jest.mock('../db/redis.js', () => ({
  getRedisClient: jest.fn(() => Promise.resolve({ publish: mockPublish })),
}));

jest.mock('../queue/index.js', () => ({
  removeRunExecutionJob: jest.fn(() => Promise.resolve()),
}));

const storeRef: { current: MemoryRunStore } = { current: new MemoryRunStore() };

jest.mock('../runs/index.js', () => ({
  getRunStore: () => storeRef.current,
}));

describe('applyRunKill', () => {
  beforeEach(async () => {
    resetMetrics();
    mockPublish.mockClear();
    setRunKillSideEffects({});
    storeRef.current = new MemoryRunStore();
    await storeRef.current.createRun({ id: 'run-1', name: 'Test', input: {} });
    await storeRef.current.updateRunStatus('run-1', 'running', {
      startedAt: new Date().toISOString(),
      logs: [{ timestamp: 't0', level: 'info', message: 'started' }],
    });
  });

  it('persists killed status, appends logs, and publishes once', async () => {
    const result = await applyRunKill('run-1', 'policy_violation:timeout');

    expect(result.applied).toBe(true);
    expect(result.idempotent).toBe(false);

    const run = await storeRef.current.getRunById('run-1');
    expect(run?.status).toBe('killed');
    expect(run?.logs).toHaveLength(2);
    expect(run?.logs?.[0]?.message).toBe('started');
    expect(run?.output).toMatchObject({
      source: 'sentinel',
      action: 'deny',
      policy: 'timeout',
    });

    expect(mockPublish).toHaveBeenCalled();
    const metrics = await sentinelKillsTotal.get();
    expect(metrics.values.some((v) => v.labels.trigger === 'policy')).toBe(true);
  });

  it('is idempotent for terminal runs and does not publish', async () => {
    await applyRunKill('run-1', 'manual_cancellation');
    mockPublish.mockClear();
    resetMetrics();

    const result = await applyRunKill('run-1', 'manual_cancellation');

    expect(result.applied).toBe(false);
    expect(result.idempotent).toBe(true);
    expect(mockPublish).not.toHaveBeenCalled();
  });

  it('isTerminalRunStatus covers killed/completed/failed', () => {
    expect(isTerminalRunStatus('killed')).toBe(true);
    expect(isTerminalRunStatus('running')).toBe(false);
  });
});
