import { describe, it, expect, vi } from 'vitest';
import { Worker } from './worker.js';
import type { AppConfig } from './config.js';
import type { Job } from './db/repository.js';

function makeConfig(): AppConfig {
  return {
    provider: {
      model: 'test-model',
      workers: [{ id: 'worker-1', endpoint: 'http://localhost:11434/v1', roles: ['auto', 'fast', 'quality', 'reflection'] }],
    },
    worktreeDir: '/tmp/worker-reflection-dispatch-test',
    concurrency: 1,
    maxMovements: 30,
    retry: { maxAttempts: 3, backoffSeconds: [60, 300, 900] },
    ask: { maxPerJob: 2 },
    subtasks: { maxDepth: 2 },
    tools: {
      searxngUrl: 'http://localhost:8080',
      visionModel: 'vision',
      visionTimeout: 60,
      visionMaxTokens: 1024,
      webfetchTimeout: 30,
      websearchTimeout: 15,
      webfetchAllowedHosts: [],
    },
  };
}

function makeReflectionJob(): Job {
  return {
    id: 'job-reflect-1',
    repo: 'local/reflection-job-orig-1',
    issueNumber: 0,
    prNumber: null,
    status: 'running',
    pieceName: 'reflection',
    requiredRole: 'reflection',
    requiredProfile: 'reflection',
    currentMovement: null,
    instruction: '',
    branchName: null,
    worktreePath: null,
    attempt: 1,
    maxAttempts: 3,
    nextRetryAt: null,
    errorSummary: null,
    resumeMovement: null,
    askCount: 0,
    workerId: 'worker-1',
    parentJobId: null,
    subtaskDepth: 0,
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
    taskKind: 'reflection',
    payload: JSON.stringify({
      originalJobId: 'job-orig-1',
      userId: 'u-1',
      pieceName: 'chat',
      outcome: 'succeeded',
    }),
    ownerId: 'u-1',
    visibility: 'private',
    visibilityScopeOrgId: null,
    browserSessionProfileId: null,
  } as unknown as Job;
}

describe('worker reflection dispatch', () => {
  it('a task_kind=reflection job reaches a terminal state without going through the agent loop', async () => {
    // Track all updateJob calls so we can verify a terminal status is set.
    const updateJobCalls: Array<[string, Record<string, unknown>]> = [];

    const repo = {
      lockIssue: vi.fn().mockResolvedValue(true),
      unlockIssue: vi.fn().mockResolvedValue(undefined),
      addAuditLog: vi.fn().mockResolvedValue(undefined),
      updateWorkerNodeHealth: vi.fn().mockResolvedValue(undefined),
      updateJob: vi.fn().mockImplementation((id: string, patch: Record<string, unknown>) => {
        updateJobCalls.push([id, patch]);
        return Promise.resolve(undefined);
      }),
    };

    const worker = new Worker(
      'worker-1',
      'http://localhost:11434/v1',
      'test-model',
      repo as never,
      makeConfig(),
    );

    const job = makeReflectionJob();
    await (worker as unknown as { executeJob: (job: Job) => Promise<void> }).executeJob(job);

    // handleReflectionJob must have called updateJob with a terminal status.
    // Note: the reflection-runner skeleton returns 'failed', so the status will be
    // 'failed' until Phase 3.2+ implements real logic. We accept either terminal status
    // to avoid coupling the test to the stub's return value.
    const terminalCall = updateJobCalls.find(
      ([id, patch]) =>
        id === 'job-reflect-1' &&
        (patch.status === 'succeeded' || patch.status === 'failed'),
    );
    expect(terminalCall).toBeDefined();

    // The agent-loop path (loadPiece) must NOT have been called.
    // We verify this indirectly: lockIssue was called (got past acquireJobOrRequeue),
    // but no 'Piece not found' / loadPiece side-effects occurred
    // (the mock repo has no loadPiece, so if it were called it would throw).
    expect(repo.lockIssue).toHaveBeenCalledWith('local/reflection-job-orig-1', 0, 'job-reflect-1');
    expect(repo.unlockIssue).toHaveBeenCalledWith('local/reflection-job-orig-1', 0);
  });
});
