/**
 * Tests for copilot chat update-messages API route
 *
 * @vitest-environment node
 */
import { authMockFns } from '@sim/testing'
import { NextRequest } from 'next/server'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

const {
  mockSelect,
  mockFrom,
  mockWhere,
  mockLimit,
  mockUpdate,
  mockSet,
  mockUpdateWhere,
  mockReturning,
  mockReplaceCopilotChatMessages,
} = vi.hoisted(() => ({
  mockSelect: vi.fn(),
  mockFrom: vi.fn(),
  mockWhere: vi.fn(),
  mockLimit: vi.fn(),
  mockUpdate: vi.fn(),
  mockSet: vi.fn(),
  mockUpdateWhere: vi.fn(),
  mockReturning: vi.fn(),
  mockReplaceCopilotChatMessages: vi.fn(),
}))

vi.mock('@sim/db', () => ({
  db: {
    select: mockSelect,
    update: mockUpdate,
    transaction: async (
      cb: (tx: { update: typeof mockUpdate; select: typeof mockSelect }) => unknown
    ) => cb({ update: mockUpdate, select: mockSelect }),
  },
}))

vi.mock('@/lib/copilot/chat/messages-store', () => ({
  replaceCopilotChatMessages: mockReplaceCopilotChatMessages,
}))

vi.mock('drizzle-orm', () => ({
  and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })),
  eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })),
}))

import { POST } from '@/app/api/copilot/chat/update-messages/route'

function createMockRequest(method: string, body: Record<string, unknown>): NextRequest {
  return new NextRequest('http://localhost:3000/api/copilot/chat/update-messages', {
    method,
    body: JSON.stringify(body),
    headers: { 'Content-Type': 'application/json' },
  })
}

