/**
 * Tests for durable extraction jobs — queue-backed create/get/list behavior.
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { MemoryQueueStore } from '@bytelyst/queue';
import { createJob, getJob, listJobs, resetJobStore, setJobStoreForTesting } from './jobs.js';
import { type WebhookConfig } from './webhooks.js';

vi.mock('../../lib/python-bridge.js', () => ({
  sidecarExtract: vi.fn(),
}));

vi.mock('./webhooks.js', async () => {
  const actual = await vi.importActual<typeof import('./webhooks.js')>('./webhooks.js');
  return {
    ...actual,
    triggerJobWebhook: vi.fn(),
  };
});

import { sidecarExtract } from '../../lib/python-bridge.js';
import { triggerJobWebhook } from './webhooks.js';

const mockSidecarExtract = vi.mocked(sidecarExtract);
const mockTriggerJobWebhook = vi.mocked(triggerJobWebhook);

describe('extraction jobs', () => {
  beforeEach(async () => {
    mockSidecarExtract.mockReset();
    mockTriggerJobWebhook.mockReset();
    await setJobStoreForTesting(new MemoryQueueStore());
    await resetJobStore();
  });

  it('creates a durable job and processes it', async () => {
    mockSidecarExtract.mockResolvedValue({
      extractions: [{ extraction_class: 'person', extraction_text: 'John' }],
      metadata: { model_id: 'gemini', duration_ms: 100, char_count: 20 },
    });

    const job = await createJob([{ text: 'Meet John at 3pm' }]);
    expect(['pending', 'processing', 'completed']).toContain(job.status);

    await vi.waitFor(async () => {
      const completed = await getJob(job.id);
      expect(completed?.status).toBe('completed');
      expect(completed?.results).toHaveLength(1);
      expect(completed?.results[0].extractions[0].extraction_text).toBe('John');
    });
  });

  it('marks the job failed when every input errors', async () => {
    mockSidecarExtract.mockRejectedValue(new Error('Sidecar unavailable'));

    const job = await createJob([{ text: 'fail me' }]);

    await vi.waitFor(async () => {
      const finished = await getJob(job.id);
      expect(finished?.status).toBe('failed');
      expect(finished?.errors[0].error).toBe('Sidecar unavailable');
    });
  });

  it('lists jobs newest first', async () => {
    mockSidecarExtract.mockResolvedValue({
      extractions: [],
      metadata: { model_id: 'test', duration_ms: 10, char_count: 5 },
    });

    const first = await createJob([{ text: 'First' }]);
    await new Promise(resolve => globalThis.setTimeout(resolve, 10));
    const second = await createJob([{ text: 'Second' }]);

    await vi.waitFor(async () => {
      const jobs = await listJobs();
      expect(jobs.length).toBeGreaterThanOrEqual(2);
    });

    const jobs = await listJobs();
    expect(jobs[0].id).toBe(second.id);
    expect(jobs.some(job => job.id === first.id)).toBe(true);
  });

  it('stores webhook config and triggers webhook on completion', async () => {
    mockSidecarExtract.mockResolvedValue({
      extractions: [],
      metadata: { model_id: 'test', duration_ms: 10, char_count: 5 },
    });
    mockTriggerJobWebhook.mockResolvedValue(undefined);

    const webhookConfig: WebhookConfig = {
      url: 'https://example.com/webhook',
      secret: 'secret',
      retryAttempts: 3,
    };

    const job = await createJob([{ text: 'test' }], 'req-123', webhookConfig);
    expect(job.webhookConfig).toEqual(webhookConfig);

    await vi.waitFor(async () => {
      const finished = await getJob(job.id);
      expect(finished?.status).toBe('completed');
    });

    expect(mockTriggerJobWebhook).toHaveBeenCalledWith(
      expect.objectContaining({ id: job.id, status: 'completed' }),
      webhookConfig
    );
  });
});
