import type { ReactNode } from 'react'
import type { ModalContextState } from '@/context/modal-context'
import type { ProviderContextState } from '@/context/provider-context'
import type { AppDetailResponse } from '@/models/app'
import type { AppSSO } from '@/types/app'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { Plan } from '@/app/components/billing/type'
import { baseProviderContextValue } from '@/context/provider-context'
import { AppModeEnum } from '@/types/app'
import SettingsModal from './index'

vi.mock('react-i18next', async () => {
  const actual = await vi.importActual<typeof import('react-i18next')>('react-i18next')
  return {
    ...actual,
    useTranslation: () => ({
      t: (key: string, options?: Record<string, unknown>) => {
        if (options?.returnObjects)
          return [`${key}-feature-1`, `${key}-feature-2`]
        if (options)
          return `${key}:${JSON.stringify(options)}`
        return key
      },
      i18n: {
        language: 'en',
        changeLanguage: vi.fn(),
      },
    }),
    Trans: ({ children }: { children?: ReactNode }) => <>{children}</>,
  }
})

const mockNotify = vi.fn()
const mockOnClose = vi.fn()
const mockOnSave = vi.fn()
const mockSetShowPricingModal = vi.fn()
const mockSetShowAccountSettingModal = vi.fn()
const mockUseProviderContext = vi.fn<() => ProviderContextState>()

const buildModalContext = (): ModalContextState => ({
  setShowAccountSettingModal: mockSetShowAccountSettingModal,
  setShowApiBasedExtensionModal: vi.fn(),
  setShowModerationSettingModal: vi.fn(),
  setShowExternalDataToolModal: vi.fn(),
  setShowPricingModal: mockSetShowPricingModal,
  setShowAnnotationFullModal: vi.fn(),
  setShowModelModal: vi.fn(),
  setShowExternalKnowledgeAPIModal: vi.fn(),
  setShowModelLoadBalancingModal: vi.fn(),
  setShowOpeningModal: vi.fn(),
  setShowUpdatePluginModal: vi.fn(),
  setShowEducationExpireNoticeModal: vi.fn(),
  setShowTriggerEventsLimitModal: vi.fn(),
})

vi.mock('@/context/modal-context', () => ({
  useModalContext: () => buildModalContext(),
}))

vi.mock('@/app/components/base/toast', async () => {
  const actual = await vi.importActual<typeof import('@/app/components/base/toast')>('@/app/components/base/toast')
  return {
    ...actual,
    useToastContext: () => ({
      notify: mockNotify,
      close: vi.fn(),
    }),
  }
})

vi.mock('@/context/i18n', async () => {
  const actual = await vi.importActual<typeof import('@/context/i18n')>('@/context/i18n')
  return {
    ...actual,
    useDocLink: () => (path?: string) => `https://docs.example.com${path ?? ''}`,
  }
})

vi.mock('@/context/provider-context', async () => {
  const actual = await vi.importActual<typeof import('@/context/provider-context')>('@/context/provider-context')
  return {
    ...actual,
    useProviderContext: () => mockUseProviderContext(),
  }
})

const mockAppInfo = {
  site: {
    title: 'Test App',
    icon_type: 'emoji',
    icon: '😀',
    icon_background: '#ABCDEF',
    icon_url: 'https://example.com/icon.png',
    description: 'A description',
    chat_color_theme: '#123456',
    chat_color_theme_inverted: true,
    copyright: '© Dify',
    privacy_policy: '',
    custom_disclaimer: 'Disclaimer',
    default_language: 'en-US',
    show_workflow_steps: true,
    use_icon_as_answer_icon: true,
  },
  mode: AppModeEnum.ADVANCED_CHAT,
  enable_sso: false,
} as unknown as AppDetailResponse & Partial<AppSSO>

const renderSettingsModal = () => render(
  <SettingsModal
    isChat
    isShow
    appInfo={mockAppInfo}
    onClose={mockOnClose}
    onSave={mockOnSave}
  />,
)

