import type { ComponentProps } from 'react'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import * as React from 'react'
import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import ConfigContext from '@/context/debug-configuration'
import { AppModeEnum, ModelModeType, TransferMethod } from '@/types/app'
import Debug from '../index'
import { APP_CHAT_WITH_MULTIPLE_MODEL, APP_CHAT_WITH_MULTIPLE_MODEL_RESTART } from '../types'

type DebugContextValue = ComponentProps<typeof ConfigContext.Provider>['value']
type DebugProps = ComponentProps<typeof Debug>

const mockState = vi.hoisted(() => ({
  mockSendCompletionMessage: vi.fn(),
  mockHandleRestart: vi.fn(),
  mockSetFeatures: vi.fn(),
  mockEventEmitterEmit: vi.fn(),
  mockToastCall: vi.fn(),
  mockToastDismiss: vi.fn(),
  mockToastUpdate: vi.fn(),
  mockToastPromise: vi.fn(),
  mockText2speechDefaultModel: null as unknown,
  mockStoreState: {
    currentLogItem: null as unknown,
    setCurrentLogItem: vi.fn(),
    showPromptLogModal: false,
    setShowPromptLogModal: vi.fn(),
    showAgentLogModal: false,
    setShowAgentLogModal: vi.fn(),
  },
  mockFeaturesState: {
    moreLikeThis: { enabled: false },
    moderation: { enabled: false },
    text2speech: { enabled: false },
    file: {
      enabled: false,
      allowed_file_upload_methods: [] as string[],
      fileUploadConfig: undefined as { image_file_size_limit?: number } | undefined,
    },
  },
  mockProviderContext: {
    textGenerationModelList: [] as Array<{
      provider: string
      models: Array<{
        model: string
        features?: string[]
        model_properties: { mode?: string }
      }>
    }>,
  },
}))

vi.mock('@langgenius/dify-ui/toast', () => ({
  toast: Object.assign(mockState.mockToastCall, {
    success: vi.fn((message: string, options?: Record<string, unknown>) =>
      mockState.mockToastCall({ type: 'success', message, ...options }),
    ),
    error: vi.fn((message: string, options?: Record<string, unknown>) =>
      mockState.mockToastCall({ type: 'error', message, ...options }),
    ),
    warning: vi.fn((message: string, options?: Record<string, unknown>) =>
      mockState.mockToastCall({ type: 'warning', message, ...options }),
    ),
    info: vi.fn((message: string, options?: Record<string, unknown>) =>
      mockState.mockToastCall({ type: 'info', message, ...options }),
    ),
    dismiss: mockState.mockToastDismiss,
    update: mockState.mockToastUpdate,
    promise: mockState.mockToastPromise,
  }),
}))

vi.mock('@/app/components/app/configuration/debug/chat-user-input', () => ({
  default: () => <div data-testid="chat-user-input">ChatUserInput</div>,
}))

vi.mock('@/app/components/app/configuration/prompt-value-panel', () => ({
  default: ({
    onSend,
    onVisionFilesChange,
  }: {
    onSend: () => void
    onVisionFilesChange: (files: Array<Record<string, unknown>>) => void
  }) => (
    <div data-testid="prompt-value-panel">
      <button type="button" data-testid="panel-send" onClick={onSend}>
        Send
      </button>
      <button
        type="button"
        data-testid="panel-set-pending-file"
        onClick={() => onVisionFilesChange([{ transfer_method: TransferMethod.local_file }])}
      >
        Pending File
      </button>
      <button
        type="button"
        data-testid="panel-set-uploaded-file"
        onClick={() =>
          onVisionFilesChange([
            { transfer_method: TransferMethod.local_file, upload_file_id: 'file-id' },
          ])
        }
      >
        Uploaded File
      </button>
      <button
        type="button"
        data-testid="panel-set-remote-file"
        onClick={() =>
          onVisionFilesChange([
            { transfer_method: TransferMethod.remote_url, url: 'https://example.com/file.png' },
          ])
        }
      >
        Remote File
      </button>
    </div>
  ),
}))

vi.mock('@/app/components/app/store', () => ({
  useStore: (
    selector: (state: {
      currentLogItem: unknown
      setCurrentLogItem: () => void
      showPromptLogModal: boolean
      setShowPromptLogModal: () => void
      showAgentLogModal: boolean
      setShowAgentLogModal: () => void
    }) => unknown,
  ) => selector(mockState.mockStoreState),
}))

