import { describe, expect, it, vi } from 'vitest';
import {
  DEFAULT_JOB_TIMEOUT_MILLISECONDS,
  EngineManager,
  MAX_CONTACT_SHEETS_PER_ENGINE,
  MAX_EXECUTIONS_PER_ENGINE,
  MAX_PROBE_TIMEOUT_MILLISECONDS,
  MIN_PROBE_TIMEOUT_MILLISECONDS,
  MULTITHREAD_CODEC_THREAD_LIMIT,
  engineJobTimeoutMilliseconds,
  prepareEngineArguments,
  probeTimeoutMilliseconds,
  shouldRecycleExecutionCore,
} from '../../src/ffmpeg/EngineManager';

describe('prepareEngineArguments', () => {
  it('leaves single-thread commands unchanged', () => {
    const args = ['-i', '/input/source.mp4', '/work/output.mp4'];
    expect(
      prepareEngineArguments(
        args,
        'single-thread',
        ['/input/source.mp4'],
        ['/work/output.mp4']
      )
    ).toEqual(args);
  });

  it('bounds multithread input and output codec threads', () => {
    expect(
      prepareEngineArguments(
        ['-i', '/input/source.mp4', '-c:v', 'libx264', '/work/output.mp4'],
        'multithread',
        ['/input/source.mp4'],
        ['/work/output.mp4']
      )
    ).toEqual([
      '-threads',
      String(MULTITHREAD_CODEC_THREAD_LIMIT),
      '-i',
      '/input/source.mp4',
      '-c:v',
      'libx264',
      '-threads',
      String(MULTITHREAD_CODEC_THREAD_LIMIT),
      '/work/output.mp4',
    ]);
  });

  it('bounds a generated output pattern in the final output position', () => {
    expect(
      prepareEngineArguments(
        ['-i', '/input/source.mp4', '-f', 'segment', '/work/part-%03d.mp4'],
        'multithread',
        ['/input/source.mp4'],
        ['/work/part-001.mp4', '/work/part-002.mp4']
      )
    ).toEqual([
      '-threads',
      '2',
      '-i',
      '/input/source.mp4',
      '-f',
      'segment',
      '-threads',
      '2',
      '/work/part-%03d.mp4',
    ]);
  });
});

describe('engineJobTimeoutMilliseconds', () => {
  it('uses a bounded duration-aware watchdog', () => {
    expect(engineJobTimeoutMilliseconds({ expectedDurationSeconds: 2 })).toBe(
      120_000
    );
    expect(
      engineJobTimeoutMilliseconds({ expectedDurationSeconds: 10_000 })
    ).toBe(DEFAULT_JOB_TIMEOUT_MILLISECONDS);
  });

  it('honors a shorter explicit timeout and caps a longer one', () => {
    expect(
      engineJobTimeoutMilliseconds({
        expectedDurationSeconds: 30,
        timeoutMilliseconds: 5_000,
      })
    ).toBe(5_000);
    expect(
      engineJobTimeoutMilliseconds({
        timeoutMilliseconds: DEFAULT_JOB_TIMEOUT_MILLISECONDS * 2,
      })
    ).toBe(DEFAULT_JOB_TIMEOUT_MILLISECONDS);
  });
});

describe('probeTimeoutMilliseconds', () => {
  it('keeps small probes responsive, scales real media, and caps the watchdog', () => {
    expect(probeTimeoutMilliseconds(34_057)).toBe(
      MIN_PROBE_TIMEOUT_MILLISECONDS
    );
    expect(probeTimeoutMilliseconds(153_019_068)).toBe(72_966);
    expect(probeTimeoutMilliseconds(2 * 1024 * 1024 * 1024)).toBe(
      MAX_PROBE_TIMEOUT_MILLISECONDS
    );
  });

  it('rejects an already-cancelled probe before loading an engine', async () => {
    const factory = vi.fn(async () => {
      throw new Error('The engine factory must not be called.');
    });
    const manager = new EngineManager(factory);
    const controller = new AbortController();
    controller.abort();

    await expect(
      manager.probe(
        new File(['media'], 'cancelled.mp4', { type: 'video/mp4' }),
        undefined,
        controller.signal
      )
    ).rejects.toMatchObject({
      code: 'cancelled',
      message: 'The media probe was cancelled.',
    });
    expect(factory).not.toHaveBeenCalled();
  });
});

describe('execution core recycling policy', () => {
  it('refreshes only after the bounded idle-core job budget is exhausted', () => {
    expect(shouldRecycleExecutionCore(MAX_EXECUTIONS_PER_ENGINE - 1)).toBe(
      false
    );
    expect(shouldRecycleExecutionCore(MAX_EXECUTIONS_PER_ENGINE)).toBe(true);
  });

  it('refreshes before a fourth contact sheet hits retained tile-filter state', () => {
    expect(
      shouldRecycleExecutionCore(
        3,
        'contact-sheet',
        MAX_CONTACT_SHEETS_PER_ENGINE - 1
      )
    ).toBe(false);
    expect(
      shouldRecycleExecutionCore(
        3,
        'contact-sheet',
        MAX_CONTACT_SHEETS_PER_ENGINE
      )
    ).toBe(true);
    expect(
      shouldRecycleExecutionCore(
        3,
        'thumbnail-series',
        MAX_CONTACT_SHEETS_PER_ENGINE
      )
    ).toBe(false);
  });
});