describe('SettingsModal', () => {
  beforeEach(() => {
    mockNotify.mockClear()
    mockOnClose.mockClear()
    mockOnSave.mockClear()
    mockSetShowPricingModal.mockClear()
    mockSetShowAccountSettingModal.mockClear()
    mockUseProviderContext.mockReturnValue({
      ...baseProviderContextValue,
      enableBilling: true,
      plan: {
        ...baseProviderContextValue.plan,
        type: Plan.sandbox,
      },
      webappCopyrightEnabled: true,
    })
  })

  it('should render the modal and expose the expanded settings section', async () => {
    renderSettingsModal()
    expect(screen.getByText('appOverview.overview.appInfo.settings.title')).toBeInTheDocument()

    const showMoreEntry = screen.getByText('appOverview.overview.appInfo.settings.more.entry')
    fireEvent.click(showMoreEntry)

    await waitFor(() => {
      expect(screen.getByPlaceholderText('appOverview.overview.appInfo.settings.more.copyRightPlaceholder')).toBeInTheDocument()
      expect(screen.getByPlaceholderText('appOverview.overview.appInfo.settings.more.privacyPolicyPlaceholder')).toBeInTheDocument()
    })
  })

  it('should notify the user when the name is empty', async () => {
    renderSettingsModal()
    const nameInput = screen.getByPlaceholderText('app.appNamePlaceholder')
    fireEvent.change(nameInput, { target: { value: '' } })
    fireEvent.click(screen.getByText('common.operation.save'))

    await waitFor(() => {
      expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ message: 'app.newApp.nameNotEmpty' }))
    })
    expect(mockOnSave).not.toHaveBeenCalled()
  })

  it('should validate the theme color and show an error when the hex is invalid', async () => {
    renderSettingsModal()
    const colorInput = screen.getByPlaceholderText('E.g #A020F0')
    fireEvent.change(colorInput, { target: { value: 'not-a-hex' } })

    fireEvent.click(screen.getByText('common.operation.save'))
    await waitFor(() => {
      expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({
        message: 'appOverview.overview.appInfo.settings.invalidHexMessage',
      }))
    })
    expect(mockOnSave).not.toHaveBeenCalled()
  })

  it('should validate the privacy policy URL when advanced settings are open', async () => {
    renderSettingsModal()
    fireEvent.click(screen.getByText('appOverview.overview.appInfo.settings.more.entry'))
    const privacyInput = screen.getByPlaceholderText('appOverview.overview.appInfo.settings.more.privacyPolicyPlaceholder')
    // eslint-disable-next-line sonarjs/no-clear-text-protocols
    fireEvent.change(privacyInput, { target: { value: 'ftp://invalid-url' } })

    fireEvent.click(screen.getByText('common.operation.save'))
    await waitFor(() => {
      expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({
        message: 'appOverview.overview.appInfo.settings.invalidPrivacyPolicy',
      }))
    })
    expect(mockOnSave).not.toHaveBeenCalled()
  })

  it('should save valid settings and close the modal', async () => {
    mockOnSave.mockResolvedValueOnce(undefined)
    renderSettingsModal()

    fireEvent.click(screen.getByText('common.operation.save'))

    await waitFor(() => expect(mockOnSave).toHaveBeenCalled())
    expect(mockOnSave).toHaveBeenCalledWith(expect.objectContaining({
      title: mockAppInfo.site.title,
      description: mockAppInfo.site.description,
      default_language: mockAppInfo.site.default_language,
      chat_color_theme: mockAppInfo.site.chat_color_theme,
      chat_color_theme_inverted: mockAppInfo.site.chat_color_theme_inverted,
      prompt_public: false,
      copyright: mockAppInfo.site.copyright,
      privacy_policy: mockAppInfo.site.privacy_policy,
      custom_disclaimer: mockAppInfo.site.custom_disclaimer,
      icon_type: 'emoji',
      icon: mockAppInfo.site.icon,
      icon_background: mockAppInfo.site.icon_background,
      show_workflow_steps: mockAppInfo.site.show_workflow_steps,
      use_icon_as_answer_icon: mockAppInfo.site.use_icon_as_answer_icon,
      enable_sso: mockAppInfo.enable_sso,
    }))
    expect(mockOnClose).toHaveBeenCalled()
  })
})
