import Fastify from 'fastify';
import bcrypt from 'bcryptjs';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { MemoryDatastoreProvider } from '@bytelyst/datastore';
import { setProvider } from '../../lib/datastore.js';
import { registerOptionalApiKeyContext } from '../../lib/api-key-auth.js';

const rawApiKey = `wai_${'b'.repeat(64)}`;

const repoMock = {
  listJobDefinitions: vi.fn(),
  getJobDefinition: vi.fn(),
  updateJobDefinition: vi.fn(),
  listJobRuns: vi.fn(),
};

const registryMock = {
  getJobHandler: vi.fn(),
};

const runnerMock = {
  executeJob: vi.fn(),
};

vi.mock('./repository.js', () => repoMock);
vi.mock('./registry.js', () => registryMock);
vi.mock('./runner.js', () => runnerMock);

async function seedApiKey(
  scopes: string[],
  tokenType: 'product_api' | 'service_api' = 'service_api'
) {
  const provider = new MemoryDatastoreProvider();
  setProvider(provider);
  const collection = provider.getCollection('api_tokens', '/id');
  await collection.create({
    id: 'tok_jobs_1',
    productId: 'lysnrai',
    userId: 'svc_jobs',
    userName: 'Jobs Service',
    tokenType,
    environment: 'dev',
    prefix: rawApiKey.slice(0, 12),
    tokenHash: await bcrypt.hash(rawApiKey, 10),
    status: 'active',
    scopes,
    expiresAt: '2099-01-01T00:00:00.000Z',
    lastUsed: null,
  });
}

describe('jobRoutes api key integration', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    delete process.env.API_KEY_RATE_LIMIT_CONFIG_JSON;
  });

  it('allows job reads via scoped api key', async () => {
    await seedApiKey(['jobs:read']);
    repoMock.listJobDefinitions.mockResolvedValue([{ id: 'job_sync', productId: 'lysnrai' }]);

    const { jobRoutes } = await import('./routes.js');
    const app = Fastify();
    await registerOptionalApiKeyContext(app);
    await app.register(jobRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'GET',
      url: '/api/jobs',
      headers: { 'x-api-key': rawApiKey },
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.listJobDefinitions).toHaveBeenCalledWith('lysnrai');
  });

  it('allows job trigger via scoped api key', async () => {
    await seedApiKey(['jobs:write']);
    registryMock.getJobHandler.mockReturnValue(() => Promise.resolve({ success: true }));
    repoMock.getJobDefinition.mockResolvedValue({
      id: 'job_reindex',
      productId: 'lysnrai',
      name: 'reindex',
    });
    runnerMock.executeJob.mockResolvedValue({ id: 'run_1', status: 'success' });

    const { jobRoutes } = await import('./routes.js');
    const app = Fastify();
    await registerOptionalApiKeyContext(app);
    await app.register(jobRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'POST',
      url: '/api/jobs/trigger',
      headers: { 'x-api-key': rawApiKey },
      payload: { jobName: 'reindex' },
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.getJobDefinition).toHaveBeenCalledWith('job_reindex', 'lysnrai');
    expect(runnerMock.executeJob).toHaveBeenCalled();
  });

  it('rejects product_api keys on job routes', async () => {
    await seedApiKey(['jobs:read'], 'product_api');
    repoMock.listJobDefinitions.mockResolvedValue([{ id: 'job_sync', productId: 'lysnrai' }]);

    const { jobRoutes } = await import('./routes.js');
    const app = Fastify();
    await registerOptionalApiKeyContext(app);
    await app.register(jobRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'GET',
      url: '/api/jobs',
      headers: { 'x-api-key': rawApiKey },
    });

    expect(res.statusCode).toBe(403);
    expect(repoMock.listJobDefinitions).not.toHaveBeenCalled();
  });
});
