/**
 * Tests for chat identifier API route
 *
 * @vitest-environment node
 */
import { loggerMock } from '@sim/testing'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

/**
 * Creates a mock NextRequest with cookies support for testing.
 */
function createMockNextRequest(
  method = 'GET',
  body?: unknown,
  headers: Record<string, string> = {},
  url = 'http://localhost:3000/api/test'
): any {
  const headersObj = new Headers({
    'Content-Type': 'application/json',
    ...headers,
  })

  return {
    method,
    headers: headersObj,
    cookies: {
      get: vi.fn().mockReturnValue(undefined),
    },
    json:
      body !== undefined
        ? vi.fn().mockResolvedValue(body)
        : vi.fn().mockRejectedValue(new Error('No body')),
    url,
  }
}

const createMockStream = () => {
  return new ReadableStream({
    start(controller) {
      controller.enqueue(
        new TextEncoder().encode('data: {"blockId":"agent-1","chunk":"Hello"}\n\n')
      )
      controller.enqueue(
        new TextEncoder().encode('data: {"blockId":"agent-1","chunk":" world"}\n\n')
      )
      controller.enqueue(
        new TextEncoder().encode('data: {"event":"final","data":{"success":true}}\n\n')
      )
      controller.close()
    },
  })
}

vi.mock('@/lib/execution/preprocessing', () => ({
  preprocessExecution: vi.fn().mockResolvedValue({
    success: true,
    actorUserId: 'test-user-id',
    workflowRecord: {
      id: 'test-workflow-id',
      userId: 'test-user-id',
      isDeployed: true,
      workspaceId: 'test-workspace-id',
      variables: {},
    },
    userSubscription: {
      plan: 'pro',
      status: 'active',
    },
    rateLimitInfo: {
      allowed: true,
      remaining: 100,
      resetAt: new Date(),
    },
  }),
}))

vi.mock('@/lib/logs/execution/logging-session', () => ({
  LoggingSession: vi.fn().mockImplementation(() => ({
    safeStart: vi.fn().mockResolvedValue(undefined),
    safeCompleteWithError: vi.fn().mockResolvedValue(undefined),
  })),
}))

vi.mock('@/lib/workflows/streaming/streaming', () => ({
  createStreamingResponse: vi.fn().mockImplementation(async () => createMockStream()),
}))

vi.mock('@/lib/core/utils/sse', () => ({
  SSE_HEADERS: {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    Connection: 'keep-alive',
    'X-Accel-Buffering': 'no',
  },
}))

vi.mock('@/lib/core/utils/request', () => ({
  generateRequestId: vi.fn().mockReturnValue('test-request-id'),
}))

vi.mock('@/lib/core/security/encryption', () => ({
  decryptSecret: vi.fn().mockResolvedValue({ decrypted: 'test-password' }),
}))