vi.mock('@/app/components/app/text-generate/item', () => ({
  default: ({
    content,
    isLoading,
    isShowTextToSpeech,
    messageId,
  }: {
    content: string
    isLoading: boolean
    isShowTextToSpeech: boolean
    messageId: string | null
  }) => (
    <div
      data-testid="text-generation"
      data-loading={isLoading ? 'true' : 'false'}
      data-tts={isShowTextToSpeech ? 'true' : 'false'}
      data-message-id={messageId || ''}
    >
      {content}
    </div>
  ),
}))

vi.mock('@/app/components/base/action-button', () => ({
  default: ({
    children,
    onClick,
    state,
  }: {
    children: React.ReactNode
    onClick?: () => void
    state?: string
  }) => (
    <button type="button" data-testid="action-button" data-state={state} onClick={onClick}>
      {children}
    </button>
  ),
  ActionButtonState: {
    Active: 'active',
  },
}))

vi.mock('@/app/components/base/agent-log-modal', () => ({
  default: ({ onCancel }: { onCancel: () => void }) => (
    <div data-testid="agent-log-modal">
      <button type="button" data-testid="agent-log-cancel" onClick={onCancel}>
        Cancel
      </button>
    </div>
  ),
}))

vi.mock('@/app/components/base/features/hooks', () => ({
  useFeatures: (
    selector: (state: {
      features: {
        moreLikeThis: { enabled: boolean }
        moderation: { enabled: boolean }
        text2speech: { enabled: boolean }
        file: {
          enabled: boolean
          allowed_file_upload_methods: string[]
          fileUploadConfig?: { image_file_size_limit?: number }
        }
      }
    }) => unknown,
  ) => selector({ features: mockState.mockFeaturesState }),
  useFeaturesStore: () => ({
    getState: () => ({
      features: mockState.mockFeaturesState,
      setFeatures: mockState.mockSetFeatures,
    }),
  }),
}))

vi.mock('@/app/components/base/prompt-log-modal', () => ({
  default: ({ onCancel }: { onCancel: () => void }) => (
    <div data-testid="prompt-log-modal">
      <button type="button" data-testid="prompt-log-cancel" onClick={onCancel}>
        Cancel
      </button>
    </div>
  ),
}))

vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
  useDefaultModel: () => ({ data: mockState.mockText2speechDefaultModel }),
}))

vi.mock('@/context/event-emitter', () => ({
  useEventEmitterContextContext: () => ({
    eventEmitter: { emit: mockState.mockEventEmitterEmit },
  }),
}))

vi.mock('@/context/provider-context', () => ({
  useProviderContext: () => mockState.mockProviderContext,
}))

vi.mock('@/service/debug', () => ({
  sendCompletionMessage: mockState.mockSendCompletionMessage,
}))

vi.mock('../../base/group-name', () => ({
  default: ({ name }: { name: string }) => <div>{name}</div>,
}))

vi.mock('../../base/warning-mask/cannot-query-dataset', () => ({
  default: ({ onConfirm }: { onConfirm: () => void }) => (
    <div data-testid="cannot-query-dataset">
      <button type="button" data-testid="cannot-query-confirm" onClick={onConfirm}>
        Confirm
      </button>
    </div>
  ),
}))

vi.mock('../../base/warning-mask/formatting-changed', () => ({
  default: ({ onConfirm, onCancel }: { onConfirm: () => void; onCancel: () => void }) => (
    <div data-testid="formatting-changed">
      <button type="button" data-testid="formatting-confirm" onClick={onConfirm}>
        Confirm
      </button>
      <button type="button" data-testid="formatting-cancel" onClick={onCancel}>
        Cancel
      </button>
    </div>
  ),
}))

vi.mock('../debug-with-multiple-model', () => ({
  default: ({
    checkCanSend,
    onDebugWithMultipleModelChange,
  }: {
    checkCanSend: () => boolean
    onDebugWithMultipleModelChange: (item: {
      id: string
      model: string
      provider: string
      parameters: Record<string, unknown>
    }) => void
  }) => (
    <div data-testid="debug-with-multiple-model">
      <button type="button" data-testid="multiple-check-can-send" onClick={() => checkCanSend()}>
        Check
      </button>
      <button
        type="button"
        data-testid="multiple-switch-to-single"
        onClick={() =>
          onDebugWithMultipleModelChange({
            id: 'model-1',
            model: 'vision-model',
            provider: 'openai',
            parameters: { temperature: 0.2 },
          })
        }
      >
        Switch
      </button>
    </div>
  ),
}))

