import { render, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { message } from 'antd'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

import type { ImportUsersResult } from '@/types/import-export'
import { ImportExportPage } from './ImportExportPage'

const downloadImportTemplateMock = vi.fn<(format: 'csv' | 'xlsx') => Promise<void>>()
const exportUsersMock = vi.fn<(payload: unknown) => Promise<void>>()
const importUsersMock = vi.fn<(file: File) => Promise<ImportUsersResult>>()
const realGetComputedStyle = window.getComputedStyle.bind(window)

vi.mock('@/services/import-export', () => ({
  downloadImportTemplate: (format: 'csv' | 'xlsx') => downloadImportTemplateMock(format),
  exportUsers: (payload: unknown) => exportUsersMock(payload),
  importUsers: (file: File) => importUsersMock(file),
}))

function getUploadInput() {
  const uploadInput = document.querySelector('input[type="file"]')

  expect(uploadInput).not.toBeNull()

  return uploadInput as HTMLInputElement
}

describe('ImportExportPage', () => {
  beforeEach(() => {
    downloadImportTemplateMock.mockReset()
    exportUsersMock.mockReset()
    importUsersMock.mockReset()

    vi.spyOn(message, 'success').mockImplementation(() => undefined as never)
    vi.spyOn(message, 'error').mockImplementation(() => undefined as never)
    vi.spyOn(window, 'getComputedStyle').mockImplementation((element) => realGetComputedStyle(element))
  })

  afterEach(() => {
    vi.restoreAllMocks()
  })

  it('downloads the default import template format', async () => {
    const user = userEvent.setup()
    downloadImportTemplateMock.mockResolvedValue(undefined)

    render(<ImportExportPage />)
    const importPanel = screen.getByRole('tabpanel', { name: /导入用户/ })

    await user.click(within(importPanel).getByRole('button', { name: /下载模板/ }))

    await waitFor(() => expect(downloadImportTemplateMock).toHaveBeenCalledWith('csv'))
    expect(message.success).toHaveBeenCalledTimes(1)
  })

  it('switches the import format and surfaces template download failures', async () => {
    const user = userEvent.setup()
    downloadImportTemplateMock.mockRejectedValue(new Error('template failed'))

    render(<ImportExportPage />)
    const importPanel = screen.getByRole('tabpanel', { name: /导入用户/ })

    await user.click(within(importPanel).getByRole('radio', { name: 'Excel (.xlsx)' }))
    await user.click(within(importPanel).getByRole('button', { name: /下载模板/ }))

    await waitFor(() => expect(downloadImportTemplateMock).toHaveBeenCalledWith('xlsx'))
    await waitFor(() => expect(message.error).toHaveBeenCalledWith('template failed'))
  })

  it('rejects unsupported and oversized import files before hitting the service', async () => {
    const user = userEvent.setup({ applyAccept: false })

    render(<ImportExportPage />)

    await user.upload(getUploadInput(), new File(['hello'], 'users.txt', { type: 'text/plain' }))
    await waitFor(() => expect(message.error).toHaveBeenCalledTimes(1))

    const oversizedFile = new File([new Uint8Array(11 * 1024 * 1024)], 'users.csv', { type: 'text/csv' })
    await user.upload(getUploadInput(), oversizedFile)

    await waitFor(() => expect(message.error).toHaveBeenCalledTimes(2))
    expect(importUsersMock).not.toHaveBeenCalled()
  })

  it('surfaces backend import failures for supported files', async () => {
    const user = userEvent.setup()
    importUsersMock.mockRejectedValue(new Error('import failed'))

    render(<ImportExportPage />)

    const csvFile = new File(['username,email'], 'users.csv', { type: 'text/csv' })
    await user.upload(getUploadInput(), csvFile)

    await waitFor(() => expect(importUsersMock).toHaveBeenCalledTimes(1))
    await waitFor(() => expect(message.error).toHaveBeenCalledWith('import failed'))
  })

  it('submits a csv import and renders a success result summary', async () => {
    const user = userEvent.setup()
    importUsersMock.mockResolvedValue({
      success_count: 2,
      fail_count: 0,
      errors: [],
      message: '导入完成',
    })

    render(<ImportExportPage />)

    const csvFile = new File(['username,email'], 'users.csv', { type: 'text/csv' })
    await user.upload(getUploadInput(), csvFile)

    await waitFor(() => expect(importUsersMock).toHaveBeenCalledTimes(1))
    expect(await screen.findByText('成功 2 条，失败 0 条')).toBeInTheDocument()
    expect(document.querySelector('.ant-alert-success')).not.toBeNull()
  })

  it('submits an xlsx import, renders warning details, and resets the import flow', async () => {
    const user = userEvent.setup()
    importUsersMock.mockResolvedValue({
      success_count: 1,
      fail_count: 2,
      errors: ['row 2 invalid email', 'row 3 duplicate username'],
      message: '',
    })

    render(<ImportExportPage />)

    const xlsxFile = new File(['binary'], 'users.xlsx', {
      type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    })
    await user.upload(getUploadInput(), xlsxFile)

    await waitFor(() => expect(importUsersMock).toHaveBeenCalledTimes(1))
    expect(await screen.findByText('row 2 invalid email')).toBeInTheDocument()
    expect(screen.getByText('row 3 duplicate username')).toBeInTheDocument()
    expect(document.querySelector('.ant-alert-warning')).not.toBeNull()
    expect(message.success).toHaveBeenCalledTimes(1)

    await user.click(screen.getByRole('button', { name: '继续导入' }))

    expect(screen.queryByText('row 2 invalid email')).not.toBeInTheDocument()
    expect(screen.getByRole('button', { name: /下载模板/ })).toBeInTheDocument()
  })

  it('requires at least one export field and submits the selected export payload', async () => {
    const user = userEvent.setup()
    exportUsersMock.mockResolvedValue(undefined)

    render(<ImportExportPage />)

    await user.click(screen.getByRole('tab', { name: /导出用户/ }))
    const exportPanel = screen.getByRole('tabpanel', { name: /导出用户/ })

    await user.click(within(exportPanel).getByRole('button', { name: '清空' }))
    await user.click(within(exportPanel).getByRole('button', { name: /导出用户数据/ }))

    expect(exportUsersMock).not.toHaveBeenCalled()
    expect(message.error).toHaveBeenCalledTimes(1)

    await user.click(within(exportPanel).getByRole('button', { name: '全选' }))
    await user.type(within(exportPanel).getByRole('textbox'), 'alice')
    await user.click(within(exportPanel).getByRole('radio', { name: /CSV \(\.csv\)/ }))
    await user.click(within(exportPanel).getByRole('button', { name: /导出用户数据/ }))

    await waitFor(() => expect(exportUsersMock).toHaveBeenCalledWith({
      format: 'csv',
      fields: ['id', 'username', 'email', 'phone', 'nickname', 'status', 'totp_enabled', 'last_login_time', 'created_at'],
      keyword: 'alice',
      status: undefined,
    }))
    expect(message.success).toHaveBeenCalledTimes(1)
  })

  it('updates export field selection and surfaces export failures with an empty keyword', async () => {
    const user = userEvent.setup()
    exportUsersMock.mockRejectedValue(new Error('export failed'))

    render(<ImportExportPage />)

    await user.click(screen.getByRole('tab', { name: /导出用户/ }))
    const exportPanel = screen.getByRole('tabpanel', { name: /导出用户/ })

    await user.click(within(exportPanel).getByRole('checkbox', { name: /ID/ }))
    await user.click(within(exportPanel).getByRole('button', { name: /导出用户数据/ }))

    await waitFor(() => expect(exportUsersMock).toHaveBeenCalledWith({
      format: 'xlsx',
      fields: ['username', 'email', 'phone', 'status', 'created_at'],
      keyword: undefined,
      status: undefined,
    }))
    await waitFor(() => expect(message.error).toHaveBeenCalledWith('export failed'))
  })
})
