import { beforeEach, describe, expect, test, vi } from 'vitest';
import { NextRequest } from 'next/server';

const hoisted = vi.hoisted(() => ({
  db: null as {
    select: ReturnType<typeof vi.fn>;
  } | null,
  row: {
    id: 'doc-1',
    type: 'pdf',
  },
  requireAuthContext: vi.fn(),
  readCurrentParsedPdfArtifact: vi.fn(),
  readParsedPdfArtifactByKey: vi.fn(),
  lookupCurrentPdfParseOperation: vi.fn(),
  createOrReuseCurrentPdfParseOperation: vi.fn(),
  checkJobRate: vi.fn(),
  getPdfLayoutRateConfig: vi.fn(),
  getResolvedRuntimeConfig: vi.fn(),
  buildComputeRateLimitedResponse: vi.fn(),
}));

vi.mock('@/db', () => ({
  get db() {
    return hoisted.db;
  },
}));

vi.mock('@/lib/server/auth/auth', () => ({
  requireAuthContext: hoisted.requireAuthContext,
}));

vi.mock('@/lib/server/pdf-parse/artifact', () => ({
  readCurrentParsedPdfArtifact: hoisted.readCurrentParsedPdfArtifact,
  readParsedPdfArtifactByKey: hoisted.readParsedPdfArtifactByKey,
}));

vi.mock('@/lib/server/pdf-parse/operation', () => ({
  lookupCurrentPdfParseOperation: hoisted.lookupCurrentPdfParseOperation,
  createOrReuseCurrentPdfParseOperation: hoisted.createOrReuseCurrentPdfParseOperation,
}));

vi.mock('@/lib/server/rate-limit/job-rate-limiter', () => ({
  checkJobRate: hoisted.checkJobRate,
  getPdfLayoutRateConfig: hoisted.getPdfLayoutRateConfig,
}));

vi.mock('@/lib/server/rate-limit/problem-response', () => ({
  buildComputeRateLimitedResponse: hoisted.buildComputeRateLimitedResponse,
}));

vi.mock('@/lib/server/runtime-config', () => ({
  getResolvedRuntimeConfig: hoisted.getResolvedRuntimeConfig,
}));

vi.mock('@/lib/server/documents/blobstore', () => ({
  isValidDocumentId: vi.fn(() => true),
}));

vi.mock('@/lib/server/storage/s3', () => ({
  isS3Configured: vi.fn(() => true),
}));

vi.mock('@/lib/server/testing/test-namespace', () => ({
  getOpenReaderTestNamespace: vi.fn(() => null),
}));

vi.mock('@/lib/server/logger', () => ({
  createRequestLogger: vi.fn(() => ({
    logger: {
      warn: vi.fn(),
      info: vi.fn(),
      error: vi.fn(),
    },
    requestId: 'req-test',
  })),
}));

