import { beforeEach, describe, expect, it, vi } from 'vitest'

const {
  listKnowledgeBasesMock,
  getKnowledgeBaseByIdMock,
  updateKnowledgeBaseMock,
  listKnowledgeItemsMock,
  getKnowledgeItemByIdMock
} = vi.hoisted(() => ({
  listKnowledgeBasesMock: vi.fn(),
  getKnowledgeBaseByIdMock: vi.fn(),
  updateKnowledgeBaseMock: vi.fn(),
  listKnowledgeItemsMock: vi.fn(),
  getKnowledgeItemByIdMock: vi.fn()
}))

vi.mock('@data/services/KnowledgeBaseService', () => ({
  knowledgeBaseService: {
    list: listKnowledgeBasesMock,
    getById: getKnowledgeBaseByIdMock,
    update: updateKnowledgeBaseMock
  }
}))

vi.mock('@data/services/KnowledgeItemService', () => ({
  knowledgeItemService: {
    list: listKnowledgeItemsMock,
    getById: getKnowledgeItemByIdMock
  }
}))

import {
  KNOWLEDGE_BASES_DEFAULT_LIMIT,
  KNOWLEDGE_BASES_DEFAULT_PAGE,
  KNOWLEDGE_BASES_MAX_LIMIT,
  KNOWLEDGE_ITEMS_DEFAULT_LIMIT,
  KNOWLEDGE_ITEMS_MAX_LIMIT
} from '@shared/data/api/schemas/knowledges'

import { knowledgeHandlers } from '../knowledges'

const GROUP_ID = '11111111-1111-4111-8111-111111111111'
const ITEM_ID = '0198f3f2-7d1a-7abc-8def-123456789abc'

