import type { PluginProvider } from '@/models/common'
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { useAppContext } from '@/context/app-context'
import SerpapiPlugin from '../SerpapiPlugin'
import { updatePluginKey, validatePluginKey } from '../utils'

const mockEventEmitter = vi.hoisted(() => {
  let subscriber: ((value: string) => void) | undefined
  return {
    useSubscription: vi.fn((callback: (value: string) => void) => {
      subscriber = callback
    }),
    emit: vi.fn((value: string) => {
      subscriber?.(value)
    }),
    reset: () => {
      subscriber = undefined
    },
  }
})

const { mockToast } = vi.hoisted(() => {
  const mockToast = Object.assign(vi.fn(), {
    success: vi.fn(),
    error: vi.fn(),
    warning: vi.fn(),
    info: vi.fn(),
    dismiss: vi.fn(),
    update: vi.fn(),
    promise: vi.fn(),
  })
  return { mockToast }
})

vi.mock('@langgenius/dify-ui/toast', () => ({
  toast: mockToast,
}))

vi.mock('@/context/app-context', () => ({
  useAppContext: vi.fn(),
}))

vi.mock('../utils', () => ({
  updatePluginKey: vi.fn(),
  validatePluginKey: vi.fn(),
}))

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

describe('SerpapiPlugin', () => {
  const mockOnUpdate = vi.fn()
  const mockUpdatePluginKey = updatePluginKey as ReturnType<typeof vi.fn>
  const mockValidatePluginKey = validatePluginKey as ReturnType<typeof vi.fn>

  beforeEach(() => {
    vi.clearAllMocks()
    mockEventEmitter.reset()
    const mockUseAppContext = useAppContext as ReturnType<typeof vi.fn>
    mockUseAppContext.mockReturnValue({
      isCurrentWorkspaceManager: true,
    })
    mockValidatePluginKey.mockResolvedValue({ status: 'success' })
    mockUpdatePluginKey.mockResolvedValue({ status: 'success' })
  })

  it('should show key input when manager clicks edit key', () => {
    const mockPlugin: PluginProvider = {
      tool_name: 'serpapi',
      credentials: {
        api_key: 'existing-key',
      },
    } as PluginProvider

    render(<SerpapiPlugin plugin={mockPlugin} onUpdate={mockOnUpdate} />)

    fireEvent.click(screen.getByText('common.provider.editKey'))
    expect(screen.getByPlaceholderText('common.plugin.serpapi.apiKeyPlaceholder')).toBeInTheDocument()
  })

  it('should clear existing key on focus and show validation error for invalid key', async () => {
    vi.useFakeTimers()
    try {
      mockValidatePluginKey.mockResolvedValue({ status: 'error', message: 'Invalid API key' })

      const mockPlugin: PluginProvider = {
        tool_name: 'serpapi',
        credentials: {
          api_key: 'existing-key',
        },
      } as PluginProvider

      render(<SerpapiPlugin plugin={mockPlugin} onUpdate={mockOnUpdate} />)

      fireEvent.click(screen.getByText('common.provider.editKey'))
      const input = screen.getByPlaceholderText('common.plugin.serpapi.apiKeyPlaceholder')

      expect(input).toHaveValue('existing-key')
      fireEvent.focus(input)
      expect(input).toHaveValue('')

      fireEvent.change(input, {
        target: { value: 'invalid-key' },
      })

      await act(async () => {
        await vi.advanceTimersByTimeAsync(1000)
      })

      expect(screen.getByText(/Invalid API key/)).toBeInTheDocument()

      fireEvent.focus(input)
      expect(input).toHaveValue('invalid-key')

      fireEvent.change(input, {
        target: { value: '' },
      })

      await act(async () => {
        await vi.advanceTimersByTimeAsync(1000)
      })

      expect(screen.queryByText(/Invalid API key/)).toBeNull()
    }
    finally {
      vi.useRealTimers()
    }
  })

  it('should not open key input when user is not workspace manager', () => {
    const mockUseAppContext = useAppContext as ReturnType<typeof vi.fn>
    mockUseAppContext.mockReturnValue({
      isCurrentWorkspaceManager: false,
    })

    const mockPlugin = {
      tool_name: 'serpapi',
      is_enabled: true,
      credentials: null,
    } satisfies PluginProvider

    render(<SerpapiPlugin plugin={mockPlugin} onUpdate={mockOnUpdate} />)

    fireEvent.click(screen.getByText('common.provider.addKey'))

    expect(screen.queryByPlaceholderText('common.plugin.serpapi.apiKeyPlaceholder')).toBeNull()
  })

  it('should save changed key and trigger success feedback', async () => {
    const mockPlugin: PluginProvider = {
      tool_name: 'serpapi',
      credentials: {
        api_key: 'existing-key',
      },
    } as PluginProvider

    render(<SerpapiPlugin plugin={mockPlugin} onUpdate={mockOnUpdate} />)

    fireEvent.click(screen.getByText('common.provider.editKey'))
    fireEvent.change(screen.getByPlaceholderText('common.plugin.serpapi.apiKeyPlaceholder'), {
      target: { value: 'new-key' },
    })
    fireEvent.click(screen.getByText('common.operation.save'))

    await waitFor(() => {
      expect(screen.queryByPlaceholderText('common.plugin.serpapi.apiKeyPlaceholder')).toBeNull()
    })
  })

  it('should keep editor open when save request fails', async () => {
    mockUpdatePluginKey.mockResolvedValue({ status: 'error', message: 'update failed' })

    const mockPlugin: PluginProvider = {
      tool_name: 'serpapi',
      credentials: {
        api_key: 'existing-key',
      },
    } as PluginProvider

    render(<SerpapiPlugin plugin={mockPlugin} onUpdate={mockOnUpdate} />)

    fireEvent.click(screen.getByText('common.provider.editKey'))
    fireEvent.change(screen.getByPlaceholderText('common.plugin.serpapi.apiKeyPlaceholder'), {
      target: { value: 'new-key' },
    })
    fireEvent.click(screen.getByText('common.operation.save'))

    await waitFor(() => {
      expect(screen.getByPlaceholderText('common.plugin.serpapi.apiKeyPlaceholder')).toBeInTheDocument()
    })
  })

  it('should keep editor open when key value is unchanged', async () => {
    const mockPlugin: PluginProvider = {
      tool_name: 'serpapi',
      credentials: {
        api_key: 'existing-key',
      },
    } as PluginProvider

    render(<SerpapiPlugin plugin={mockPlugin} onUpdate={mockOnUpdate} />)

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

    await waitFor(() => {
      expect(screen.getByPlaceholderText('common.plugin.serpapi.apiKeyPlaceholder')).toBeInTheDocument()
    })
  })
})
