import { prismaMock } from '../setup';

jest.mock('../../queue', () => ({
  translationQueue: {
    add: jest.fn(),
  },
}));

jest.mock('../../services/geminiClient', () => ({
  geminiClient: {
    translateBulk: jest.fn(),
  },
}));

jest.mock('../../services/creditService', () => ({
  deductCredits: jest.fn(),
  getCurrentBalance: jest.fn(),
}));

jest.mock('../../utils/encryption', () => ({
  generateContentHash: jest.fn(),
}));

jest.mock('../../utils/logger', () => ({
  logger: {
    debug: jest.fn(),
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
  },
}));

jest.mock('../../utils/metrics', () => ({
  trackTranslationJob: jest.fn(),
  trackTokensProcessed: jest.fn(),
}));

import { TranslationService } from '../../services/translationService';
import { geminiClient } from '../../services/geminiClient';
import { deductCredits, getCurrentBalance } from '../../services/creditService';
import { generateContentHash } from '../../utils/encryption';
import { Tone } from '../../types';

const mockGeminiBulk = geminiClient.translateBulk as jest.MockedFunction<typeof geminiClient.translateBulk>;
const mockDeductCredits = deductCredits as jest.MockedFunction<typeof deductCredits>;
const mockGetCurrentBalance = getCurrentBalance as jest.MockedFunction<typeof getCurrentBalance>;
const mockGenerateContentHash = generateContentHash as jest.MockedFunction<typeof generateContentHash>;

const USER_ID = 'user-batching';
const JOB_ID = 'job-batching';
const MODEL_USED = 'gemini-3.1-flash-lite';

function makeRequest(strings: Array<{ id: string; content: string }>) {
  return {
    strings,
    sourceLang: 'en',
    targetLang: 'es',
    tone: Tone.NEUTRAL,
  };
}

describe('TranslationService bulk batching', () => {
  let service: TranslationService;

  beforeEach(() => {
    service = new TranslationService();

    mockGeminiBulk.mockReset();
    mockDeductCredits.mockReset();
    mockGetCurrentBalance.mockReset();
    mockGenerateContentHash.mockReset();

    mockGetCurrentBalance.mockResolvedValue(1_000_000);
    mockDeductCredits.mockResolvedValue({
      id: 'credit-transaction',
      balanceAfter: 999_000,
    } as any);
    mockGenerateContentHash.mockReturnValue('bulk-content-hash');
    prismaMock.translationJob.create.mockResolvedValue({ id: JOB_ID } as any);
    prismaMock.translationJob.update.mockResolvedValue({ id: JOB_ID } as any);

    mockGeminiBulk.mockImplementation(async (batch) => ({
      results: batch.map(({ id, content }) => ({
        id,
        translation: `translated-${content}`,
        success: true,
      })),
      model_used: MODEL_USED,
      tokens_used: batch.length,
      input_tokens: batch.length,
      output_tokens: batch.length,
    }));
  });

  it('packs count-capped batches, merges all results, and preserves input order', async () => {
    const strings = Array.from({ length: 250 }, (_, index) => ({
      id: `string-${index}`,
      content: `source-${index}`,
    }));

    const result = await service.translateBulkSync(USER_ID, makeRequest(strings));

    expect(mockGeminiBulk).toHaveBeenCalledTimes(3);
    expect(mockGeminiBulk.mock.calls.map(([batch]) => batch.length)).toEqual([100, 100, 50]);
    expect(result.results).toEqual(strings.map(({ id, content }) => ({
      id,
      translation: `translated-${content}`,
      success: true,
    })));
    expect(result.results).toHaveLength(strings.length);
    expect(result.failedCount).toBe(0);
    expect(result.totalCharactersUsed).toBe(
      strings.reduce((total, string) => total + string.content.length, 0)
    );
    expect(mockDeductCredits).toHaveBeenCalledTimes(1);
  });

  it('uses one Gemini call for a small request', async () => {
    const strings = [
      { id: 'first', content: 'first source' },
      { id: 'second', content: 'second source' },
    ];

    const result = await service.translateBulkSync(USER_ID, makeRequest(strings));

    expect(mockGeminiBulk).toHaveBeenCalledTimes(1);
    expect(mockGeminiBulk).toHaveBeenCalledWith(strings, 'en', 'es', Tone.NEUTRAL);
    expect(result.results.map(({ id }) => id)).toEqual(['first', 'second']);
  });
});