describe('knowledgeHandlers', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  describe('/knowledge-bases', () => {
    it('should apply default pagination when query is missing', async () => {
      const response = {
        items: [{ id: 'kb-1', name: 'Knowledge Base' }],
        total: 1,
        page: KNOWLEDGE_BASES_DEFAULT_PAGE
      }
      listKnowledgeBasesMock.mockResolvedValueOnce(response)

      const result = await knowledgeHandlers['/knowledge-bases'].GET({})

      expect(listKnowledgeBasesMock).toHaveBeenCalledWith({
        page: KNOWLEDGE_BASES_DEFAULT_PAGE,
        limit: KNOWLEDGE_BASES_DEFAULT_LIMIT
      })
      expect(result).toEqual(response)
    })

    it('should delegate explicit pagination to knowledgeBaseService.list', async () => {
      const response = {
        items: [{ id: 'kb-2', name: 'Knowledge Base 2' }],
        total: 3,
        page: 2
      }
      listKnowledgeBasesMock.mockResolvedValueOnce(response)

      const result = await knowledgeHandlers['/knowledge-bases'].GET({
        query: {
          page: 2,
          limit: 10
        } as never
      } as never)

      expect(listKnowledgeBasesMock).toHaveBeenCalledWith({
        page: 2,
        limit: 10
      })
      expect(result).toEqual(response)
    })

    it('should delegate trimmed search to knowledgeBaseService.list', async () => {
      const response = {
        items: [{ id: 'kb-3', name: 'Research Notes' }],
        total: 1,
        page: 1
      }
      listKnowledgeBasesMock.mockResolvedValueOnce(response)

      const result = await knowledgeHandlers['/knowledge-bases'].GET({
        query: {
          search: '  research  '
        }
      } as never)

      expect(listKnowledgeBasesMock).toHaveBeenCalledWith({
        page: KNOWLEDGE_BASES_DEFAULT_PAGE,
        limit: KNOWLEDGE_BASES_DEFAULT_LIMIT,
        search: 'research'
      })
      expect(result).toEqual(response)
    })

    it('should reject invalid pagination before calling the service', async () => {
      await expect(
        knowledgeHandlers['/knowledge-bases'].GET({
          query: {
            limit: KNOWLEDGE_BASES_MAX_LIMIT + 1
          } as never
        } as never)
      ).rejects.toHaveProperty('name', 'ZodError')

      expect(listKnowledgeBasesMock).not.toHaveBeenCalled()
    })
  })

  describe('/knowledge-bases/:id', () => {
    it('should delegate GET/PATCH with the path id', async () => {
      getKnowledgeBaseByIdMock.mockResolvedValueOnce({ id: 'kb-1' })
      updateKnowledgeBaseMock.mockResolvedValueOnce({ id: 'kb-1', name: 'Updated Base' })

      await expect(knowledgeHandlers['/knowledge-bases/:id'].GET({ params: { id: 'kb-1' } })).resolves.toEqual({
        id: 'kb-1'
      })

      await expect(
        knowledgeHandlers['/knowledge-bases/:id'].PATCH({
          params: { id: 'kb-1' },
          body: { name: '  Updated Base  ' }
        })
      ).resolves.toEqual({
        id: 'kb-1',
        name: 'Updated Base'
      })

      expect(getKnowledgeBaseByIdMock).toHaveBeenCalledWith('kb-1')
      expect(updateKnowledgeBaseMock).toHaveBeenCalledWith('kb-1', { name: 'Updated Base' })
    })

    it('should reject invalid PATCH bodies before calling the service', async () => {
      await expect(
        knowledgeHandlers['/knowledge-bases/:id'].PATCH({
          params: { id: 'kb-1' },
          body: {
            dimensions: 3072
          }
        } as never)
      ).rejects.toHaveProperty('name', 'ZodError')

      expect(updateKnowledgeBaseMock).not.toHaveBeenCalled()
    })

    it('should reject blank names in PATCH bodies before calling the service', async () => {
      await expect(
        knowledgeHandlers['/knowledge-bases/:id'].PATCH({
          params: { id: 'kb-1' },
          body: {
            name: '   '
          }
        } as never)
      ).rejects.toHaveProperty('name', 'ZodError')

      expect(updateKnowledgeBaseMock).not.toHaveBeenCalled()
    })

    it('should reject embeddingModelId updates before calling the service', async () => {
      await expect(
        knowledgeHandlers['/knowledge-bases/:id'].PATCH({
          params: { id: 'kb-1' },
          body: {
            embeddingModelId: '  new-model  '
          }
        } as never)
      ).rejects.toHaveProperty('name', 'ZodError')

      expect(updateKnowledgeBaseMock).not.toHaveBeenCalled()
    })

    it('should trim groupId in PATCH bodies before calling the service', async () => {
      updateKnowledgeBaseMock.mockResolvedValueOnce({ id: 'kb-1', groupId: GROUP_ID })

      await expect(
        knowledgeHandlers['/knowledge-bases/:id'].PATCH({
          params: { id: 'kb-1' },
          body: {
            groupId: `  ${GROUP_ID}  `
          }
        })
      ).resolves.toMatchObject({ id: 'kb-1' })

      expect(updateKnowledgeBaseMock).toHaveBeenCalledWith('kb-1', {
        groupId: GROUP_ID
      })
    })

    it('should reject null embeddingModelId clears before calling the service', async () => {
      await expect(
        knowledgeHandlers['/knowledge-bases/:id'].PATCH({
          params: { id: 'kb-1' },
          body: {
            embeddingModelId: null
          }
        } as never)
      ).rejects.toHaveProperty('name', 'ZodError')

      expect(updateKnowledgeBaseMock).not.toHaveBeenCalled()
    })

    it('should pass nullable model and processor clears before calling the service', async () => {
      updateKnowledgeBaseMock.mockResolvedValueOnce({
        id: 'kb-1',
        rerankModelId: null,
        fileProcessorId: null
      })

      await expect(
        knowledgeHandlers['/knowledge-bases/:id'].PATCH({
          params: { id: 'kb-1' },
          body: {
            rerankModelId: null,
            fileProcessorId: null
          }
        })
      ).resolves.toMatchObject({ id: 'kb-1', rerankModelId: null, fileProcessorId: null })

      expect(updateKnowledgeBaseMock).toHaveBeenCalledWith('kb-1', {
        rerankModelId: null,
        fileProcessorId: null
      })
    })

    it('should reject non-nullable optional config null clears before calling the service', async () => {
      await expect(
        knowledgeHandlers['/knowledge-bases/:id'].PATCH({
          params: { id: 'kb-1' },
          body: {
            threshold: null,
            documentCount: null,
            hybridAlpha: null
          }
        } as never)
      ).rejects.toHaveProperty('name', 'ZodError')

      expect(updateKnowledgeBaseMock).not.toHaveBeenCalled()
    })

    it('should pass null groupId clears before calling the service', async () => {
      updateKnowledgeBaseMock.mockResolvedValueOnce({ id: 'kb-1', groupId: null })

      await expect(
        knowledgeHandlers['/knowledge-bases/:id'].PATCH({
          params: { id: 'kb-1' },
          body: {
            groupId: null
          }
        })
      ).resolves.toMatchObject({ id: 'kb-1', groupId: null })

      expect(updateKnowledgeBaseMock).toHaveBeenCalledWith('kb-1', {
        groupId: null
      })
    })
  })

  describe('/knowledge-bases/:id/items', () => {
    it('should apply the default limit when query is missing', async () => {
      listKnowledgeItemsMock.mockResolvedValueOnce({
        items: [],
        total: 0,
        nextCursor: undefined
      })

      await knowledgeHandlers['/knowledge-bases/:id/items'].GET({
        params: { id: 'kb-1' }
      })

      expect(listKnowledgeItemsMock).toHaveBeenCalledWith('kb-1', {
        limit: KNOWLEDGE_ITEMS_DEFAULT_LIMIT
      })
    })

    it('should pass cursor and type/group filters to knowledge item listing', async () => {
      listKnowledgeItemsMock.mockResolvedValueOnce({
        items: [],
        total: 0,
        nextCursor: undefined
      })

      await knowledgeHandlers['/knowledge-bases/:id/items'].GET({
        params: { id: 'kb-1' },
        query: {
          cursor: '1700000000000:item-9',
          limit: 10,
          type: 'directory',
          groupId: ITEM_ID
        } as never
      } as never)

      expect(listKnowledgeItemsMock).toHaveBeenCalledWith('kb-1', {
        cursor: '1700000000000:item-9',
        limit: 10,
        type: 'directory',
        groupId: ITEM_ID
      })
    })

    it('should pass null groupId root filters to knowledge item listing', async () => {
      listKnowledgeItemsMock.mockResolvedValueOnce({
        items: [],
        total: 0,
        nextCursor: undefined
      })

      await knowledgeHandlers['/knowledge-bases/:id/items'].GET({
        params: { id: 'kb-1' },
        query: {
          groupId: null
        }
      } as never)

      expect(listKnowledgeItemsMock).toHaveBeenCalledWith('kb-1', {
        limit: KNOWLEDGE_ITEMS_DEFAULT_LIMIT,
        groupId: null
      })
    })

    it('should reject non-positive limit values', async () => {
      await expect(
        knowledgeHandlers['/knowledge-bases/:id/items'].GET({
          params: { id: 'kb-1' },
          query: {
            limit: 0
          } as never
        } as never)
      ).rejects.toHaveProperty('name', 'ZodError')

      expect(listKnowledgeItemsMock).not.toHaveBeenCalled()
    })

    it('should reject limit values above the max limit', async () => {
      await expect(
        knowledgeHandlers['/knowledge-bases/:id/items'].GET({
          params: { id: 'kb-1' },
          query: {
            limit: KNOWLEDGE_ITEMS_MAX_LIMIT + 1
          } as never
        } as never)
      ).rejects.toHaveProperty('name', 'ZodError')

      expect(listKnowledgeItemsMock).not.toHaveBeenCalled()
    })

    it('should reject invalid type filters', async () => {
      await expect(
        knowledgeHandlers['/knowledge-bases/:id/items'].GET({
          params: { id: 'kb-1' },
          query: {
            type: 'memory'
          } as never
        } as never)
      ).rejects.toHaveProperty('name', 'ZodError')

      expect(listKnowledgeItemsMock).not.toHaveBeenCalled()
    })
  })

  describe('/knowledge-items/:id', () => {
    it('should delegate GET with the item id', async () => {
      getKnowledgeItemByIdMock.mockResolvedValueOnce({ id: 'item-1' })

      await expect(knowledgeHandlers['/knowledge-items/:id'].GET({ params: { id: 'item-1' } })).resolves.toEqual({
        id: 'item-1'
      })

      expect(getKnowledgeItemByIdMock).toHaveBeenCalledWith('item-1')
    })
  })
})