describe('Chat Identifier API Route', () => {
  const mockAddCorsHeaders = vi.fn().mockImplementation((response) => response)
  const mockValidateChatAuth = vi.fn().mockResolvedValue({ authorized: true })
  const mockSetChatAuthCookie = vi.fn()
  const mockValidateAuthToken = vi.fn().mockReturnValue(false)

  const mockChatResult = [
    {
      id: 'chat-id',
      workflowId: 'workflow-id',
      userId: 'user-id',
      isActive: true,
      authType: 'public',
      title: 'Test Chat',
      description: 'Test chat description',
      customizations: {
        welcomeMessage: 'Welcome to the test chat',
        primaryColor: '#000000',
      },
      outputConfigs: [{ blockId: 'block-1', path: 'output' }],
    },
  ]

  const mockWorkflowResult = [
    {
      isDeployed: true,
      state: {
        blocks: {},
        edges: [],
        loops: {},
        parallels: {},
      },
      deployedState: {
        blocks: {},
        edges: [],
        loops: {},
        parallels: {},
      },
    },
  ]

  beforeEach(() => {
    vi.resetModules()

    vi.doMock('@/lib/core/security/deployment', () => ({
      addCorsHeaders: mockAddCorsHeaders,
      validateAuthToken: mockValidateAuthToken,
      setDeploymentAuthCookie: vi.fn(),
      isEmailAllowed: vi.fn().mockReturnValue(false),
    }))

    vi.doMock('@/app/api/chat/utils', () => ({
      validateChatAuth: mockValidateChatAuth,
      setChatAuthCookie: mockSetChatAuthCookie,
    }))

    // Mock logger - use loggerMock from @sim/testing
    vi.doMock('@sim/logger', () => loggerMock)

    vi.doMock('@sim/db', () => {
      const mockSelect = vi.fn().mockImplementation((fields) => {
        if (fields && fields.isDeployed !== undefined) {
          return {
            from: vi.fn().mockReturnValue({
              where: vi.fn().mockReturnValue({
                limit: vi.fn().mockReturnValue(mockWorkflowResult),
              }),
            }),
          }
        }
        return {
          from: vi.fn().mockReturnValue({
            where: vi.fn().mockReturnValue({
              limit: vi.fn().mockReturnValue(mockChatResult),
            }),
          }),
        }
      })

      return {
        db: {
          select: mockSelect,
        },
        chat: {},
        workflow: {},
      }
    })

    vi.doMock('@/app/api/workflows/utils', () => ({
      createErrorResponse: vi.fn().mockImplementation((message, status, code) => {
        return new Response(
          JSON.stringify({
            error: code || 'Error',
            message,
          }),
          { status }
        )
      }),
      createSuccessResponse: vi.fn().mockImplementation((data) => {
        return new Response(JSON.stringify(data), { status: 200 })
      }),
    }))
  })

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

  describe('GET endpoint', () => {
    it('should return chat info for a valid identifier', async () => {
      const req = createMockNextRequest('GET')
      const params = Promise.resolve({ identifier: 'test-chat' })

      const { GET } = await import('@/app/api/chat/[identifier]/route')

      const response = await GET(req, { params })

      expect(response.status).toBe(200)

      const data = await response.json()
      expect(data).toHaveProperty('id', 'chat-id')
      expect(data).toHaveProperty('title', 'Test Chat')
      expect(data).toHaveProperty('description', 'Test chat description')
      expect(data).toHaveProperty('customizations')
      expect(data.customizations).toHaveProperty('welcomeMessage', 'Welcome to the test chat')
    })

    it('should return 404 for non-existent identifier', async () => {
      vi.doMock('@sim/db', () => {
        const mockLimit = vi.fn().mockReturnValue([])
        const mockWhere = vi.fn().mockReturnValue({ limit: mockLimit })
        const mockFrom = vi.fn().mockReturnValue({ where: mockWhere })
        const mockSelect = vi.fn().mockReturnValue({ from: mockFrom })

        return {
          db: {
            select: mockSelect,
          },
        }
      })

      const req = createMockNextRequest('GET')
      const params = Promise.resolve({ identifier: 'nonexistent' })

      const { GET } = await import('@/app/api/chat/[identifier]/route')

      const response = await GET(req, { params })

      expect(response.status).toBe(404)

      const data = await response.json()
      expect(data).toHaveProperty('error')
      expect(data).toHaveProperty('message', 'Chat not found')
    })

    it('should return 403 for inactive chat', async () => {
      vi.doMock('@sim/db', () => {
        const mockLimit = vi.fn().mockReturnValue([
          {
            id: 'chat-id',
            isActive: false,
            authType: 'public',
          },
        ])
        const mockWhere = vi.fn().mockReturnValue({ limit: mockLimit })
        const mockFrom = vi.fn().mockReturnValue({ where: mockWhere })
        const mockSelect = vi.fn().mockReturnValue({ from: mockFrom })

        return {
          db: {
            select: mockSelect,
          },
        }
      })

      const req = createMockNextRequest('GET')
      const params = Promise.resolve({ identifier: 'inactive-chat' })

      const { GET } = await import('@/app/api/chat/[identifier]/route')

      const response = await GET(req, { params })

      expect(response.status).toBe(403)

      const data = await response.json()
      expect(data).toHaveProperty('error')
      expect(data).toHaveProperty('message', 'This chat is currently unavailable')
    })

    it('should return 401 when authentication is required', async () => {
      const originalValidateChatAuth = mockValidateChatAuth.getMockImplementation()
      mockValidateChatAuth.mockImplementationOnce(async () => ({
        authorized: false,
        error: 'auth_required_password',
      }))

      const req = createMockNextRequest('GET')
      const params = Promise.resolve({ identifier: 'password-protected-chat' })

      const { GET } = await import('@/app/api/chat/[identifier]/route')

      const response = await GET(req, { params })

      expect(response.status).toBe(401)

      const data = await response.json()
      expect(data).toHaveProperty('error')
      expect(data).toHaveProperty('message', 'auth_required_password')

      if (originalValidateChatAuth) {
        mockValidateChatAuth.mockImplementation(originalValidateChatAuth)
      }
    })
  })

  describe('POST endpoint', () => {
    it('should handle authentication requests without input', async () => {
      const req = createMockNextRequest('POST', { password: 'test-password' })
      const params = Promise.resolve({ identifier: 'password-protected-chat' })

      const { POST } = await import('@/app/api/chat/[identifier]/route')

      const response = await POST(req, { params })

      expect(response.status).toBe(200)

      const data = await response.json()
      expect(data).toHaveProperty('authenticated', true)

      expect(mockSetChatAuthCookie).toHaveBeenCalled()
    })

    it('should return 400 for requests without input', async () => {
      const req = createMockNextRequest('POST', {})
      const params = Promise.resolve({ identifier: 'test-chat' })

      const { POST } = await import('@/app/api/chat/[identifier]/route')

      const response = await POST(req, { params })

      expect(response.status).toBe(400)

      const data = await response.json()
      expect(data).toHaveProperty('error')
      expect(data).toHaveProperty('message', 'No input provided')
    })

    it('should return 401 for unauthorized access', async () => {
      const originalValidateChatAuth = mockValidateChatAuth.getMockImplementation()
      mockValidateChatAuth.mockImplementationOnce(async () => ({
        authorized: false,
        error: 'Authentication required',
      }))

      const req = createMockNextRequest('POST', { input: 'Hello' })
      const params = Promise.resolve({ identifier: 'protected-chat' })

      const { POST } = await import('@/app/api/chat/[identifier]/route')

      const response = await POST(req, { params })

      expect(response.status).toBe(401)

      const data = await response.json()
      expect(data).toHaveProperty('error')
      expect(data).toHaveProperty('message', 'Authentication required')

      if (originalValidateChatAuth) {
        mockValidateChatAuth.mockImplementation(originalValidateChatAuth)
      }
    })

    it('should return 503 when workflow is not available', async () => {
      const { preprocessExecution } = await import('@/lib/execution/preprocessing')
      const originalImplementation = vi.mocked(preprocessExecution).getMockImplementation()

      vi.mocked(preprocessExecution).mockResolvedValueOnce({
        success: false,
        error: {
          message: 'Workflow is not deployed',
          statusCode: 403,
          logCreated: false,
        },
      })

      const req = createMockNextRequest('POST', { input: 'Hello' })
      const params = Promise.resolve({ identifier: 'test-chat' })

      const { POST } = await import('@/app/api/chat/[identifier]/route')

      const response = await POST(req, { params })

      expect(response.status).toBe(403)

      const data = await response.json()
      expect(data).toHaveProperty('error')
      expect(data).toHaveProperty('message', 'Workflow is not deployed')

      if (originalImplementation) {
        vi.mocked(preprocessExecution).mockImplementation(originalImplementation)
      }
    })

    it('should return streaming response for valid chat messages', async () => {
      const req = createMockNextRequest('POST', {
        input: 'Hello world',
        conversationId: 'conv-123',
      })
      const params = Promise.resolve({ identifier: 'test-chat' })

      const { POST } = await import('@/app/api/chat/[identifier]/route')
      const { createStreamingResponse } = await import('@/lib/workflows/streaming/streaming')

      const response = await POST(req, { params })

      expect(response.status).toBe(200)
      expect(response.headers.get('Content-Type')).toBe('text/event-stream')
      expect(response.headers.get('Cache-Control')).toBe('no-cache')
      expect(response.headers.get('Connection')).toBe('keep-alive')

      expect(createStreamingResponse).toHaveBeenCalledWith(
        expect.objectContaining({
          workflow: expect.objectContaining({
            id: 'workflow-id',
            userId: 'user-id',
          }),
          input: expect.objectContaining({
            input: 'Hello world',
            conversationId: 'conv-123',
          }),
          streamConfig: expect.objectContaining({
            isSecureMode: true,
            workflowTriggerType: 'chat',
          }),
        })
      )
    }, 10000)

    it('should handle streaming response body correctly', async () => {
      const req = createMockNextRequest('POST', { input: 'Hello world' })
      const params = Promise.resolve({ identifier: 'test-chat' })

      const { POST } = await import('@/app/api/chat/[identifier]/route')

      const response = await POST(req, { params })

      expect(response.status).toBe(200)
      expect(response.body).toBeInstanceOf(ReadableStream)

      if (response.body) {
        const reader = response.body.getReader()
        const { value, done } = await reader.read()

        if (!done && value) {
          const chunk = new TextDecoder().decode(value)
          expect(chunk).toMatch(/^data: /)
        }

        reader.releaseLock()
      }
    })

    it('should handle workflow execution errors gracefully', async () => {
      const { createStreamingResponse } = await import('@/lib/workflows/streaming/streaming')
      const originalStreamingResponse = vi.mocked(createStreamingResponse).getMockImplementation()
      vi.mocked(createStreamingResponse).mockImplementationOnce(async () => {
        throw new Error('Execution failed')
      })

      const req = createMockNextRequest('POST', { input: 'Trigger error' })
      const params = Promise.resolve({ identifier: 'test-chat' })

      const { POST } = await import('@/app/api/chat/[identifier]/route')

      const response = await POST(req, { params })

      expect(response.status).toBe(500)

      const data = await response.json()
      expect(data).toHaveProperty('error')
      expect(data).toHaveProperty('message', 'Execution failed')

      if (originalStreamingResponse) {
        vi.mocked(createStreamingResponse).mockImplementation(originalStreamingResponse)
      }
    })

    it('should handle invalid JSON in request body', async () => {
      const req = {
        method: 'POST',
        headers: new Headers(),
        json: vi.fn().mockRejectedValue(new Error('Invalid JSON')),
      } as any

      const params = Promise.resolve({ identifier: 'test-chat' })

      const { POST } = await import('@/app/api/chat/[identifier]/route')

      const response = await POST(req, { params })

      expect(response.status).toBe(400)

      const data = await response.json()
      expect(data).toHaveProperty('error')
      expect(data).toHaveProperty('message', 'Invalid request body')
    })

    it('should pass conversationId to streaming execution when provided', async () => {
      const req = createMockNextRequest('POST', {
        input: 'Hello world',
        conversationId: 'test-conversation-123',
      })
      const params = Promise.resolve({ identifier: 'test-chat' })

      const { POST } = await import('@/app/api/chat/[identifier]/route')
      const { createStreamingResponse } = await import('@/lib/workflows/streaming/streaming')

      await POST(req, { params })

      expect(createStreamingResponse).toHaveBeenCalledWith(
        expect.objectContaining({
          input: expect.objectContaining({
            input: 'Hello world',
            conversationId: 'test-conversation-123',
          }),
        })
      )
    })

    it('should handle missing conversationId gracefully', async () => {
      const req = createMockNextRequest('POST', { input: 'Hello world' })
      const params = Promise.resolve({ identifier: 'test-chat' })

      const { POST } = await import('@/app/api/chat/[identifier]/route')
      const { createStreamingResponse } = await import('@/lib/workflows/streaming/streaming')

      await POST(req, { params })

      expect(createStreamingResponse).toHaveBeenCalledWith(
        expect.objectContaining({
          input: expect.objectContaining({
            input: 'Hello world',
          }),
        })
      )
    })
  })
})
