/**
 * Integration Tests for Translation Routes
 *
 * Tests synchronous translation, asynchronous jobs, job status/cancellation,
 * and token estimation endpoints with comprehensive scenarios.
 */

// Mock external services BEFORE any imports
jest.mock('../../../queue', () => ({
  translationQueue: {
    add: jest.fn().mockResolvedValue({ id: 'mock-job-id' }),
    process: jest.fn(),
    on: jest.fn(),
    close: jest.fn().mockResolvedValue(undefined),
    getJob: jest.fn().mockResolvedValue(null),
    getJobs: jest.fn().mockResolvedValue([]),
    pause: jest.fn().mockResolvedValue(undefined),
    resume: jest.fn().mockResolvedValue(undefined),
    clean: jest.fn().mockResolvedValue([]),
    empty: jest.fn().mockResolvedValue(undefined),
  },
}));
jest.mock('../../../services/geminiClient');
jest.mock('../../../services/emailService');

import request from 'supertest';
import { createServer } from '../../../server';
import { prismaMock, mockUUID } from '../../setup';
import { Application } from 'express';
import { Prisma, TranslationJobStatus } from '@prisma/client';
import { Tone } from '../../../types';
import { geminiClient } from '../../../services/geminiClient';

describe('Translation Routes', () => {
  let app: Application;

  // Test user data
  const testUser = {
    id: 'user_test_123',
    email: 'test@example.com',
    status: 'active',
    email_verified: true,
    subscriptions: [
      {
        plugin: 'translate',
        plan_tier: 'professional',
        status: 'active',
        credit_balance: 100000,
        credit_allocation: 500000,
        rate_limit: 120,
      },
    ],
  };

  // Test API key
  const testApiKey = 'sk_test_abcdef123456';
  const testApiKeyHash = 'hashed_api_key';

  const decimal = (value: number) => new Prisma.Decimal(value);

  /**
   * A translationJob row as Prisma returns it: snake_case columns, Decimal money
   * columns and Date timestamps. The service layer calls .toNumber()/.toISOString()
   * on those, so plain numbers/strings would fault the route.
   */
  const jobRow = (overrides: Record<string, unknown> = {}) => ({
    id: mockUUID(1),
    user_id: testUser.id,
    client_job_id: null,
    plugin: 'translate',
    status: TranslationJobStatus.pending,
    source_lang: 'en',
    target_lang: 'es',
    tone: 'neutral',
    content: 'Hello, world! This is a test translation.',
    content_hash: 'content-hash',
    translation: null,
    model: null,
    characters_used: 0,
    tokens_used: 0,
    input_tokens: 0,
    output_tokens: 0,
    cost: decimal(0),
    customer_cost: decimal(0),
    error_message: null,
    processing_time_ms: null,
    callback_url: null,
    callback_secret: null,
    created_at: new Date('2026-01-01T00:00:00.000Z'),
    updated_at: new Date('2026-01-01T00:00:00.000Z'),
    completed_at: null,
    ...overrides,
  });

  const geminiTranslation = {
    translation: 'Hola, mundo! Esta es una traducción de prueba.',
    tokens_used: 15,
    input_tokens: 9,
    output_tokens: 6,
    processing_time_ms: 120,
    // Must be a model the provider pricing table knows, or cost calculation throws.
    model_used: 'gemini-3.1-flash-lite',
  };

  beforeAll(() => {
    app = createServer();
  });

  beforeEach(() => {
    // Mock API key validation: middleware resolves the key with its nested user
    // and that user's subscriptions.
    prismaMock.apiKey.findUnique.mockResolvedValue({
      id: 'key_123',
      user_id: testUser.id,
      key_hash: testApiKeyHash,
      prefix: 'sk_test_',
      name: 'Test Key',
      is_active: true,
      last_used_at: new Date(),
      created_at: new Date(),
      updated_at: new Date(),
      user: testUser,
    } as any);

    // Middleware fires a non-blocking last_used_at update on every request.
    prismaMock.apiKey.update.mockResolvedValue({ id: 'key_123' } as any);

    // Mock user lookup
    prismaMock.user.findUnique.mockResolvedValue(testUser as any);

    // Credit balance is the most recent credit transaction's balance_after
    prismaMock.creditTransaction.findFirst.mockResolvedValue({
      balance_after: 100000,
    } as any);
    prismaMock.creditTransaction.create.mockResolvedValue({
      id: 'tx_123',
      balance_after: 99959,
    } as any);

    // Interactive transactions run their callback against the same mock client.
    prismaMock.$transaction.mockImplementation(((callback: any) =>
      typeof callback === 'function' ? callback(prismaMock) : Promise.all(callback)) as any);
    prismaMock.$executeRaw.mockResolvedValue(1 as any);
    prismaMock.$queryRaw.mockResolvedValue([] as any);

    // The rate limiter resolves the caller's tier through tierService, which
    // caches subscription_tiers rows read from the database.
    prismaMock.subscriptionTier.findMany.mockResolvedValue([
      {
        id: 'tier_professional',
        slug: 'professional',
        plugin: 'translate',
        display_order: 2,
        is_active: true,
        rate_limit: 120,
        credit_allocation: 500000,
      },
    ] as any);

    // Customer price per character comes off the subscription row.
    prismaMock.subscription.findFirst.mockResolvedValue({
      customer_cost_per_char: decimal(0.0001),
    } as any);

    (geminiClient.translate as jest.Mock).mockResolvedValue(geminiTranslation as never);
  });

  describe('POST /v1/translate - Synchronous Translation', () => {
    const validRequest = {
      sourceLang: 'en',
      targetLang: 'es',
      content: 'Hello, world! This is a test translation.',
      tone: 'neutral' as Tone,
    };
    const validRequestCharacters = validRequest.content.length;

    it('should translate successfully with valid API key and sufficient credits', async () => {
      // Mock: No duplicate job
      prismaMock.translationJob.findFirst.mockResolvedValue(null);

      // Mock: Job creation
      const created = jobRow({
        id: mockUUID(10),
        status: TranslationJobStatus.processing,
      });
      prismaMock.translationJob.create.mockResolvedValue(created as any);
      prismaMock.translationJob.update.mockResolvedValue(created as any);

      const response = await request(app)
        .post('/v1/translate')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send(validRequest)
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.translation).toBe(geminiTranslation.translation);
      expect(response.body.characters_used).toBe(validRequestCharacters);
      expect(response.body.job_id).toBe(mockUUID(10));
      expect(response.body.status).toBe('completed');
      // The post-deduction balance comes off the credit transaction row the
      // deduction wrote (balance_after: 99959).
      expect(response.body.data.creditBalance).toBe(99959);
      // customerCost = characters * subscription.customer_cost_per_char, rounded to 4 decimals
      expect(response.body.cost_usd).toBe(
        Math.round(validRequestCharacters * 0.0001 * 10000) / 10000
      );
    });

    it('should return 401 without API key', async () => {
      // No Authorization header at all: the middleware rejects before any lookup.
      const response = await request(app)
        .post('/v1/translate')
        .send(validRequest)
        .expect(401);

      expect(response.body.success).toBe(false);
      expect(response.body.error.code).toBe('MISSING_API_KEY');
    });

    it('should return 401 for an unknown API key', async () => {
      // Mock: No API key found
      prismaMock.apiKey.findUnique.mockResolvedValue(null);

      const response = await request(app)
        .post('/v1/translate')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send(validRequest)
        .expect(401);

      expect(response.body.success).toBe(false);
      expect(response.body.error.code).toBe('INVALID_API_KEY');
    });

    it('should return 402 insufficient credits', async () => {
      // Mock: User with low credits
      prismaMock.creditTransaction.findFirst.mockResolvedValue({
        balance_after: 5,
      } as any);

      const response = await request(app)
        .post('/v1/translate')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send(validRequest)
        .expect(402);

      expect(response.body.error).toBe(true);
      expect(response.body.code).toBe('INSUFFICIENT_CREDITS');
      expect(response.body.message).toContain('Insufficient credits');
    });

    it('should return 400 for content exceeding sync limit (5000 chars)', async () => {
      const longContent = 'a'.repeat(5001);

      const response = await request(app)
        .post('/v1/translate')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          ...validRequest,
          content: longContent,
        })
        .expect(400);

      expect(response.body.code).toBe('VALIDATION_ERROR');
      expect(response.body.errors[0].field).toBe('content');
      expect(response.body.errors[0].message).toContain('5000 characters');
    });

    it('should return 400 for invalid language codes', async () => {
      const response = await request(app)
        .post('/v1/translate')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          ...validRequest,
          sourceLang: 'invalid',
          targetLang: 'also-invalid',
        })
        .expect(400);

      expect(response.body.code).toBe('VALIDATION_ERROR');
      expect(response.body.errors.map((e: any) => e.field)).toEqual(
        expect.arrayContaining(['sourceLang', 'targetLang'])
      );
    });

    it('should return cached translation for duplicate content', async () => {
      // Mock: Duplicate completed job within the dedup window
      prismaMock.translationJob.findFirst.mockResolvedValue(
        jobRow({
          id: mockUUID(20),
          status: TranslationJobStatus.completed,
          translation: 'Hola, mundo! (cached)',
          characters_used: validRequestCharacters,
          cost: decimal(0.000015),
          customer_cost: decimal(0.00041),
          processing_time_ms: 50,
        }) as any
      );

      const response = await request(app)
        .post('/v1/translate')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send(validRequest)
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.job_id).toBe(mockUUID(20));
      expect(response.body.translation).toBe('Hola, mundo! (cached)');
      // A cache hit must not call the translation provider.
      expect(geminiClient.translate).not.toHaveBeenCalled();
      expect(prismaMock.translationJob.create).not.toHaveBeenCalled();
    });

    it('should ignore an unrecognized model field (model selection is code-driven, not client-supplied)', async () => {
      prismaMock.translationJob.findFirst.mockResolvedValue(null);
      const created = jobRow({ id: mockUUID(30), status: TranslationJobStatus.processing });
      prismaMock.translationJob.create.mockResolvedValue(created as any);
      prismaMock.translationJob.update.mockResolvedValue(created as any);

      const response = await request(app)
        .post('/v1/translate')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          ...validRequest,
          model: 'invalid-model',
        })
        .expect(200);

      expect(response.body.success).toBe(true);

      // The client value is stripped by the schema and never persisted.
      const createData = (prismaMock.translationJob.create.mock.calls[0][0] as any).data;
      expect(createData).not.toHaveProperty('model');
      const updateData = (prismaMock.translationJob.update.mock.calls[0][0] as any).data;
      expect(updateData.model).toBe(geminiTranslation.model_used);
    });

    it('should validate tone parameter', async () => {
      const response = await request(app)
        .post('/v1/translate')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          ...validRequest,
          tone: 'invalid-tone',
        })
        .expect(400);

      expect(response.body.code).toBe('VALIDATION_ERROR');
      expect(response.body.errors[0].field).toBe('tone');
    });
  });

  describe('POST /v1/jobs - Asynchronous Translation', () => {
    const validJobRequest = {
      sourceLang: 'en',
      targetLang: 'fr',
      content: 'This is a longer content that will be processed asynchronously.',
      tone: 'formal' as Tone,
      callbackUrl: 'https://example.com/webhook',
    };

    it('should submit async job successfully', async () => {
      // Mock: No duplicate job
      prismaMock.translationJob.findFirst.mockResolvedValue(null);

      // Mock: Job creation
      prismaMock.translationJob.create.mockResolvedValue(
        jobRow({
          id: mockUUID(40),
          target_lang: 'fr',
          tone: 'formal',
          callback_url: validJobRequest.callbackUrl,
        }) as any
      );

      const response = await request(app)
        .post('/v1/jobs')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send(validJobRequest)
        .expect(202);

      expect(response.body.success).toBe(true);
      expect(response.body.data.jobId).toBe(mockUUID(40));
      expect(response.body.data.status).toBe('pending');
    });

    it('should submit job with client job ID', async () => {
      prismaMock.translationJob.findFirst.mockResolvedValue(null);

      prismaMock.translationJob.create.mockResolvedValue(
        jobRow({
          id: mockUUID(41),
          client_job_id: 'wp_post_456',
          target_lang: 'fr',
          tone: 'formal',
        }) as any
      );

      const response = await request(app)
        .post('/v1/jobs')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          ...validJobRequest,
          clientJobId: 'wp_post_456',
        })
        .expect(202);

      expect(response.body.data.jobId).toBe(mockUUID(41));
      expect(response.body.data.clientJobId).toBe('wp_post_456');
    });

    it('should prevent duplicate job submission', async () => {
      // Mock: Duplicate pending job inside the dedup window
      prismaMock.translationJob.findFirst.mockResolvedValue(
        jobRow({
          id: mockUUID(42),
          status: TranslationJobStatus.pending,
          target_lang: 'fr',
          tone: 'formal',
        }) as any
      );

      const response = await request(app)
        .post('/v1/jobs')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send(validJobRequest)
        .expect(202);

      // Should return existing job, without creating another one
      expect(response.body.data.jobId).toBe(mockUUID(42));
      expect(response.body.data.status).toBe('pending');
      expect(prismaMock.translationJob.create).not.toHaveBeenCalled();
    });

    it('should reject content exceeding async limit (50000 chars)', async () => {
      const veryLongContent = 'a'.repeat(50001);

      const response = await request(app)
        .post('/v1/jobs')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          ...validJobRequest,
          content: veryLongContent,
        })
        .expect(400);

      expect(response.body.code).toBe('VALIDATION_ERROR');
      expect(response.body.errors[0].message).toContain('50000 character');
    });

    it('should return 402 for insufficient credits', async () => {
      prismaMock.creditTransaction.findFirst.mockResolvedValue({
        balance_after: 0,
      } as any);

      const response = await request(app)
        .post('/v1/jobs')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send(validJobRequest)
        .expect(402);

      expect(response.body.error).toBe(true);
      expect(response.body.code).toBe('INSUFFICIENT_CREDITS');
    });
  });

  describe('GET /v1/jobs/:jobId - Job Status', () => {
    it('should get job status successfully', async () => {
      prismaMock.translationJob.findUnique.mockResolvedValue(
        jobRow({
          id: mockUUID(50),
          client_job_id: 'wp_789',
          status: TranslationJobStatus.processing,
          target_lang: 'de',
        }) as any
      );

      const response = await request(app)
        .get(`/v1/jobs/${mockUUID(50)}`)
        .set('Authorization', `Bearer ${testApiKey}`)
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.data.jobId).toBe(mockUUID(50));
      expect(response.body.data.status).toBe('processing');
      expect(response.body.data.clientJobId).toBe('wp_789');
    });

    it('should return 404 for non-existent job', async () => {
      prismaMock.translationJob.findUnique.mockResolvedValue(null);

      const response = await request(app)
        .get(`/v1/jobs/${mockUUID(51)}`)
        .set('Authorization', `Bearer ${testApiKey}`)
        .expect(404);

      expect(response.body.error).toBe(true);
      expect(response.body.code).toBe('JOB_NOT_FOUND');
    });

    it('should return 403 for unauthorized access (different user)', async () => {
      prismaMock.translationJob.findUnique.mockResolvedValue(
        jobRow({
          id: mockUUID(52),
          user_id: 'other_user_999',
          status: TranslationJobStatus.completed,
        }) as any
      );

      const response = await request(app)
        .get(`/v1/jobs/${mockUUID(52)}`)
        .set('Authorization', `Bearer ${testApiKey}`)
        .expect(403);

      expect(response.body.error).toBe(true);
      expect(response.body.code).toBe('ACCESS_DENIED');
    });

    it('should return completed job with translation', async () => {
      prismaMock.translationJob.findUnique.mockResolvedValue(
        jobRow({
          id: mockUUID(53),
          status: TranslationJobStatus.completed,
          target_lang: 'ja',
          tone: 'casual',
          translation: 'こんにちは、世界！',
          characters_used: 25,
          cost: decimal(0.00005),
          customer_cost: decimal(0.000125),
          processing_time_ms: 1200,
          completed_at: new Date('2026-01-01T00:05:00.000Z'),
        }) as any
      );

      const response = await request(app)
        .get(`/v1/jobs/${mockUUID(53)}`)
        .set('Authorization', `Bearer ${testApiKey}`)
        .expect(200);

      expect(response.body.data.status).toBe('completed');
      expect(response.body.data.translation).toBe('こんにちは、世界！');
      expect(response.body.data.charactersUsed).toBe(25);
      // The external API exposes customer_cost as `cost`.
      expect(response.body.data.cost).toBe(0.000125);
    });

    it('should return failed job with error message', async () => {
      prismaMock.translationJob.findUnique.mockResolvedValue(
        jobRow({
          id: mockUUID(54),
          status: TranslationJobStatus.failed,
          target_lang: 'ko',
          error_message: 'Translation service timeout',
        }) as any
      );

      const response = await request(app)
        .get(`/v1/jobs/${mockUUID(54)}`)
        .set('Authorization', `Bearer ${testApiKey}`)
        .expect(200);

      expect(response.body.data.status).toBe('failed');
      expect(response.body.data.errorMessage).toBe('Translation service timeout');
    });

    it('should validate UUID format for job ID', async () => {
      const response = await request(app)
        .get('/v1/jobs/invalid-uuid-format')
        .set('Authorization', `Bearer ${testApiKey}`)
        .expect(400);

      expect(response.body.code).toBe('VALIDATION_ERROR');
      expect(response.body.errors[0].field).toBe('jobId');
      expect(response.body.errors[0].message).toBe('Invalid job ID format');
    });
  });

  describe('POST /v1/jobs/:jobId/cancel - Cancel Job', () => {
    it('rejects cancellation when API-key authentication has no bound site', async () => {
      const pendingJob = jobRow({ id: mockUUID(60), status: TranslationJobStatus.pending });
      prismaMock.translationJob.findUnique.mockResolvedValue(pendingJob as any);

      const response = await request(app)
        .post(`/v1/jobs/${mockUUID(60)}/cancel`)
        .set('Authorization', `Bearer ${testApiKey}`)
        .expect(403);

      expect(response.body).toMatchObject({ error: true, code: 'SITE_SCOPE_REQUIRED' });
      expect(prismaMock.translationJob.findUnique).not.toHaveBeenCalled();
      expect(prismaMock.translationJob.updateMany).not.toHaveBeenCalled();
    });
  });

  describe('POST /v1/estimate - Token Estimation', () => {
    it('should estimate tokens for single language successfully', async () => {
      const estimateRequest = {
        content: 'This is a test content for token estimation.',
        source_lang: 'en',
        target_langs: ['es'],
      };

      const response = await request(app)
        .post('/v1/estimate')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send(estimateRequest)
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.data.total_tokens).toBeGreaterThan(0);
      // estimates is keyed by target language code
      expect(Object.keys(response.body.data.estimates)).toEqual(['es']);
      expect(response.body.data.estimates.es.estimated_tokens).toBeGreaterThan(0);
    });

    it('should estimate tokens for multiple languages', async () => {
      const estimateRequest = {
        content: 'Multi-language estimation test.',
        source_lang: 'en',
        target_langs: ['es', 'fr', 'de', 'ja'],
      };

      const response = await request(app)
        .post('/v1/estimate')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send(estimateRequest)
        .expect(200);

      expect(Object.keys(response.body.data.estimates)).toEqual(['es', 'fr', 'de', 'ja']);
      expect(response.body.data.total_tokens).toBeGreaterThan(0);
    });

    it('should validate content length', async () => {
      const veryLongContent = 'a'.repeat(50001);

      const response = await request(app)
        .post('/v1/estimate')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          content: veryLongContent,
          source_lang: 'en',
          target_langs: ['es'],
        })
        .expect(400);

      expect(response.body.code).toBe('VALIDATION_ERROR');
      expect(response.body.errors[0].field).toBe('content');
    });

    it('should validate language codes', async () => {
      const response = await request(app)
        .post('/v1/estimate')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          content: 'Test',
          source_lang: 'invalid',
          target_langs: ['also-invalid'],
        })
        .expect(400);

      expect(response.body.code).toBe('VALIDATION_ERROR');
      expect(response.body.errors.map((e: any) => e.field)).toEqual(
        expect.arrayContaining(['source_lang', 'target_langs.0'])
      );
    });

    it('should validate target_langs is non-empty array', async () => {
      const response = await request(app)
        .post('/v1/estimate')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          content: 'Test',
          source_lang: 'en',
          target_langs: [],
        })
        .expect(400);

      expect(response.body.code).toBe('VALIDATION_ERROR');
      expect(response.body.errors[0].field).toBe('target_langs');
    });

    it('should limit target languages to maximum 20', async () => {
      const tooManyLanguages = Array(21).fill('es');

      const response = await request(app)
        .post('/v1/estimate')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          content: 'Test',
          source_lang: 'en',
          target_langs: tooManyLanguages,
        })
        .expect(400);

      expect(response.body.code).toBe('VALIDATION_ERROR');
      expect(response.body.errors[0].field).toBe('target_langs');
    });
  });

  describe('POST /v1/jobs/:jobId/retry - Retry Failed Job', () => {
    it('should retry failed job successfully', async () => {
      const failedJob = jobRow({
        id: mockUUID(70),
        status: TranslationJobStatus.failed,
        target_lang: 'pt',
        error_message: 'Previous error',
      });

      prismaMock.translationJob.findUnique.mockResolvedValue(failedJob as any);
      prismaMock.translationJob.update.mockResolvedValue({
        ...failedJob,
        status: TranslationJobStatus.pending,
        error_message: null,
      } as any);

      const response = await request(app)
        .post(`/v1/jobs/${mockUUID(70)}/retry`)
        .set('Authorization', `Bearer ${testApiKey}`)
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.data.jobId).toBe(mockUUID(70));
      expect(response.body.data.status).toBe('pending');
      expect(response.body.message).toContain('retry');
    });

    it('should return 400 when trying to retry non-failed job', async () => {
      prismaMock.translationJob.findUnique.mockResolvedValue(
        jobRow({ id: mockUUID(71), status: TranslationJobStatus.completed }) as any
      );

      const response = await request(app)
        .post(`/v1/jobs/${mockUUID(71)}/retry`)
        .set('Authorization', `Bearer ${testApiKey}`)
        .expect(400);

      expect(response.body.error).toBe(true);
      expect(response.body.code).toBe('CANNOT_RETRY');
    });
  });

  describe('GET /v1/jobs - List Jobs', () => {
    it('should list all jobs for authenticated user', async () => {
      prismaMock.translationJob.findMany.mockResolvedValue([
        jobRow({ id: mockUUID(80), status: TranslationJobStatus.completed }),
        jobRow({ id: mockUUID(81), status: TranslationJobStatus.pending, target_lang: 'fr', tone: 'formal' }),
      ] as any);
      prismaMock.translationJob.count.mockResolvedValue(2);

      const response = await request(app)
        .get('/v1/jobs')
        .set('Authorization', `Bearer ${testApiKey}`)
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.data).toHaveLength(2);
      expect(response.body.data.map((job: any) => job.jobId)).toEqual([mockUUID(80), mockUUID(81)]);
      expect(response.body.pagination.total).toBe(2);
    });

    it('should filter jobs by status', async () => {
      prismaMock.translationJob.findMany.mockResolvedValue([
        jobRow({ id: mockUUID(82), status: TranslationJobStatus.pending, target_lang: 'de' }),
      ] as any);
      prismaMock.translationJob.count.mockResolvedValue(1);

      const response = await request(app)
        .get('/v1/jobs?status=pending')
        .set('Authorization', `Bearer ${testApiKey}`)
        .expect(200);

      expect(response.body.data).toHaveLength(1);
      expect(response.body.data[0].status).toBe('pending');
      expect((prismaMock.translationJob.findMany.mock.calls[0][0] as any).where).toEqual({
        user_id: testUser.id,
        plugin: 'translate',
        status: 'pending',
      });
    });

    it('should paginate results', async () => {
      prismaMock.translationJob.findMany.mockResolvedValue([]);
      prismaMock.translationJob.count.mockResolvedValue(100);

      const response = await request(app)
        .get('/v1/jobs?limit=10&offset=20')
        .set('Authorization', `Bearer ${testApiKey}`)
        .expect(200);

      expect(response.body.pagination.limit).toBe(10);
      expect(response.body.pagination.offset).toBe(20);
      expect(response.body.pagination.total).toBe(100);
      expect(response.body.pagination.hasMore).toBe(true);
    });
  });
});
