import type { ComponentType, SVGProps } from 'react'
import { Field } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { RadioGroup } from '@langgenius/dify-ui/radio'
import { fireEvent, render, screen } from '@testing-library/react'
import { HybridSearchModeEnum, RetrievalSearchMethodEnum, WeightedScoreEnum } from '../../../types'
import { SearchMethodOption } from '../search-method-option'

const mockUseModelListAndDefaultModel = vi.hoisted(() => vi.fn())
const mockUseProviderContext = vi.hoisted(() => vi.fn())
const mockUseCredentialPanelState = vi.hoisted(() => vi.fn())

vi.mock(
  '@/app/components/header/account-setting/model-provider-page/hooks',
  async (importOriginal) => {
    const actual =
      await importOriginal<
        typeof import('@/app/components/header/account-setting/model-provider-page/hooks')
      >()
    return {
      ...actual,
      useModelListAndDefaultModel: (
        ...args: Parameters<typeof actual.useModelListAndDefaultModel>
      ) => mockUseModelListAndDefaultModel(...args),
    }
  },
)

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

vi.mock(
  '@/app/components/header/account-setting/model-provider-page/provider-added-card/use-credential-panel-state',
  () => ({
    useCredentialPanelState: (...args: unknown[]) => mockUseCredentialPanelState(...args),
  }),
)

const SearchIcon: ComponentType<SVGProps<SVGSVGElement>> = (props) => (
  <svg aria-hidden="true" {...props} />
)

const hybridSearchModeOptions = [
  {
    id: HybridSearchModeEnum.WeightedScore,
    title: 'Weighted mode',
    description: 'Use weighted score',
  },
  {
    id: HybridSearchModeEnum.RerankingModel,
    title: 'Rerank mode',
    description: 'Use reranking model',
  },
]

const weightedScore = {
  weight_type: WeightedScoreEnum.Customized,
  vector_setting: {
    vector_weight: 0.8,
    embedding_provider_name: 'openai',
    embedding_model_name: 'text-embedding-3-large',
  },
  keyword_setting: {
    keyword_weight: 0.2,
  },
}

const createProps = () => ({
  option: {
    id: RetrievalSearchMethodEnum.semantic,
    icon: SearchIcon,
    title: 'Semantic title',
    description: 'Semantic description',
    effectColor: 'purple',
  },
  searchMethod: RetrievalSearchMethodEnum.semantic,
  onRetrievalSearchMethodChange: vi.fn(),
  hybridSearch: {
    mode: HybridSearchModeEnum.WeightedScore,
    options: hybridSearchModeOptions,
    onModeChange: vi.fn(),
    weightedScore,
    onWeightedScoreChange: vi.fn(),
  },
  reranking: {
    enabled: false,
    onEnabledChange: vi.fn(),
    rerankingModel: {
      reranking_provider_name: '',
      reranking_model_name: '',
    },
    onRerankingModelChange: vi.fn(),
    showMultiModalTip: false,
  },
  retrievalParameters: {
    topK: {
      value: 3,
      onChange: vi.fn(),
    },
    scoreThreshold: {
      value: 0.5,
      onChange: vi.fn(),
      enabled: true,
      onEnabledChange: vi.fn(),
    },
  },
})

function renderSearchMethodOption(props: ReturnType<typeof createProps>) {
  const { onRetrievalSearchMethodChange, ...optionProps } = props

  render(
    <Field name="retrieval_search_method">
      <Fieldset
        render={
          <RadioGroup
            value={props.searchMethod}
            onValueChange={(value) => onRetrievalSearchMethodChange(value)}
          />
        }
      >
        <FieldsetLegend>Retrieval search method</FieldsetLegend>
        <SearchMethodOption {...optionProps} />
      </Fieldset>
    </Field>,
  )
}