describe('Copilot Chat Update Messages API Route', () => {
  beforeEach(() => {
    vi.clearAllMocks()

    authMockFns.mockGetSession.mockResolvedValue(null)

    mockSelect.mockReturnValue({ from: mockFrom })
    mockFrom.mockReturnValue({ where: mockWhere })
    mockWhere.mockReturnValue({ limit: mockLimit })
    mockLimit.mockResolvedValue([])
    mockUpdate.mockReturnValue({ set: mockSet })
    mockSet.mockReturnValue({ where: mockUpdateWhere })
    mockUpdateWhere.mockReturnValue({ returning: mockReturning })
    mockReturning.mockResolvedValue([{ model: 'gpt-4' }])
  })

  afterEach(() => {
    vi.restoreAllMocks()
  })

  describe('POST', () => {
    it('should return 401 when user is not authenticated', async () => {
      authMockFns.mockGetSession.mockResolvedValue(null)

      const req = createMockRequest('POST', {
        chatId: 'chat-123',
        messages: [
          {
            id: 'msg-1',
            role: 'user',
            content: 'Hello',
            timestamp: '2024-01-01T00:00:00.000Z',
          },
        ],
      })

      const response = await POST(req)

      expect(response.status).toBe(401)
      const responseData = await response.json()
      expect(responseData).toEqual({ error: 'Unauthorized' })
    })

    it('should return 400 for invalid request body - missing chatId', async () => {
      authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } })

      const req = createMockRequest('POST', {
        messages: [
          {
            id: 'msg-1',
            role: 'user',
            content: 'Hello',
            timestamp: '2024-01-01T00:00:00.000Z',
          },
        ],
      })

      const response = await POST(req)

      expect(response.status).toBe(400)
      const responseData = await response.json()
      expect(responseData.error).toBe('Validation error')
    })

    it('should return 400 for invalid request body - missing messages', async () => {
      authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } })

      const req = createMockRequest('POST', {
        chatId: 'chat-123',
      })

      const response = await POST(req)

      expect(response.status).toBe(400)
      const responseData = await response.json()
      expect(responseData.error).toBe('Validation error')
    })

    it('should return 400 for invalid message structure - missing required fields', async () => {
      authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } })

      const req = createMockRequest('POST', {
        chatId: 'chat-123',
        messages: [
          {
            id: 'msg-1',
          },
        ],
      })

      const response = await POST(req)

      expect(response.status).toBe(400)
      const responseData = await response.json()
      expect(responseData.error).toBe('Validation error')
    })

    it('should return 400 for invalid message role', async () => {
      authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } })

      const req = createMockRequest('POST', {
        chatId: 'chat-123',
        messages: [
          {
            id: 'msg-1',
            role: 'invalid-role',
            content: 'Hello',
            timestamp: '2024-01-01T00:00:00.000Z',
          },
        ],
      })

      const response = await POST(req)

      expect(response.status).toBe(400)
      const responseData = await response.json()
      expect(responseData.error).toBe('Validation error')
    })

    it('should return 404 when chat is not found', async () => {
      authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } })

      mockLimit.mockResolvedValueOnce([])

      const req = createMockRequest('POST', {
        chatId: 'non-existent-chat',
        messages: [
          {
            id: 'msg-1',
            role: 'user',
            content: 'Hello',
            timestamp: '2024-01-01T00:00:00.000Z',
          },
        ],
      })

      const response = await POST(req)

      expect(response.status).toBe(404)
      const responseData = await response.json()
      expect(responseData.error).toBe('Chat not found or unauthorized')
    })

    it('should return 404 when chat belongs to different user', async () => {
      authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } })

      mockLimit.mockResolvedValueOnce([])

      const req = createMockRequest('POST', {
        chatId: 'other-user-chat',
        messages: [
          {
            id: 'msg-1',
            role: 'user',
            content: 'Hello',
            timestamp: '2024-01-01T00:00:00.000Z',
          },
        ],
      })

      const response = await POST(req)

      expect(response.status).toBe(404)
      const responseData = await response.json()
      expect(responseData.error).toBe('Chat not found or unauthorized')
    })

    it('should successfully update chat messages', async () => {
      authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } })

      const existingChat = {
        id: 'chat-123',
        userId: 'user-123',
        messages: [],
      }
      mockLimit.mockResolvedValueOnce([existingChat])

      const messages = [
        {
          id: 'msg-1',
          role: 'user',
          content: 'Hello, how are you?',
          timestamp: '2024-01-01T10:00:00.000Z',
        },
        {
          id: 'msg-2',
          role: 'assistant',
          content: 'I am doing well, thank you!',
          timestamp: '2024-01-01T10:01:00.000Z',
        },
      ]

      const req = createMockRequest('POST', {
        chatId: 'chat-123',
        messages,
      })

      const response = await POST(req)

      expect(response.status).toBe(200)
      const responseData = await response.json()
      expect(responseData).toEqual({
        success: true,
        messageCount: 2,
      })

      expect(mockSelect).toHaveBeenCalled()
      expect(mockUpdate).toHaveBeenCalled()
      expect(mockSet).toHaveBeenCalledWith({ updatedAt: expect.any(Date) })
      expect(mockReplaceCopilotChatMessages).toHaveBeenCalledWith(
        'chat-123',
        messages,
        { chatModel: 'gpt-4' },
        expect.anything()
      )
    })

    it('should successfully update chat messages with optional fields', async () => {
      authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } })

      const existingChat = {
        id: 'chat-456',
        userId: 'user-123',
        messages: [],
      }
      mockLimit.mockResolvedValueOnce([existingChat])

      const messages = [
        {
          id: 'msg-1',
          role: 'user',
          content: 'Hello',
          timestamp: '2024-01-01T10:00:00.000Z',
        },
        {
          id: 'msg-2',
          role: 'assistant',
          content: 'Hi there!',
          timestamp: '2024-01-01T10:01:00.000Z',
          toolCalls: [
            {
              id: 'tool-1',
              name: 'get_weather',
              arguments: { location: 'NYC' },
            },
          ],
          contentBlocks: [
            {
              type: 'text',
              content: 'Here is the weather information',
            },
          ],
        },
      ]

      const req = createMockRequest('POST', {
        chatId: 'chat-456',
        messages,
      })

      const response = await POST(req)

      expect(response.status).toBe(200)
      const responseData = await response.json()
      expect(responseData).toEqual({
        success: true,
        messageCount: 2,
      })

      expect(mockSet).toHaveBeenCalledWith({ updatedAt: expect.any(Date) })
      expect(mockReplaceCopilotChatMessages).toHaveBeenCalledWith(
        'chat-456',
        [
          {
            id: 'msg-1',
            role: 'user',
            content: 'Hello',
            timestamp: '2024-01-01T10:00:00.000Z',
          },
          {
            id: 'msg-2',
            role: 'assistant',
            content: 'Hi there!',
            timestamp: '2024-01-01T10:01:00.000Z',
            contentBlocks: [
              {
                type: 'text',
                content: 'Here is the weather information',
              },
              {
                type: 'tool',
                phase: 'call',
                toolCall: {
                  id: 'tool-1',
                  name: 'get_weather',
                  state: 'pending',
                },
              },
            ],
          },
        ],
        { chatModel: 'gpt-4' },
        expect.anything()
      )
    })

    it('should handle empty messages array', async () => {
      authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } })

      const existingChat = {
        id: 'chat-789',
        userId: 'user-123',
        messages: [],
      }
      mockLimit.mockResolvedValueOnce([existingChat])

      const req = createMockRequest('POST', {
        chatId: 'chat-789',
        messages: [],
      })

      const response = await POST(req)

      expect(response.status).toBe(200)
      const responseData = await response.json()
      expect(responseData).toEqual({
        success: true,
        messageCount: 0,
      })

      expect(mockSet).toHaveBeenCalledWith({ updatedAt: expect.any(Date) })
      expect(mockReplaceCopilotChatMessages).toHaveBeenCalledWith(
        'chat-789',
        [],
        { chatModel: 'gpt-4' },
        expect.anything()
      )
    })

    it('should handle database errors during chat lookup', async () => {
      authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } })

      mockLimit.mockRejectedValueOnce(new Error('Database connection failed'))

      const req = createMockRequest('POST', {
        chatId: 'chat-123',
        messages: [
          {
            id: 'msg-1',
            role: 'user',
            content: 'Hello',
            timestamp: '2024-01-01T00:00:00.000Z',
          },
        ],
      })

      const response = await POST(req)

      expect(response.status).toBe(500)
      const responseData = await response.json()
      expect(responseData.error).toBe('Failed to update chat messages')
    })

    it('should handle database errors during update operation', async () => {
      authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } })

      const existingChat = {
        id: 'chat-123',
        userId: 'user-123',
        messages: [],
      }
      mockLimit.mockResolvedValueOnce([existingChat])

      mockSet.mockReturnValueOnce({
        where: vi.fn().mockRejectedValue(new Error('Update operation failed')),
      })

      const req = createMockRequest('POST', {
        chatId: 'chat-123',
        messages: [
          {
            id: 'msg-1',
            role: 'user',
            content: 'Hello',
            timestamp: '2024-01-01T00:00:00.000Z',
          },
        ],
      })

      const response = await POST(req)

      expect(response.status).toBe(500)
      const responseData = await response.json()
      expect(responseData.error).toBe('Failed to update chat messages')
    })

    it('should handle JSON parsing errors in request body', async () => {
      authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } })

      const req = new NextRequest('http://localhost:3000/api/copilot/chat/update-messages', {
        method: 'POST',
        body: '{invalid-json',
        headers: {
          'Content-Type': 'application/json',
        },
      })

      const response = await POST(req)

      expect(response.status).toBe(500)
      const responseData = await response.json()
      expect(responseData.error).toBe('Failed to update chat messages')
    })

    it('should handle large message arrays', async () => {
      authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } })

      const existingChat = {
        id: 'chat-large',
        userId: 'user-123',
        messages: [],
      }
      mockLimit.mockResolvedValueOnce([existingChat])

      const messages = Array.from({ length: 100 }, (_, i) => ({
        id: `msg-${i + 1}`,
        role: i % 2 === 0 ? 'user' : 'assistant',
        content: `Message ${i + 1}`,
        timestamp: new Date(2024, 0, 1, 10, i).toISOString(),
      }))

      const req = createMockRequest('POST', {
        chatId: 'chat-large',
        messages,
      })

      const response = await POST(req)

      expect(response.status).toBe(200)
      const responseData = await response.json()
      expect(responseData).toEqual({
        success: true,
        messageCount: 100,
      })

      expect(mockSet).toHaveBeenCalledWith({ updatedAt: expect.any(Date) })
      expect(mockReplaceCopilotChatMessages).toHaveBeenCalledWith(
        'chat-large',
        messages,
        { chatModel: 'gpt-4' },
        expect.anything()
      )
    })

    it('should handle messages with both user and assistant roles', async () => {
      authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } })

      const existingChat = {
        id: 'chat-mixed',
        userId: 'user-123',
        messages: [],
      }
      mockLimit.mockResolvedValueOnce([existingChat])

      const messages = [
        {
          id: 'msg-1',
          role: 'user',
          content: 'What is the weather like?',
          timestamp: '2024-01-01T10:00:00.000Z',
        },
        {
          id: 'msg-2',
          role: 'assistant',
          content: 'Let me check the weather for you.',
          timestamp: '2024-01-01T10:01:00.000Z',
          toolCalls: [
            {
              id: 'tool-weather',
              name: 'get_weather',
              arguments: { location: 'current' },
            },
          ],
        },
        {
          id: 'msg-3',
          role: 'assistant',
          content: 'The weather is sunny and 75°F.',
          timestamp: '2024-01-01T10:02:00.000Z',
        },
        {
          id: 'msg-4',
          role: 'user',
          content: 'Thank you!',
          timestamp: '2024-01-01T10:03:00.000Z',
        },
      ]

      const req = createMockRequest('POST', {
        chatId: 'chat-mixed',
        messages,
      })

      const response = await POST(req)

      expect(response.status).toBe(200)
      const responseData = await response.json()
      expect(responseData).toEqual({
        success: true,
        messageCount: 4,
      })
    })
  })
})
