import type { App } from '@/types/app'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'
import { trackEvent } from '@/app/components/base/amplitude'
import { ToastContext } from '@/app/components/base/toast/context'

import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context'
import { useRouter } from '@/next/navigation'
import { createApp } from '@/service/apps'
import { AppModeEnum } from '@/types/app'
import { getRedirection } from '@/utils/app-redirection'
import CreateAppModal from './index'

vi.mock('ahooks', () => ({
  useDebounceFn: <T extends (...args: unknown[]) => unknown>(fn: T) => {
    const run = (...args: Parameters<T>) => fn(...args)
    const cancel = vi.fn()
    const flush = vi.fn()
    return { run, cancel, flush }
  },
  useKeyPress: vi.fn(),
  useHover: () => false,
}))
vi.mock('@/next/navigation', () => ({
  useRouter: vi.fn(),
}))
vi.mock('@/app/components/base/amplitude', () => ({
  trackEvent: vi.fn(),
}))
vi.mock('@/service/apps', () => ({
  createApp: vi.fn(),
}))
vi.mock('@/utils/app-redirection', () => ({
  getRedirection: vi.fn(),
}))
vi.mock('@/context/provider-context', () => ({
  useProviderContext: vi.fn(),
}))
vi.mock('@/context/app-context', () => ({
  useAppContext: vi.fn(),
}))
vi.mock('@/context/i18n', () => ({
  useDocLink: () => () => '/guides',
}))
vi.mock('@/hooks/use-theme', () => ({
  default: () => ({ theme: 'light' }),
}))

const mockNotify = vi.fn()
const mockUseRouter = vi.mocked(useRouter)
const mockPush = vi.fn()
const mockCreateApp = vi.mocked(createApp)
const mockTrackEvent = vi.mocked(trackEvent)
const mockGetRedirection = vi.mocked(getRedirection)
const mockUseProviderContext = vi.mocked(useProviderContext)
const mockUseAppContext = vi.mocked(useAppContext)

const defaultPlanUsage = {
  buildApps: 0,
  teamMembers: 0,
  annotatedResponse: 0,
  documentsUploadQuota: 0,
  apiRateLimit: 0,
  triggerEvents: 0,
  vectorSpace: 0,
}

const renderModal = () => {
  const onClose = vi.fn()
  const onSuccess = vi.fn()
  render(
    <ToastContext.Provider value={{ notify: mockNotify, close: vi.fn() }}>
      <CreateAppModal show onClose={onClose} onSuccess={onSuccess} defaultAppMode={AppModeEnum.ADVANCED_CHAT} />
    </ToastContext.Provider>,
  )
  return { onClose, onSuccess }
}

describe('CreateAppModal', () => {
  const mockSetItem = vi.fn()
  const originalLocalStorage = window.localStorage

  beforeEach(() => {
    vi.clearAllMocks()
    mockUseRouter.mockReturnValue({ push: mockPush } as unknown as ReturnType<typeof useRouter>)
    mockUseProviderContext.mockReturnValue({
      plan: {
        type: AppModeEnum.ADVANCED_CHAT,
        usage: defaultPlanUsage,
        total: { ...defaultPlanUsage, buildApps: 1 },
        reset: {},
      },
      enableBilling: true,
    } as unknown as ReturnType<typeof useProviderContext>)
    mockUseAppContext.mockReturnValue({
      isCurrentWorkspaceEditor: true,
    } as unknown as ReturnType<typeof useAppContext>)
    mockSetItem.mockClear()
    Object.defineProperty(window, 'localStorage', {
      value: {
        setItem: mockSetItem,
        getItem: vi.fn(),
        removeItem: vi.fn(),
        clear: vi.fn(),
        key: vi.fn(),
        length: 0,
      },
      writable: true,
    })
  })

  afterAll(() => {
    Object.defineProperty(window, 'localStorage', {
      value: originalLocalStorage,
      writable: true,
    })
  })

  it('creates an app, notifies success, and fires callbacks', async () => {
    const mockApp: Partial<App> = { id: 'app-1', mode: AppModeEnum.ADVANCED_CHAT }
    mockCreateApp.mockResolvedValue(mockApp as App)
    const { onClose, onSuccess } = renderModal()

    const nameInput = screen.getByPlaceholderText('app.newApp.appNamePlaceholder')
    fireEvent.change(nameInput, { target: { value: 'My App' } })
    fireEvent.click(screen.getByRole('button', { name: /app\.newApp\.Create/ }))

    await waitFor(() => expect(mockCreateApp).toHaveBeenCalledWith({
      name: 'My App',
      description: '',
      icon_type: 'emoji',
      icon: '🤖',
      icon_background: '#FFEAD5',
      mode: AppModeEnum.ADVANCED_CHAT,
    }))

    expect(mockTrackEvent).toHaveBeenCalledWith('create_app', {
      app_mode: AppModeEnum.ADVANCED_CHAT,
      description: '',
    })
    expect(mockNotify).toHaveBeenCalledWith({ type: 'success', message: 'app.newApp.appCreated' })
    expect(onSuccess).toHaveBeenCalled()
    expect(onClose).toHaveBeenCalled()
    await waitFor(() => expect(mockSetItem).toHaveBeenCalledWith(NEED_REFRESH_APP_LIST_KEY, '1'))
    await waitFor(() => expect(mockGetRedirection).toHaveBeenCalledWith(true, mockApp, mockPush))
  })

  it('shows error toast when creation fails', async () => {
    mockCreateApp.mockRejectedValue(new Error('boom'))
    const { onClose } = renderModal()

    const nameInput = screen.getByPlaceholderText('app.newApp.appNamePlaceholder')
    fireEvent.change(nameInput, { target: { value: 'My App' } })
    fireEvent.click(screen.getByRole('button', { name: /app\.newApp\.Create/ }))

    await waitFor(() => expect(mockCreateApp).toHaveBeenCalled())
    expect(mockNotify).toHaveBeenCalledWith({ type: 'error', message: 'boom' })
    expect(onClose).not.toHaveBeenCalled()
  })
})
