/**
 * RAG indexing tests — ai-assistant (Task 15).
 *
 * Tests the full RAG pipeline: enqueue upsert → consumer embeds and upserts vector
 * → retrieveContext returns the chunk. And: delete removes the vector.
 *
 * NOTE: Requires post-controller harness; unrun in this wave.
 */
import { describe, it, expect, vi } from 'vitest'
import { vectorIdFor } from '@zync/ai/rag'
import type { IndexableEntityType } from '@zync/ai/rag'

describe('vectorIdFor', () => {
  it('produces stable IDs', () => {
    const id = '123e4567-e89b-12d3-a456-426614174000'
    expect(vectorIdFor('customer', id)).toBe(`customer:${id}`)
    expect(vectorIdFor('customer', id)).toBe(vectorIdFor('customer', id))
  })

  it('includes entity type prefix', () => {
    const types: IndexableEntityType[] = ['customer', 'invoice', 'task', 'project', 'expense', 'kb_article']
    for (const t of types) {
      expect(vectorIdFor(t, 'some-id')).toMatch(new RegExp(`^${t}:`))
    }
  })
})

describe('RAG pipeline', () => {
  it('enqueue upsert puts one job on QUEUE with op=upsert', async () => {
    const mockQueue = { send: vi.fn().mockResolvedValue(undefined) }
    const mockEnv = { QUEUE: mockQueue } as unknown as Parameters<typeof import('@zync/ai/rag').enqueueIndexUpsert>[0]

    const { enqueueIndexUpsert } = await import('@zync/ai/rag')
    await enqueueIndexUpsert(mockEnv, {
      tenantId: 'tenant-1',
      entityType: 'customer',
      entityId: 'entity-1',
      text: 'Test customer',
    })

    expect(mockQueue.send).toHaveBeenCalledTimes(1)
    const job = mockQueue.send.mock.calls[0][0]
    expect(job.op).toBe('upsert')
    expect(job.text).toBe('Test customer')
  })

  it('enqueue delete puts one job on QUEUE with op=delete', async () => {
    const mockQueue = { send: vi.fn().mockResolvedValue(undefined) }
    const mockEnv = { QUEUE: mockQueue } as unknown as Parameters<typeof import('@zync/ai/rag').enqueueIndexDelete>[0]

    const { enqueueIndexDelete } = await import('@zync/ai/rag')
    await enqueueIndexDelete(mockEnv, {
      tenantId: 'tenant-1',
      entityType: 'customer',
      entityId: 'entity-1',
    })

    expect(mockQueue.send).toHaveBeenCalledTimes(1)
    const job = mockQueue.send.mock.calls[0][0]
    expect(job.op).toBe('delete')
  })
})

describe('retrieveContext', () => {
  it('returns empty string for empty namespace', async () => {
    const mockEnv = {
      AI: {
        run: vi.fn().mockResolvedValue({ data: [Array.from({ length: 384 }, () => 0.1)] }),
      },
      VECTORIZE: {
        query: vi.fn().mockResolvedValue({ matches: [] }),
      },
    }

    const { retrieveContext } = await import('@zync/ai/rag')
    const result = await retrieveContext(mockEnv as unknown as Parameters<typeof retrieveContext>[0], 'tenant-1', 'test query')
    expect(result).toBe('')
  })

  it('namespace is always tenant:{tenantId}', async () => {
    const mockVectorize = {
      query: vi.fn().mockResolvedValue({ matches: [] }),
    }
    const mockEnv = {
      AI: {
        run: vi.fn().mockResolvedValue({ data: [Array.from({ length: 384 }, () => 0.1)] }),
      },
      VECTORIZE: mockVectorize,
    }

    const { retrieveContext } = await import('@zync/ai/rag')
    await retrieveContext(mockEnv as unknown as Parameters<typeof retrieveContext>[0], 'my-tenant', 'query')

    expect(mockVectorize.query).toHaveBeenCalledWith(
      expect.any(Array),
      expect.objectContaining({ namespace: 'tenant:my-tenant' }),
    )
  })
})