vi.mock('../debug-with-single-model', () => {
  function DebugWithSingleModelMock({
    checkCanSend,
    ref,
  }: {
    checkCanSend: () => boolean
    ref?: React.Ref<{ handleRestart: () => void }>
  }) {
    React.useImperativeHandle(ref, () => ({
      handleRestart: mockState.mockHandleRestart,
    }))

    return (
      <div data-testid="debug-with-single-model">
        <button type="button" data-testid="single-check-can-send" onClick={() => checkCanSend()}>
          Check
        </button>
      </div>
    )
  }

  return { default: DebugWithSingleModelMock }
})

const createContextValue = (overrides: Partial<DebugContextValue> = {}): DebugContextValue => ({
  readonly: false,
  canTestAndRun: true,
  appId: 'app-id',
  isAPIKeySet: true,
  isTrailFinished: false,
  mode: AppModeEnum.CHAT,
  modelModeType: ModelModeType.chat,
  promptMode: 'simple' as DebugContextValue['promptMode'],
  setPromptMode: vi.fn(),
  isAdvancedMode: false,
  isAgent: false,
  isFunctionCall: false,
  isOpenAI: true,
  collectionList: [],
  canReturnToSimpleMode: false,
  setCanReturnToSimpleMode: vi.fn(),
  chatPromptConfig: { prompt: [] } as DebugContextValue['chatPromptConfig'],
  completionPromptConfig: {
    prompt: { text: '' },
    conversation_histories_role: { user_prefix: 'user', assistant_prefix: 'assistant' },
  } as DebugContextValue['completionPromptConfig'],
  currentAdvancedPrompt: [],
  setCurrentAdvancedPrompt: vi.fn(),
  showHistoryModal: vi.fn(),
  conversationHistoriesRole: { user_prefix: 'user', assistant_prefix: 'assistant' },
  setConversationHistoriesRole: vi.fn(),
  hasSetBlockStatus: { context: false, history: true, query: true },
  conversationId: null,
  setConversationId: vi.fn(),
  introduction: '',
  setIntroduction: vi.fn(),
  suggestedQuestions: [],
  setSuggestedQuestions: vi.fn(),
  controlClearChatMessage: 0,
  setControlClearChatMessage: vi.fn(),
  prevPromptConfig: { prompt_template: '', prompt_variables: [] },
  setPrevPromptConfig: vi.fn(),
  moreLikeThisConfig: { enabled: false },
  setMoreLikeThisConfig: vi.fn(),
  suggestedQuestionsAfterAnswerConfig: { enabled: false },
  setSuggestedQuestionsAfterAnswerConfig: vi.fn(),
  speechToTextConfig: { enabled: false },
  setSpeechToTextConfig: vi.fn(),
  textToSpeechConfig: { enabled: false, voice: '', language: '' },
  setTextToSpeechConfig: vi.fn(),
  citationConfig: { enabled: false },
  setCitationConfig: vi.fn(),
  annotationConfig: {
    id: '',
    enabled: false,
    score_threshold: 0.7,
    embedding_model: {
      embedding_model_name: '',
      embedding_provider_name: '',
    },
  },
  setAnnotationConfig: vi.fn(),
  moderationConfig: { enabled: false },
  setModerationConfig: vi.fn(),
  externalDataToolsConfig: [],
  setExternalDataToolsConfig: vi.fn(),
  formattingChanged: false,
  setFormattingChanged: vi.fn(),
  inputs: {},
  setInputs: vi.fn(),
  query: '',
  setQuery: vi.fn(),
  completionParams: {},
  setCompletionParams: vi.fn(),
  modelConfig: {
    provider: 'openai',
    model_id: 'gpt-4',
    mode: ModelModeType.chat,
    configs: {
      prompt_template: '',
      prompt_variables: [],
    },
    chat_prompt_config: { prompt: [] },
    completion_prompt_config: {
      prompt: { text: '' },
      conversation_histories_role: { user_prefix: 'user', assistant_prefix: 'assistant' },
    },
    more_like_this: null,
    opening_statement: '',
    suggested_questions: [],
    sensitive_word_avoidance: null,
    speech_to_text: null,
    text_to_speech: null,
    file_upload: null,
    suggested_questions_after_answer: null,
    retriever_resource: null,
    annotation_reply: null,
    external_data_tools: [],
    system_parameters: {
      audio_file_size_limit: 0,
      file_size_limit: 0,
      image_file_size_limit: 0,
      video_file_size_limit: 0,
      workflow_file_upload_limit: 0,
    },
    dataSets: [],
    agentConfig: {
      enabled: false,
      max_iteration: 5,
      tools: [],
      strategy: 'react',
    },
  } as DebugContextValue['modelConfig'],
  setModelConfig: vi.fn(),
  dataSets: [],
  setDataSets: vi.fn(),
  showSelectDataSet: vi.fn(),
  datasetConfigs: {
    retrieval_model: 'single',
    reranking_model: {
      reranking_provider_name: '',
      reranking_model_name: '',
    },
    top_k: 4,
    score_threshold_enabled: false,
    score_threshold: 0.7,
    datasets: { datasets: [] },
  } as DebugContextValue['datasetConfigs'],
  datasetConfigsRef: { current: null } as unknown as DebugContextValue['datasetConfigsRef'],
  setDatasetConfigs: vi.fn(),
  hasSetContextVar: false,
  isShowVisionConfig: false,
  visionConfig: {
    enabled: false,
    number_limits: 2,
    detail: 'low',
    transfer_methods: [],
  } as DebugContextValue['visionConfig'],
  setVisionConfig: vi.fn(),
  isAllowVideoUpload: false,
  isShowDocumentConfig: false,
  isShowAudioConfig: false,
  rerankSettingModalOpen: false,
  setRerankSettingModalOpen: vi.fn(),
  ...overrides,
})