describe('SearchMethodOption', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockUseModelListAndDefaultModel.mockReturnValue({
      modelList: [],
      defaultModel: undefined,
    })
    mockUseProviderContext.mockReturnValue({
      modelProviders: [],
    })
    mockUseCredentialPanelState.mockReturnValue({
      variant: 'api-active',
      priority: 'apiKeyOnly',
      supportsCredits: false,
      showPrioritySwitcher: false,
      hasCredentials: true,
      isCreditsExhausted: false,
      credentialName: undefined,
      credits: 0,
    })
  })

  it('should render semantic search controls and notify retrieval and reranking changes', () => {
    const props = createProps()

    renderSearchMethodOption(props)

    expect(screen.getByText('Semantic title'))!.toBeInTheDocument()
    expect(screen.getByText('common.modelProvider.rerankModel.key'))!.toBeInTheDocument()
    expect(screen.getByText('plugin.detailPanel.configureModel'))!.toBeInTheDocument()
    expect(screen.getAllByRole('switch')).toHaveLength(2)

    fireEvent.click(screen.getAllByRole('switch')[0]!)

    expect(props.reranking.onEnabledChange).toHaveBeenCalledWith(true)
  })

  it('should notify retrieval changes when an inactive option is selected', () => {
    const props = createProps()
    const fullTextProps = {
      ...props,
      option: {
        ...props.option,
        id: RetrievalSearchMethodEnum.fullText,
        title: 'Full-text title',
      },
    }

    renderSearchMethodOption(fullTextProps)

    expect(screen.getByText('Full-text title'))!.toBeInTheDocument()

    fireEvent.click(screen.getByText('Full-text title'))

    expect(props.onRetrievalSearchMethodChange).toHaveBeenCalledWith(
      RetrievalSearchMethodEnum.fullText,
    )
  })

  it('should render hybrid weighted-score controls without reranking model selector', () => {
    const props = createProps()
    const hybridProps = {
      ...props,
      option: {
        ...props.option,
        id: RetrievalSearchMethodEnum.hybrid,
        title: 'Hybrid title',
      },
      searchMethod: RetrievalSearchMethodEnum.hybrid,
      hybridSearch: {
        ...props.hybridSearch,
        mode: HybridSearchModeEnum.WeightedScore,
      },
      reranking: {
        ...props.reranking,
        showMultiModalTip: true,
      },
    }

    renderSearchMethodOption(hybridProps)

    expect(screen.getByText('Weighted mode'))!.toBeInTheDocument()
    expect(screen.getByText('Rerank mode'))!.toBeInTheDocument()
    expect(screen.getByText('dataset.weightedScore.semantic'))!.toBeInTheDocument()
    expect(screen.getByText('dataset.weightedScore.keyword'))!.toBeInTheDocument()
    expect(screen.queryByText('common.modelProvider.rerankModel.key')).not.toBeInTheDocument()
    expect(
      screen.queryByText('datasetSettings.form.retrievalSetting.multiModalTip'),
    ).not.toBeInTheDocument()

    fireEvent.click(screen.getByText('Rerank mode'))

    expect(props.hybridSearch.onModeChange).toHaveBeenCalledWith(
      HybridSearchModeEnum.RerankingModel,
    )
  })

  it('should render the hybrid reranking selector when reranking mode is selected', () => {
    const props = createProps()
    const hybridProps = {
      ...props,
      option: {
        ...props.option,
        id: RetrievalSearchMethodEnum.hybrid,
        title: 'Hybrid title',
      },
      searchMethod: RetrievalSearchMethodEnum.hybrid,
      hybridSearch: {
        ...props.hybridSearch,
        mode: HybridSearchModeEnum.RerankingModel,
      },
      reranking: {
        ...props.reranking,
        showMultiModalTip: true,
      },
    }

    renderSearchMethodOption(hybridProps)

    expect(screen.getByText('plugin.detailPanel.configureModel'))!.toBeInTheDocument()
    expect(screen.queryByText('common.modelProvider.rerankModel.key')).not.toBeInTheDocument()
    expect(screen.queryByText('dataset.weightedScore.semantic')).not.toBeInTheDocument()
    expect(
      screen.getByText('datasetSettings.form.retrievalSetting.multiModalTip'),
    )!.toBeInTheDocument()
  })

  it('should hide the score-threshold control for keyword search', () => {
    const props = createProps()
    const keywordProps = {
      ...props,
      option: {
        ...props.option,
        id: RetrievalSearchMethodEnum.keywordSearch,
        title: 'Keyword title',
      },
      searchMethod: RetrievalSearchMethodEnum.keywordSearch,
    }

    renderSearchMethodOption(keywordProps)

    fireEvent.change(screen.getByRole('textbox'), { target: { value: '9' } })

    expect(screen.getAllByRole('textbox')).toHaveLength(1)
    expect(screen.queryAllByRole('switch')).toHaveLength(0)
    expect(props.retrievalParameters.topK.onChange).toHaveBeenCalledWith(9)
  })
})