describe('GET/POST /api/documents/[id]/parsed worker flow', () => {
  beforeEach(() => {
    hoisted.db = {
      select: vi.fn(() => ({
        from: vi.fn(() => ({
          where: vi.fn(() => ({
            limit: vi.fn(async () => [{ ...hoisted.row }]),
          })),
        })),
      })),
    };
    hoisted.requireAuthContext.mockReset();
    hoisted.requireAuthContext.mockResolvedValue({ userId: 'user-1' });
    hoisted.readCurrentParsedPdfArtifact.mockReset();
    hoisted.readCurrentParsedPdfArtifact.mockResolvedValue(null);
    hoisted.readParsedPdfArtifactByKey.mockReset();
    hoisted.readParsedPdfArtifactByKey.mockResolvedValue(null);
    hoisted.lookupCurrentPdfParseOperation.mockReset();
    hoisted.lookupCurrentPdfParseOperation.mockResolvedValue(null);
    hoisted.createOrReuseCurrentPdfParseOperation.mockReset();
    hoisted.checkJobRate.mockReset();
    hoisted.checkJobRate.mockResolvedValue({ allowed: true });
    hoisted.getPdfLayoutRateConfig.mockReset();
    hoisted.getPdfLayoutRateConfig.mockReturnValue({});
    hoisted.getResolvedRuntimeConfig.mockReset();
    hoisted.getResolvedRuntimeConfig.mockResolvedValue({});
    hoisted.buildComputeRateLimitedResponse.mockReset();
  });

  test('GET returns pending when no current artifact or worker op exists', async () => {
    const { GET } = await import('../../src/app/api/documents/[id]/parsed/route');
    const response = await GET(new NextRequest('http://localhost/api/documents/doc-1/parsed'), {
      params: Promise.resolve({ id: 'doc-1' }),
    });

    expect(response.status).toBe(409);
    await expect(response.json()).resolves.toMatchObject({
      parseStatus: 'pending',
      parseProgress: null,
      opId: null,
    });
  });

  test('GET reads the artifact referenced by a succeeded worker op', async () => {
    hoisted.lookupCurrentPdfParseOperation.mockResolvedValue({
      opId: 'op-1',
      opKey: 'pdf_layout|v1|parser|doc-1||doc-key|',
      kind: 'pdf_layout',
      jobId: 'job-1',
      status: 'succeeded',
      queuedAt: Date.now() - 1000,
      updatedAt: Date.now(),
      result: { parsedObjectKey: 'parsed-key.json' },
    });
    hoisted.readParsedPdfArtifactByKey.mockResolvedValue({
      key: 'parsed-key.json',
      bytes: Buffer.from(JSON.stringify({ documentId: 'doc-1', pages: [] })),
      parsed: { documentId: 'doc-1', pages: [] },
    });

    const { GET } = await import('../../src/app/api/documents/[id]/parsed/route');
    const response = await GET(new NextRequest('http://localhost/api/documents/doc-1/parsed'), {
      params: Promise.resolve({ id: 'doc-1' }),
    });

    expect(response.status).toBe(200);
    await expect(response.json()).resolves.toMatchObject({ documentId: 'doc-1' });
    expect(hoisted.readParsedPdfArtifactByKey).toHaveBeenCalledWith('parsed-key.json');
  });

  test('POST creates a worker op when replace is requested', async () => {
    hoisted.createOrReuseCurrentPdfParseOperation.mockResolvedValue({
      opId: 'op-force-1',
      opKey: 'pdf_layout|v1|parser|doc-1||doc-key|force',
      kind: 'pdf_layout',
      jobId: 'job-force-1',
      status: 'queued',
      queuedAt: Date.now(),
      updatedAt: Date.now(),
    });

    const { POST } = await import('../../src/app/api/documents/[id]/parsed/route');
    const request = new NextRequest('http://localhost/api/documents/doc-1/parsed', {
      method: 'POST',
      body: JSON.stringify({ replace: true }),
      headers: { 'Content-Type': 'application/json' },
    });
    const response = await POST(request, {
      params: Promise.resolve({ id: 'doc-1' }),
    });

    expect(response.status).toBe(202);
    await expect(response.json()).resolves.toMatchObject({
      parseStatus: 'pending',
      opId: 'op-force-1',
    });
    expect(hoisted.createOrReuseCurrentPdfParseOperation).toHaveBeenCalled();
  });

  test('POST does not report ready for a succeeded op until its artifact is readable', async () => {
    hoisted.lookupCurrentPdfParseOperation.mockResolvedValue({
      opId: 'op-ready-1',
      opKey: 'pdf_layout|v1|parser|doc-1||doc-key|',
      kind: 'pdf_layout',
      jobId: 'job-ready-1',
      status: 'succeeded',
      queuedAt: Date.now() - 1000,
      updatedAt: Date.now(),
      result: { parsedObjectKey: 'missing-parsed-key.json' },
    });
    hoisted.readParsedPdfArtifactByKey.mockResolvedValue(null);

    const { POST } = await import('../../src/app/api/documents/[id]/parsed/route');
    const request = new NextRequest('http://localhost/api/documents/doc-1/parsed', {
      method: 'POST',
      body: JSON.stringify({ replace: false }),
      headers: { 'Content-Type': 'application/json' },
    });
    const response = await POST(request, {
      params: Promise.resolve({ id: 'doc-1' }),
    });

    expect(response.status).toBe(202);
    await expect(response.json()).resolves.toMatchObject({
      parseStatus: 'running',
      opId: 'op-ready-1',
    });
    expect(hoisted.readParsedPdfArtifactByKey).toHaveBeenCalledWith('missing-parsed-key.json');
    expect(hoisted.createOrReuseCurrentPdfParseOperation).not.toHaveBeenCalled();
  });

  test('POST returns the rate-limited response without creating a worker op', async () => {
    const sentinel = new Response('rate limited', { status: 429 });
    hoisted.checkJobRate.mockResolvedValue({ allowed: false });
    hoisted.buildComputeRateLimitedResponse.mockReturnValue(sentinel);

    const { POST } = await import('../../src/app/api/documents/[id]/parsed/route');
    const request = new NextRequest('http://localhost/api/documents/doc-1/parsed', {
      method: 'POST',
      body: JSON.stringify({ replace: true }),
      headers: { 'Content-Type': 'application/json' },
    });
    const response = await POST(request, {
      params: Promise.resolve({ id: 'doc-1' }),
    });

    expect(response).toBe(sentinel);
    expect(hoisted.createOrReuseCurrentPdfParseOperation).not.toHaveBeenCalled();
  });
});
