import type { Features } from '../../../types'
import type { OnFeaturesChange } from '@/app/components/base/features/types'
import { act, fireEvent, render, screen } from '@testing-library/react'
import { FeaturesProvider } from '../../../context'
import AnnotationReply from '../index'

const originalConsoleError = console.error
const mockPush = vi.fn()
let mockPathname = '/app/test-app-id/configuration'
vi.mock('next/navigation', () => ({
  useRouter: () => ({ push: mockPush }),
  usePathname: () => mockPathname,
}))

let mockIsShowAnnotationConfigInit = false
let mockIsShowAnnotationFullModal = false
const mockHandleEnableAnnotation = vi.fn().mockResolvedValue(undefined)
const mockHandleDisableAnnotation = vi.fn().mockResolvedValue(undefined)
const mockSetIsShowAnnotationConfigInit = vi.fn((v: boolean) => {
  mockIsShowAnnotationConfigInit = v
})
const mockSetIsShowAnnotationFullModal = vi.fn((v: boolean) => {
  mockIsShowAnnotationFullModal = v
})

let capturedSetAnnotationConfig: ((config: Record<string, unknown>) => void) | null = null

vi.mock('@/app/components/base/features/new-feature-panel/annotation-reply/use-annotation-config', () => ({
  default: ({ setAnnotationConfig }: { setAnnotationConfig: (config: Record<string, unknown>) => void }) => {
    capturedSetAnnotationConfig = setAnnotationConfig
    return {
      handleEnableAnnotation: mockHandleEnableAnnotation,
      handleDisableAnnotation: mockHandleDisableAnnotation,
      get isShowAnnotationConfigInit() { return mockIsShowAnnotationConfigInit },
      setIsShowAnnotationConfigInit: mockSetIsShowAnnotationConfigInit,
      get isShowAnnotationFullModal() { return mockIsShowAnnotationFullModal },
      setIsShowAnnotationFullModal: mockSetIsShowAnnotationFullModal,
    }
  },
}))

vi.mock('@/app/components/billing/annotation-full/modal', () => ({
  default: ({ show, onHide }: { show: boolean, onHide: () => void }) => (
    show
      ? (
          <div data-testid="annotation-full-modal">
            <button data-testid="full-hide" onClick={onHide}>Hide</button>
          </div>
        )
      : null
  ),
}))

vi.mock('@/config', () => ({
  ANNOTATION_DEFAULT: { score_threshold: 0.9 },
}))

vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
  useModelListAndDefaultModelAndCurrentProviderAndModel: () => ({
    modelList: [{ provider: { provider: 'openai' }, models: [{ model: 'text-embedding-ada-002' }] }],
    defaultModel: { provider: { provider: 'openai' }, model: 'text-embedding-ada-002' },
    currentModel: true,
  }),
}))

vi.mock('@/app/components/header/account-setting/model-provider-page/declarations', () => ({
  ModelTypeEnum: {
    textEmbedding: 'text-embedding',
  },
}))

vi.mock('@/app/components/header/account-setting/model-provider-page/model-selector', () => ({
  default: () => (
    <div data-testid="model-selector">Model Selector</div>
  ),
}))

const defaultFeatures: Features = {
  moreLikeThis: { enabled: false },
  opening: { enabled: false },
  suggested: { enabled: false },
  text2speech: { enabled: false },
  speech2text: { enabled: false },
  citation: { enabled: false },
  moderation: { enabled: false },
  file: { enabled: false },
  annotationReply: { enabled: false },
}

const renderWithProvider = (
  props: { disabled?: boolean, onChange?: OnFeaturesChange } = {},
  featureOverrides?: Partial<Features>,
) => {
  const features = { ...defaultFeatures, ...featureOverrides }
  return render(
    <FeaturesProvider features={features}>
      <AnnotationReply disabled={props.disabled} onChange={props.onChange} />
    </FeaturesProvider>,
  )
}