const renderDebug = (
  options: {
    contextValue?: Partial<DebugContextValue>
    props?: Partial<DebugProps>
  } = {},
) => {
  const onSetting = vi.fn()
  const props: ComponentProps<typeof Debug> = {
    isAPIKeySet: true,
    onSetting,
    inputs: {},
    modelParameterParams: {
      setModel: vi.fn(),
      onCompletionParamsChange: vi.fn(),
    },
    debugWithMultipleModel: false,
    multipleModelConfigs: [],
    onMultipleModelConfigsChange: vi.fn(),
    ...options.props,
  }

  render(
    React.createElement(ConfigContext.Provider, {
      value: createContextValue(options.contextValue),
      children: <Debug {...props} />,
    }),
  )

  return { onSetting, notify: mockState.mockToastCall, props }
}

describe('Debug', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockState.mockSendCompletionMessage.mockReset()
    mockState.mockHandleRestart.mockReset()
    mockState.mockSetFeatures.mockReset()
    mockState.mockEventEmitterEmit.mockReset()
    mockState.mockText2speechDefaultModel = null
    mockState.mockStoreState = {
      currentLogItem: null,
      setCurrentLogItem: vi.fn(),
      showPromptLogModal: false,
      setShowPromptLogModal: vi.fn(),
      showAgentLogModal: false,
      setShowAgentLogModal: vi.fn(),
    }
    mockState.mockFeaturesState = {
      moreLikeThis: { enabled: false },
      moderation: { enabled: false },
      text2speech: { enabled: false },
      file: { enabled: false, allowed_file_upload_methods: [], fileUploadConfig: undefined },
    }
    mockState.mockProviderContext = {
      textGenerationModelList: [
        {
          provider: 'openai',
          models: [
            {
              model: 'vision-model',
              features: [ModelFeatureEnum.vision],
              model_properties: { mode: 'chat' },
            },
          ],
        },
      ],
    }
  })

  describe('Empty states', () => {
    it('should render no-provider empty state and forward manage action', () => {
      const { onSetting } = renderDebug({
        contextValue: {
          modelConfig: {
            ...createContextValue().modelConfig,
            provider: '',
            model_id: '',
          },
        },
        props: {
          isAPIKeySet: false,
        },
      })

      expect(screen.getByText('appDebug.noModelProviderConfigured'))!.toBeInTheDocument()
      expect(screen.getByText('appDebug.noModelProviderConfiguredTip'))!.toBeInTheDocument()

      fireEvent.click(screen.getByRole('button', { name: 'appDebug.manageModels' }))
      expect(onSetting).toHaveBeenCalledTimes(1)
    })

    it('should render no-model-selected empty state when provider exists but model is missing', () => {
      renderDebug({
        contextValue: {
          modelConfig: {
            ...createContextValue().modelConfig,
            provider: 'openai',
            model_id: '',
          },
        },
        props: {
          isAPIKeySet: true,
        },
      })

      expect(screen.getByText('appDebug.noModelSelected'))!.toBeInTheDocument()
      expect(screen.getByText('appDebug.noModelSelectedTip'))!.toBeInTheDocument()
      expect(screen.queryByText('appDebug.noModelProviderConfigured')).not.toBeInTheDocument()
    })
  })

  describe('Single model mode', () => {
    it('should render single-model panel and refresh conversation', () => {
      renderDebug()

      expect(screen.getByTestId('debug-with-single-model'))!.toBeInTheDocument()

      fireEvent.click(screen.getAllByTestId('action-button')[0]!)
      expect(mockState.mockHandleRestart).toHaveBeenCalledTimes(1)
    })

    it('should toggle chat input visibility when variable panel button is clicked', () => {
      renderDebug({
        contextValue: {
          inputs: { question: 'hello' },
          modelConfig: {
            ...createContextValue().modelConfig,
            configs: {
              prompt_template: '',
              prompt_variables: [
                {
                  key: 'question',
                  name: 'Question',
                  type: 'string',
                  required: true,
                },
              ] as DebugContextValue['modelConfig']['configs']['prompt_variables'],
            },
          },
        },
      })

      expect(screen.getByTestId('chat-user-input'))!.toBeInTheDocument()
      fireEvent.click(screen.getAllByTestId('action-button')[1]!)
      expect(screen.queryByTestId('chat-user-input')).not.toBeInTheDocument()
    })

    it('should keep refresh action available when readonly app still has test/run permission', () => {
      renderDebug({
        contextValue: {
          readonly: true,
          canTestAndRun: true,
        },
      })

      fireEvent.click(screen.getAllByTestId('action-button')[0]!)
      expect(mockState.mockHandleRestart).toHaveBeenCalledTimes(1)
    })

    it('should not render refresh action when test/run permission is missing', () => {
      renderDebug({
        contextValue: {
          readonly: false,
          canTestAndRun: false,
        },
      })

      expect(screen.queryByTestId('action-button')).not.toBeInTheDocument()
    })

    it('should show formatting confirmation and handle cancel', () => {
      const setFormattingChanged = vi.fn()

      renderDebug({
        contextValue: {
          formattingChanged: true,
          setFormattingChanged,
        },
      })

      expect(screen.getByTestId('formatting-changed'))!.toBeInTheDocument()
      fireEvent.click(screen.getByTestId('formatting-cancel'))
      expect(setFormattingChanged).toHaveBeenCalledWith(false)
    })

    it('should handle formatting confirmation with restart', () => {
      const setFormattingChanged = vi.fn()

      renderDebug({
        contextValue: {
          formattingChanged: true,
          setFormattingChanged,
        },
      })

      fireEvent.click(screen.getByTestId('formatting-confirm'))
      expect(setFormattingChanged).toHaveBeenCalledWith(false)
      expect(mockState.mockHandleRestart).toHaveBeenCalledTimes(1)
    })

    it('should notify when history block is missing in advanced completion mode', () => {
      const { notify } = renderDebug({
        contextValue: {
          isAdvancedMode: true,
          mode: AppModeEnum.CHAT,
          modelModeType: ModelModeType.completion,
          hasSetBlockStatus: { context: false, history: false, query: true },
        },
      })

      fireEvent.click(screen.getByTestId('single-check-can-send'))
      expect(notify).toHaveBeenCalledWith({
        type: 'error',
        message: 'appDebug.otherError.historyNoBeEmpty',
      })
    })

    it('should notify when query block is missing in advanced completion mode', () => {
      const { notify } = renderDebug({
        contextValue: {
          isAdvancedMode: true,
          mode: AppModeEnum.CHAT,
          modelModeType: ModelModeType.completion,
          hasSetBlockStatus: { context: false, history: true, query: false },
        },
      })

      fireEvent.click(screen.getByTestId('single-check-can-send'))
      expect(notify).toHaveBeenCalledWith({
        type: 'error',
        message: 'appDebug.otherError.queryNoBeEmpty',
      })
    })
  })

  describe('Completion mode', () => {
    it('should render prompt value panel and no-result placeholder', () => {
      renderDebug({
        contextValue: {
          mode: AppModeEnum.COMPLETION,
        },
      })

      expect(screen.getByTestId('prompt-value-panel'))!.toBeInTheDocument()
      expect(screen.getByText('appDebug.noResult'))!.toBeInTheDocument()
    })

    it('should notify when required input is missing', () => {
      const { notify } = renderDebug({
        contextValue: {
          mode: AppModeEnum.COMPLETION,
          inputs: {},
          modelConfig: {
            ...createContextValue().modelConfig,
            configs: {
              prompt_template: '',
              prompt_variables: [
                {
                  key: 'question',
                  name: 'Question',
                  type: 'string',
                  required: true,
                },
              ] as DebugContextValue['modelConfig']['configs']['prompt_variables'],
            },
          },
        },
      })

      fireEvent.click(screen.getByTestId('panel-send'))
      expect(notify).toHaveBeenCalledWith({
        type: 'error',
        message: 'appDebug.errorMessage.valueOfVarRequired:{"key":"Question"}',
      })
      expect(mockState.mockSendCompletionMessage).not.toHaveBeenCalled()
    })

    it('should notify when local file upload is still pending', () => {
      const { notify } = renderDebug({
        contextValue: {
          mode: AppModeEnum.COMPLETION,
          modelConfig: {
            ...createContextValue().modelConfig,
            configs: {
              prompt_template: '',
              prompt_variables: [],
            },
          },
        },
      })

      fireEvent.click(screen.getByTestId('panel-set-pending-file'))
      fireEvent.click(screen.getByTestId('panel-send'))

      expect(notify).toHaveBeenCalledWith({
        type: 'info',
        message: 'appDebug.errorMessage.waitForFileUpload',
      })
      expect(mockState.mockSendCompletionMessage).not.toHaveBeenCalled()
    })

    it('should show cannot-query-dataset warning when dataset context variable is missing', () => {
      renderDebug({
        contextValue: {
          mode: AppModeEnum.COMPLETION,
          dataSets: [{ id: 'dataset-1' }] as DebugContextValue['dataSets'],
          hasSetContextVar: false,
          modelConfig: {
            ...createContextValue().modelConfig,
            configs: {
              prompt_template: '',
              prompt_variables: [],
            },
          },
        },
      })

      fireEvent.click(screen.getByTestId('panel-send'))
      expect(screen.getByTestId('cannot-query-dataset'))!.toBeInTheDocument()

      fireEvent.click(screen.getByTestId('cannot-query-confirm'))
      expect(screen.queryByTestId('cannot-query-dataset')).not.toBeInTheDocument()
    })

    it('should send completion request and render completion result', async () => {
      mockState.mockText2speechDefaultModel = { provider: 'openai' }
      mockState.mockFeaturesState = {
        ...mockState.mockFeaturesState,
        text2speech: { enabled: true },
        file: {
          enabled: true,
          allowed_file_upload_methods: [],
          fileUploadConfig: { image_file_size_limit: 2 },
        },
      }

      mockState.mockSendCompletionMessage.mockImplementation(
        (
          _appId,
          _data,
          handlers: {
            onData: (chunk: string, isFirst: boolean, payload: { messageId: string }) => void
            onMessageReplace: (payload: { answer: string }) => void
            onCompleted: () => void
            onError: () => void
          },
        ) => {
          handlers.onData('hello', true, { messageId: 'msg-1' })
          handlers.onMessageReplace({ answer: 'final answer' })
          handlers.onCompleted()
        },
      )

      renderDebug({
        contextValue: {
          mode: AppModeEnum.COMPLETION,
          promptMode: 'simple' as DebugContextValue['promptMode'],
          textToSpeechConfig: { enabled: true, voice: 'alloy', language: 'en' },
          modelConfig: {
            ...createContextValue().modelConfig,
            configs: {
              prompt_template: 'Prompt',
              prompt_variables: [
                {
                  key: 'question',
                  name: 'Question',
                  type: 'string',
                  required: true,
                  is_context_var: true,
                },
              ] as DebugContextValue['modelConfig']['configs']['prompt_variables'],
            },
          },
        },
        props: {
          inputs: { question: 'hello' },
        },
      })

      fireEvent.click(screen.getByTestId('panel-send'))

      await waitFor(() => expect(mockState.mockSendCompletionMessage).toHaveBeenCalledTimes(1))
      const [, requestData] = (mockState.mockSendCompletionMessage.mock.calls[0] ?? []) as [
        unknown,
        any,
      ]
      expect(requestData).toMatchObject({
        inputs: { question: 'hello' },
        model_config: {
          model: {
            provider: 'openai',
            name: 'gpt-4',
          },
          dataset_query_variable: 'question',
        },
      })
      expect(screen.getByTestId('text-generation'))!.toHaveTextContent('final answer')
      expect(screen.getByTestId('text-generation'))!.toHaveAttribute('data-message-id', 'msg-1')
      expect(screen.getByTestId('text-generation'))!.toHaveAttribute('data-tts', 'true')
    })

    it('should notify when sending again while a response is in progress', async () => {
      mockState.mockSendCompletionMessage.mockImplementation(() => undefined)
      const { notify } = renderDebug({
        contextValue: {
          mode: AppModeEnum.COMPLETION,
          modelConfig: {
            ...createContextValue().modelConfig,
            configs: {
              prompt_template: '',
              prompt_variables: [],
            },
          },
        },
      })

      fireEvent.click(screen.getByTestId('panel-send'))
      fireEvent.click(screen.getByTestId('panel-send'))

      await waitFor(() => expect(mockState.mockSendCompletionMessage).toHaveBeenCalledTimes(1))
      expect(notify).toHaveBeenCalledWith({
        type: 'info',
        message: 'appDebug.errorMessage.waitForResponse',
      })
    })

    it('should keep remote files and reset responding state on send error', async () => {
      mockState.mockFeaturesState = {
        ...mockState.mockFeaturesState,
        file: {
          enabled: true,
          allowed_file_upload_methods: [],
          fileUploadConfig: undefined,
        },
      }

      mockState.mockSendCompletionMessage.mockImplementation(
        (
          _appId,
          data,
          handlers: {
            onError: () => void
          },
        ) => {
          expect(data.files).toEqual([
            {
              transfer_method: TransferMethod.remote_url,
              url: 'https://example.com/file.png',
            },
          ])
          handlers.onError()
        },
      )

      renderDebug({
        contextValue: {
          mode: AppModeEnum.COMPLETION,
          modelConfig: {
            ...createContextValue().modelConfig,
            configs: {
              prompt_template: '',
              prompt_variables: [],
            },
          },
        },
      })

      fireEvent.click(screen.getByTestId('panel-set-remote-file'))
      fireEvent.click(screen.getByTestId('panel-send'))

      await waitFor(() => expect(mockState.mockSendCompletionMessage).toHaveBeenCalledTimes(1))
      expect(screen.getByText('appDebug.noResult'))!.toBeInTheDocument()
    })

    it('should render prompt log modal in completion mode when store flag is enabled', () => {
      mockState.mockStoreState = {
        ...mockState.mockStoreState,
        showPromptLogModal: true,
      }

      renderDebug({
        contextValue: {
          mode: AppModeEnum.COMPLETION,
        },
      })

      expect(screen.getByTestId('prompt-log-modal'))!.toBeInTheDocument()
    })

    it('should close prompt log modal in completion mode', () => {
      const setCurrentLogItem = vi.fn()
      const setShowPromptLogModal = vi.fn()

      mockState.mockStoreState = {
        ...mockState.mockStoreState,
        currentLogItem: { id: 'log-1' },
        setCurrentLogItem,
        showPromptLogModal: true,
        setShowPromptLogModal,
      }

      renderDebug({
        contextValue: {
          mode: AppModeEnum.COMPLETION,
        },
      })

      fireEvent.click(screen.getByTestId('prompt-log-cancel'))
      expect(setCurrentLogItem).toHaveBeenCalledTimes(1)
      expect(setShowPromptLogModal).toHaveBeenCalledWith(false)
    })
  })

  describe('Multiple model mode', () => {
    it('should append a blank model when add-model button is clicked', () => {
      const onMultipleModelConfigsChange = vi.fn()

      renderDebug({
        props: {
          debugWithMultipleModel: true,
          multipleModelConfigs: [
            { id: 'model-1', model: 'vision-model', provider: 'openai', parameters: {} },
          ],
          onMultipleModelConfigsChange,
        },
      })

      fireEvent.click(screen.getByRole('button', { name: 'common.modelProvider.addModel(1/4)' }))
      expect(onMultipleModelConfigsChange).toHaveBeenCalledWith(true, [
        { id: 'model-1', model: 'vision-model', provider: 'openai', parameters: {} },
        expect.objectContaining({ model: '', provider: '', parameters: {} }),
      ])
    })

    it('should disable add-model button when there are already four models', () => {
      renderDebug({
        props: {
          debugWithMultipleModel: true,
          multipleModelConfigs: [
            { id: '1', model: 'a', provider: 'p', parameters: {} },
            { id: '2', model: 'b', provider: 'p', parameters: {} },
            { id: '3', model: 'c', provider: 'p', parameters: {} },
            { id: '4', model: 'd', provider: 'p', parameters: {} },
          ],
        },
      })

      expect(
        screen.getByRole('button', { name: 'common.modelProvider.addModel(4/4)' }),
      )!.toBeDisabled()
    })

    it('should disable add-model button when test/run permission is missing', () => {
      const onMultipleModelConfigsChange = vi.fn()

      renderDebug({
        contextValue: {
          canTestAndRun: false,
        },
        props: {
          debugWithMultipleModel: true,
          multipleModelConfigs: [
            { id: 'model-1', model: 'vision-model', provider: 'openai', parameters: {} },
          ],
          onMultipleModelConfigsChange,
        },
      })

      const addModelButton = screen.getByRole('button', {
        name: 'common.modelProvider.addModel(1/4)',
      })
      expect(addModelButton).toBeDisabled()
      fireEvent.click(addModelButton)
      expect(onMultipleModelConfigsChange).not.toHaveBeenCalled()
    })

    it('should emit completion event in multiple-model completion mode', () => {
      renderDebug({
        contextValue: {
          mode: AppModeEnum.COMPLETION,
          modelConfig: {
            ...createContextValue().modelConfig,
            configs: {
              prompt_template: '',
              prompt_variables: [],
            },
          },
        },
        props: {
          debugWithMultipleModel: true,
          multipleModelConfigs: [
            { id: '1', model: 'vision-model', provider: 'openai', parameters: {} },
          ],
        },
      })

      fireEvent.click(screen.getByTestId('panel-set-uploaded-file'))
      fireEvent.click(screen.getByTestId('panel-send'))

      expect(mockState.mockEventEmitterEmit).toHaveBeenCalledWith({
        type: APP_CHAT_WITH_MULTIPLE_MODEL,
        payload: {
          message: '',
          files: [{ transfer_method: TransferMethod.local_file, upload_file_id: 'file-id' }],
        },
      })
    })

    it('should emit restart event when refresh is clicked in multiple-model mode', () => {
      renderDebug({
        props: {
          debugWithMultipleModel: true,
          multipleModelConfigs: [
            { id: '1', model: 'vision-model', provider: 'openai', parameters: {} },
          ],
        },
      })

      fireEvent.click(screen.getAllByTestId('action-button')[0]!)
      expect(mockState.mockEventEmitterEmit).toHaveBeenCalledWith({
        type: APP_CHAT_WITH_MULTIPLE_MODEL_RESTART,
      })
    })

    it('should switch from multiple model to single model with selected parameters', () => {
      const setModel = vi.fn()
      const onCompletionParamsChange = vi.fn()
      const onMultipleModelConfigsChange = vi.fn()

      renderDebug({
        props: {
          debugWithMultipleModel: true,
          multipleModelConfigs: [
            {
              id: 'model-1',
              model: 'vision-model',
              provider: 'openai',
              parameters: { temperature: 0.2 },
            },
          ],
          onMultipleModelConfigsChange,
          modelParameterParams: {
            setModel,
            onCompletionParamsChange,
          },
        },
      })

      fireEvent.click(screen.getByTestId('multiple-switch-to-single'))

      expect(setModel).toHaveBeenCalledWith({
        modelId: 'vision-model',
        provider: 'openai',
        mode: 'chat',
        features: [ModelFeatureEnum.vision],
      })
      expect(onCompletionParamsChange).toHaveBeenCalledWith({ temperature: 0.2 })
      expect(onMultipleModelConfigsChange).toHaveBeenCalledWith(false, [])
    })

    it('should update feature store according to multiple-model vision support', () => {
      renderDebug({
        contextValue: {
          mode: AppModeEnum.CHAT,
        },
        props: {
          debugWithMultipleModel: true,
          multipleModelConfigs: [
            { id: '1', model: 'vision-model', provider: 'openai', parameters: {} },
          ],
        },
      })

      expect(mockState.mockSetFeatures).toHaveBeenCalledWith(
        expect.objectContaining({
          file: expect.objectContaining({
            enabled: true,
          }),
        }),
      )
    })

    it('should render prompt and agent log modals in multiple-model mode', () => {
      mockState.mockStoreState = {
        ...mockState.mockStoreState,
        showPromptLogModal: true,
        showAgentLogModal: true,
      }

      renderDebug({
        props: {
          debugWithMultipleModel: true,
          multipleModelConfigs: [
            { id: '1', model: 'vision-model', provider: 'openai', parameters: {} },
          ],
        },
      })

      expect(screen.getByTestId('prompt-log-modal'))!.toBeInTheDocument()
      expect(screen.getByTestId('agent-log-modal'))!.toBeInTheDocument()
    })

    it('should close prompt and agent log modals in multiple-model mode', () => {
      const setCurrentLogItem = vi.fn()
      const setShowPromptLogModal = vi.fn()
      const setShowAgentLogModal = vi.fn()

      mockState.mockStoreState = {
        ...mockState.mockStoreState,
        currentLogItem: { id: 'log-1' },
        setCurrentLogItem,
        showPromptLogModal: true,
        setShowPromptLogModal,
        showAgentLogModal: true,
        setShowAgentLogModal,
      }

      renderDebug({
        props: {
          debugWithMultipleModel: true,
          multipleModelConfigs: [
            { id: '1', model: 'vision-model', provider: 'openai', parameters: {} },
          ],
        },
      })

      fireEvent.click(screen.getByTestId('prompt-log-cancel'))
      fireEvent.click(screen.getByTestId('agent-log-cancel'))

      expect(setCurrentLogItem).toHaveBeenCalledTimes(2)
      expect(setShowPromptLogModal).toHaveBeenCalledWith(false)
      expect(setShowAgentLogModal).toHaveBeenCalledWith(false)
    })
  })
})