describe('AnnotationReply', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    vi.spyOn(console, 'error').mockImplementation((...args: unknown[]) => {
      const message = args.map(arg => String(arg)).join(' ')
      if (message.includes('A props object containing a "key" prop is being spread into JSX')
        || message.includes('React keys must be passed directly to JSX without using spread')) {
        return
      }
      originalConsoleError(...args as Parameters<typeof console.error>)
    })
    mockPathname = '/app/test-app-id/configuration'
    mockIsShowAnnotationConfigInit = false
    mockIsShowAnnotationFullModal = false
    capturedSetAnnotationConfig = null
  })

  it('should render the annotation reply title', () => {
    renderWithProvider()

    expect(screen.getByText(/feature\.annotation\.title/)).toBeInTheDocument()
  })

  it('should render description when not enabled', () => {
    renderWithProvider()

    expect(screen.getByText(/feature\.annotation\.description/)).toBeInTheDocument()
  })

  it('should render a switch toggle', () => {
    renderWithProvider()

    expect(screen.getByRole('switch')).toBeInTheDocument()
  })

  it('should call setIsShowAnnotationConfigInit when switch is toggled on', () => {
    renderWithProvider()

    fireEvent.click(screen.getByRole('switch'))

    expect(mockSetIsShowAnnotationConfigInit).toHaveBeenCalledWith(true)
  })

  it('should call handleDisableAnnotation when switch is toggled off', () => {
    renderWithProvider({}, {
      annotationReply: {
        enabled: true,
        score_threshold: 0.9,
        embedding_model: {
          embedding_provider_name: 'openai',
          embedding_model_name: 'text-embedding-ada-002',
        },
      },
    })

    fireEvent.click(screen.getByRole('switch'))

    expect(mockHandleDisableAnnotation).toHaveBeenCalled()
  })

  it('should show score threshold and embedding model when enabled', () => {
    renderWithProvider({}, {
      annotationReply: {
        enabled: true,
        score_threshold: 0.9,
        embedding_model: {
          embedding_provider_name: 'openai',
          embedding_model_name: 'text-embedding-ada-002',
        },
      },
    })

    expect(screen.getByText('0.9')).toBeInTheDocument()
    expect(screen.getByText('text-embedding-ada-002')).toBeInTheDocument()
  })

  it('should show dash when score threshold is not set', () => {
    renderWithProvider({}, {
      annotationReply: {
        enabled: true,
        embedding_model: {
          embedding_provider_name: 'openai',
          embedding_model_name: 'text-embedding-ada-002',
        },
      },
    })

    expect(screen.getByText('-')).toBeInTheDocument()
  })

  it('should show buttons when hovering over enabled feature', () => {
    renderWithProvider({}, {
      annotationReply: {
        enabled: true,
        score_threshold: 0.9,
        embedding_model: {
          embedding_provider_name: 'openai',
          embedding_model_name: 'text-embedding-ada-002',
        },
      },
    })

    const card = screen.getByText(/feature\.annotation\.title/).closest('[class]')!
    fireEvent.mouseEnter(card)

    expect(screen.getByText(/operation\.params/)).toBeInTheDocument()
    expect(screen.getByText(/feature\.annotation\.cacheManagement/)).toBeInTheDocument()
  })

  it('should call setIsShowAnnotationConfigInit when params button is clicked', () => {
    renderWithProvider({}, {
      annotationReply: {
        enabled: true,
        score_threshold: 0.9,
        embedding_model: {
          embedding_provider_name: 'openai',
          embedding_model_name: 'text-embedding-ada-002',
        },
      },
    })

    const card = screen.getByText(/feature\.annotation\.title/).closest('[class]')!
    fireEvent.mouseEnter(card)
    fireEvent.click(screen.getByText(/operation\.params/))

    expect(mockSetIsShowAnnotationConfigInit).toHaveBeenCalledWith(true)
  })

  it('should navigate to annotations page when cache management is clicked', () => {
    renderWithProvider({}, {
      annotationReply: {
        enabled: true,
        score_threshold: 0.9,
        embedding_model: {
          embedding_provider_name: 'openai',
          embedding_model_name: 'text-embedding-ada-002',
        },
      },
    })

    const card = screen.getByText(/feature\.annotation\.title/).closest('[class]')!
    fireEvent.mouseEnter(card)
    fireEvent.click(screen.getByText(/feature\.annotation\.cacheManagement/))

    expect(mockPush).toHaveBeenCalledWith('/app/test-app-id/annotations')
  })

  it('should fallback appId to empty string when pathname does not match', () => {
    mockPathname = '/apps/no-match'
    renderWithProvider({}, {
      annotationReply: {
        enabled: true,
        score_threshold: 0.9,
        embedding_model: {
          embedding_provider_name: 'openai',
          embedding_model_name: 'text-embedding-ada-002',
        },
      },
    })

    const card = screen.getByText(/feature\.annotation\.title/).closest('[class]')!
    fireEvent.mouseEnter(card)
    fireEvent.click(screen.getByText(/feature\.annotation\.cacheManagement/))

    expect(mockPush).toHaveBeenCalledWith('/app//annotations')
  })

  it('should show config param modal when isShowAnnotationConfigInit is true', async () => {
    mockIsShowAnnotationConfigInit = true
    await act(async () => {
      renderWithProvider()
      await Promise.resolve()
    })

    expect(screen.getByText(/initSetup\.title/)).toBeInTheDocument()
  })

  it('should hide config modal when hide is clicked', async () => {
    mockIsShowAnnotationConfigInit = true
    await act(async () => {
      renderWithProvider()
      await Promise.resolve()
    })

    await act(async () => {
      fireEvent.click(screen.getByRole('button', { name: /operation\.cancel/ }))
      await Promise.resolve()
    })

    expect(mockSetIsShowAnnotationConfigInit).toHaveBeenCalledWith(false)
  })

  it('should call handleEnableAnnotation when config save is clicked', async () => {
    mockIsShowAnnotationConfigInit = true
    renderWithProvider({}, {
      annotationReply: {
        enabled: true,
        score_threshold: 0.9,
        embedding_model: {
          embedding_provider_name: 'openai',
          embedding_model_name: 'text-embedding-ada-002',
        },
      },
    })

    await act(async () => {
      fireEvent.click(screen.getByText(/initSetup\.confirmBtn/))
      await Promise.resolve()
    })

    expect(mockHandleEnableAnnotation).toHaveBeenCalled()
  })

  it('should show annotation full modal when isShowAnnotationFullModal is true', () => {
    mockIsShowAnnotationFullModal = true
    renderWithProvider()

    expect(screen.getByTestId('annotation-full-modal')).toBeInTheDocument()
  })

  it('should hide annotation full modal when hide is clicked', () => {
    mockIsShowAnnotationFullModal = true
    renderWithProvider()

    fireEvent.click(screen.getByTestId('full-hide'))

    expect(mockSetIsShowAnnotationFullModal).toHaveBeenCalledWith(false)
  })

  it('should call handleEnableAnnotation and hide config modal on save', async () => {
    mockIsShowAnnotationConfigInit = true
    renderWithProvider({}, {
      annotationReply: {
        enabled: true,
        score_threshold: 0.9,
        embedding_model: {
          embedding_provider_name: 'openai',
          embedding_model_name: 'text-embedding-ada-002',
        },
      },
    })

    await act(async () => {
      fireEvent.click(screen.getByText(/initSetup\.confirmBtn/))
      await Promise.resolve()
    })

    // handleEnableAnnotation should be called with embedding model and score
    expect(mockHandleEnableAnnotation).toHaveBeenCalledWith(
      { embedding_provider_name: 'openai', embedding_model_name: 'text-embedding-ada-002' },
      0.9,
    )

    // After save resolves, config init should be hidden
    await vi.waitFor(() => {
      expect(mockSetIsShowAnnotationConfigInit).toHaveBeenCalledWith(false)
    })
  })

  it('should update features and call onChange when updateAnnotationReply is invoked', () => {
    const onChange = vi.fn()
    renderWithProvider({ onChange }, {
      annotationReply: {
        enabled: true,
        score_threshold: 0.9,
        embedding_model: {
          embedding_provider_name: 'openai',
          embedding_model_name: 'text-embedding-ada-002',
        },
      },
    })

    // The captured setAnnotationConfig is the component's updateAnnotationReply callback
    expect(capturedSetAnnotationConfig).not.toBeNull()
    act(() => {
      capturedSetAnnotationConfig!({
        enabled: true,
        score_threshold: 0.8,
        embedding_model: {
          embedding_provider_name: 'openai',
          embedding_model_name: 'new-model',
        },
      })
    })

    expect(onChange).toHaveBeenCalled()
  })

  it('should update features without calling onChange when onChange is not provided', () => {
    renderWithProvider({}, {
      annotationReply: {
        enabled: true,
        score_threshold: 0.9,
        embedding_model: {
          embedding_provider_name: 'openai',
          embedding_model_name: 'text-embedding-ada-002',
        },
      },
    })

    // Should not throw when onChange is not provided
    expect(capturedSetAnnotationConfig).not.toBeNull()
    expect(() => act(() => {
      capturedSetAnnotationConfig!({
        enabled: true,
        score_threshold: 0.7,
      })
    })).not.toThrow()
  })

  it('should hide info display when hovering over enabled feature', () => {
    renderWithProvider({}, {
      annotationReply: {
        enabled: true,
        score_threshold: 0.9,
        embedding_model: {
          embedding_provider_name: 'openai',
          embedding_model_name: 'text-embedding-ada-002',
        },
      },
    })

    // Before hover, info is visible
    expect(screen.getByText('0.9')).toBeInTheDocument()

    const card = screen.getByText(/feature\.annotation\.title/).closest('[class]')!
    fireEvent.mouseEnter(card)

    // After hover, buttons shown instead of info
    expect(screen.getByText(/operation\.params/)).toBeInTheDocument()
    expect(screen.queryByText('0.9')).not.toBeInTheDocument()
  })

  it('should show info display again when mouse leaves', () => {
    renderWithProvider({}, {
      annotationReply: {
        enabled: true,
        score_threshold: 0.9,
        embedding_model: {
          embedding_provider_name: 'openai',
          embedding_model_name: 'text-embedding-ada-002',
        },
      },
    })

    const card = screen.getByText(/feature\.annotation\.title/).closest('[class]')!
    fireEvent.mouseEnter(card)
    fireEvent.mouseLeave(card)

    expect(screen.getByText('0.9')).toBeInTheDocument()
  })

  it('should pass isInit prop to ConfigParamModal', async () => {
    mockIsShowAnnotationConfigInit = true
    await act(async () => {
      renderWithProvider()
      await Promise.resolve()
    })

    expect(screen.getByText(/initSetup\.confirmBtn/)).toBeInTheDocument()
    expect(screen.queryByText(/initSetup\.configConfirmBtn/)).not.toBeInTheDocument()
  })

  it('should not show annotation full modal when isShowAnnotationFullModal is false', () => {
    mockIsShowAnnotationFullModal = false
    renderWithProvider()

    expect(screen.queryByTestId('annotation-full-modal')).not.toBeInTheDocument()
  })
})
