/**
 * 额度支付流程集成测试
 * 测试完整额度支付流程
 */

import { render, screen, waitFor, fireEvent } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import PaymentPage from '@/pages/payment/index'
import { creditBalanceClient, paymentClient } from '@/api'
import { mockUseRouter, mockNavigateTo, mockShowToast, mockRedirectTo } from '~/__mocks__/taroMock'

// @tarojs/taro 已经在 jest.config.js 中通过 moduleNameMapper 重定向到 mock 文件
// 不需要额外 mock

// Mock API客户端
jest.mock('@/api', () => ({
  creditBalanceClient: {
    me: {
      $get: jest.fn(),
    },
    payment: {
      $post: jest.fn(),
    },
  },
  paymentClient: {
    payment: {
      $post: jest.fn(),
    },
  },
}))

// Mock 支付工具函数
jest.mock('@/utils/payment', () => ({
  requestWechatPayment: jest.fn(),
  PaymentStatus: {
    PENDING: 'pending',
    PROCESSING: 'processing',
    SUCCESS: 'success',
    FAILED: 'failed',
  },
  PaymentStateManager: {
    getInstance: jest.fn(() => ({
      setPaymentState: jest.fn(),
      clearPaymentState: jest.fn(),
    })),
  },
  PaymentRateLimiter: {
    getInstance: jest.fn(() => ({
      isRateLimited: jest.fn(() => ({ limited: false })),
      recordAttempt: jest.fn(),
      clearAttempts: jest.fn(),
    })),
  },
  retryPayment: jest.fn(),
}))

// 创建测试QueryClient
const createTestQueryClient = () => new QueryClient({
  defaultOptions: {
    queries: { retry: false },
    mutations: { retry: false },
  },
})

// 测试包装器
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
  <QueryClientProvider client={createTestQueryClient()}>
    {children}
  </QueryClientProvider>
)

// 测试数据工厂
const createTestCreditBalance = (overrides = {}) => ({
  totalLimit: 1000,
  usedAmount: 200,
  availableAmount: 800,
  isEnabled: true,
  ...overrides,
})

const createTestPaymentData = () => ({
  timeStamp: '1234567890',
  nonceStr: 'test-nonce',
  package: 'prepay_id=test_prepay_id',
  signType: 'MD5',
  paySign: 'test-sign',
})

describe('额度支付流程集成测试', () => {
  beforeEach(() => {
    jest.clearAllMocks()
    jest.useFakeTimers()

    // 设置默认路由参数
    mockUseRouter.mockReturnValue({
      params: {
        orderId: '123',
        amount: '100',
        orderNo: 'ORD123456',
      },
    })
  })

  afterEach(() => {
    jest.useRealTimers()
  })

  test('完整额度支付流程：从选择到支付成功', async () => {
    // Mock 额度查询返回正常数据
    const initialBalance = createTestCreditBalance({ availableAmount: 800 })
    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
      status: 200,
      json: () => Promise.resolve(initialBalance),
    })

    // Mock 额度支付成功
    const updatedBalance = createTestCreditBalance({
      usedAmount: 300,  // 原200 + 支付100 = 300
      availableAmount: 700  // 原800 - 支付100 = 700
    })
    ;(creditBalanceClient.payment.$post as jest.Mock).mockResolvedValue({
      status: 200,
      json: () => Promise.resolve(updatedBalance),
    })

    render(
      <TestWrapper>
        <PaymentPage />
      </TestWrapper>
    )

    // 1. 验证页面加载和额度显示
    await waitFor(() => {
      expect(screen.getByTestId('payment-page-title')).toBeInTheDocument()
      expect(screen.getByTestId('available-amount-text')).toHaveTextContent(/使用信用额度支付/)
    })

    // 2. 选择额度支付方式
    const creditOption = screen.getByTestId('credit-payment-option')
    fireEvent.click(creditOption)

    await waitFor(() => {
      expect(creditOption).toHaveClass('border-blue-500')
      expect(screen.getByTestId('pay-button')).toHaveTextContent('额度支付 ¥100.00')
    })

    // 3. 验证额度详情显示
    const creditDetails = screen.getByTestId('credit-payment-details')
    expect(creditDetails).toHaveTextContent(/使用信用额度支付，无需立即付款/)
    // 不应该包含额度详情
    expect(creditDetails).not.toHaveTextContent(/可用额度:/)
    expect(creditDetails).not.toHaveTextContent(/总额度:/)
    expect(creditDetails).not.toHaveTextContent(/已用额度:/)

    // 4. 点击支付按钮
    const payButton = screen.getByTestId('pay-button')
    fireEvent.click(payButton)

    // 5. 验证支付处理中状态
    await waitFor(() => {
      expect(screen.getByText('支付中...')).toBeInTheDocument()
    })

    // 6. 验证调用了额度支付API
    await waitFor(() => {
      expect(creditBalanceClient.payment.$post).toHaveBeenCalledWith({
        json: {
          referenceId: '123', // 传递订单ID而不是订单号
          remark: '订单支付 - ORD123456',
        },
      })
    })

    // 7. 验证支付成功状态
    await waitFor(() => {
      // 使用类名选择器找到支付成功状态文本
      expect(screen.getByText('支付成功', { selector: 'span.text-xl' })).toBeInTheDocument()
    })

    // 8. 推进时间以触发跳转
    jest.advanceTimersByTime(1600)

    // 9. 验证跳转到成功页面
    await waitFor(() => {
      expect(mockRedirectTo).toHaveBeenCalledWith({
        url: '/pages/payment-success/index?orderId=123&amount=100&paymentMethod=credit',
      })
    })
  })

  test('额度支付失败流程：显示错误并可以重试', async () => {
    // Mock 额度查询返回正常数据
    const initialBalance = createTestCreditBalance({ availableAmount: 800 })
    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
      status: 200,
      json: () => Promise.resolve(initialBalance),
    })

    // Mock 额度支付第一次失败，第二次成功
    let paymentCallCount = 0
    ;(creditBalanceClient.payment.$post as jest.Mock).mockImplementation(() => {
      paymentCallCount++
      if (paymentCallCount === 1) {
        return Promise.resolve({
          status: 400,
          json: () => Promise.resolve({ message: '额度支付失败，请重试' }),
        })
      } else {
        return Promise.resolve({
          status: 200,
          json: () => Promise.resolve(createTestCreditBalance({ usedAmount: 300, availableAmount: 700 })),
        })
      }
    })

    render(
      <TestWrapper>
        <PaymentPage />
      </TestWrapper>
    )

    // 等待页面加载
    await waitFor(() => {
      expect(screen.getByTestId('available-amount-text')).toHaveTextContent(/使用信用额度支付/)
    })

    // 选择额度支付
    const creditOption = screen.getByTestId('credit-payment-option')
    fireEvent.click(creditOption)

    // 点击支付按钮（第一次失败）
    const payButton = screen.getByTestId('pay-button')
    fireEvent.click(payButton)

    // 验证显示错误信息
    await waitFor(() => {
      expect(screen.getByText('额度支付失败，请重试')).toBeInTheDocument()
      expect(screen.getByText('重试支付')).toBeInTheDocument()
    })

    // 点击重试按钮
    const retryButton = screen.getByText('重试支付')
    fireEvent.click(retryButton)

    // 验证第二次支付成功
    await waitFor(() => {
      // 使用更精确的选择器，避免多个"支付成功"元素
      expect(screen.getByText('支付成功', { selector: 'span.text-xl' })).toBeInTheDocument()
    })

    // 推进时间以触发跳转
    jest.advanceTimersByTime(1600)

    await waitFor(() => {
      expect(mockRedirectTo).toHaveBeenCalledWith({
        url: '/pages/payment-success/index?orderId=123&amount=100&paymentMethod=credit',
      })
    })
  })

  test('额度不足时的支付流程', async () => {
    // Mock 额度查询返回额度不足的数据
    const initialBalance = createTestCreditBalance({
      totalLimit: 50,
      usedAmount: 45,
      availableAmount: 5  // 可用额度5元，支付金额100元
    })
    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
      status: 200,
      json: () => Promise.resolve(initialBalance),
    })

    render(
      <TestWrapper>
        <PaymentPage />
      </TestWrapper>
    )

    // 等待页面加载
    await waitFor(() => {
      // 额度不足时，额度支付选项不应该显示
      expect(screen.queryByTestId('credit-payment-option')).not.toBeInTheDocument()
    })

    // 验证支付按钮没有被禁用（因为默认选择微信支付）
    const payButton = screen.getByTestId('pay-button')
    expect(payButton).not.toBeDisabled()
  })

  test('额度为0时的支付流程', async () => {
    // Mock 额度查询返回额度为0的数据
    const initialBalance = createTestCreditBalance({
      totalLimit: 0,
      usedAmount: 0,
      availableAmount: 0,
      isEnabled: false,
    })
    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
      status: 200,
      json: () => Promise.resolve(initialBalance),
    })

    render(
      <TestWrapper>
        <PaymentPage />
      </TestWrapper>
    )

    // 等待页面加载
    await waitFor(() => {
      expect(screen.getByTestId('payment-page-title')).toBeInTheDocument()
    })

    // 验证额度支付选项不显示（因为额度为0且未启用）
    expect(screen.queryByTestId('credit-payment-option')).not.toBeInTheDocument()

    // 验证支付按钮没有被禁用（因为默认选择微信支付）
    const payButton = screen.getByTestId('pay-button')
    expect(payButton).not.toBeDisabled()
  })

  test('额度支付与微信支付切换流程', async () => {
    // Mock 额度查询返回正常数据
    const initialBalance = createTestCreditBalance({ availableAmount: 800 })
    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
      status: 200,
      json: () => Promise.resolve(initialBalance),
    })

    // Mock 微信支付参数
    const mockPaymentData = createTestPaymentData()
    ;(paymentClient.payment.$post as jest.Mock).mockResolvedValue({
      status: 200,
      json: () => Promise.resolve(mockPaymentData),
    })

    render(
      <TestWrapper>
        <PaymentPage />
      </TestWrapper>
    )

    // 等待页面加载
    await waitFor(() => {
      expect(screen.getByTestId('available-amount-text')).toHaveTextContent(/使用信用额度支付/)
    })

    // 初始为微信支付选中
    const wechatOption = screen.getByTestId('wechat-payment-option')
    expect(wechatOption).toHaveClass('border-blue-500')
    expect(screen.getByText('微信支付 ¥100.00')).toBeInTheDocument()

    // 切换到额度支付
    const creditOption = screen.getByTestId('credit-payment-option')
    fireEvent.click(creditOption)

    await waitFor(() => {
      expect(creditOption).toHaveClass('border-blue-500')
      expect(screen.getByTestId('pay-button')).toHaveTextContent('额度支付 ¥100.00')
    })

    // 验证额度详情显示
    expect(screen.getByText('• 使用信用额度支付，无需立即付款')).toBeInTheDocument()

    // 切换回微信支付
    fireEvent.click(wechatOption)

    await waitFor(() => {
      expect(wechatOption).toHaveClass('border-blue-500')
      expect(screen.getByTestId('pay-button')).toHaveTextContent('微信支付 ¥100.00')
    })

    // 验证额度详情隐藏
    expect(screen.queryByText('• 使用信用额度支付，无需立即付款')).not.toBeInTheDocument()
  })

  test('网络异常时的降级处理', async () => {
    // Mock 额度查询网络异常
    ;(creditBalanceClient.me.$get as jest.Mock).mockRejectedValue(new Error('网络连接失败'))

    render(
      <TestWrapper>
        <PaymentPage />
      </TestWrapper>
    )

    // 等待页面加载（即使额度查询失败，页面也应该显示）
    await waitFor(() => {
      // 使用data-testid查询支付页面标题
      expect(screen.getByTestId('payment-page-title')).toBeInTheDocument()
    })

    // 验证额度支付选项不显示（因为查询失败）
    // 当额度查询失败时，额度支付选项不应该显示
    expect(screen.queryByTestId('credit-payment-option')).not.toBeInTheDocument()

    // 验证支付按钮没有被禁用（因为默认选择微信支付，额度查询失败不影响微信支付）
    const payButton = screen.getByTestId('pay-button')
    expect(payButton).not.toBeDisabled()
  })

  test('支付过程中的取消操作', async () => {
    // Mock 额度查询返回正常数据
    const initialBalance = createTestCreditBalance({ availableAmount: 800 })
    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
      status: 200,
      json: () => Promise.resolve(initialBalance),
    })

    // Mock 额度支付延迟（模拟用户取消）
    let resolvePayment: any
    const paymentPromise = new Promise((resolve) => {
      resolvePayment = resolve
    })
    ;(creditBalanceClient.payment.$post as jest.Mock).mockReturnValue(paymentPromise)

    render(
      <TestWrapper>
        <PaymentPage />
      </TestWrapper>
    )

    // 等待页面加载
    await waitFor(() => {
      expect(screen.getByTestId('available-amount-text')).toHaveTextContent(/使用信用额度支付/)
    })

    // 选择额度支付
    const creditOption = screen.getByTestId('credit-payment-option')
    fireEvent.click(creditOption)

    // 点击支付按钮
    const payButton = screen.getByTestId('pay-button')
    fireEvent.click(payButton)

    // 验证支付处理中状态
    await waitFor(() => {
      expect(screen.getByText('支付中...')).toBeInTheDocument()
    })

    // 此时页面应该显示支付处理中，用户无法进行其他操作
    await waitFor(() => {
      // 重新获取按钮元素（文本已变为"支付处理中..."）
      const processingButton = screen.getByText('支付处理中...')
      // 检查按钮是否被禁用
      expect(processingButton).toBeDisabled()
    })

    // 模拟支付完成（超时或其他原因）
    resolvePayment({
      status: 200,
      json: () => Promise.resolve(createTestCreditBalance({ usedAmount: 300, availableAmount: 700 })),
    })

    // 验证支付成功
    await waitFor(() => {
      // 使用更具体的查询，避免多个"支付成功"元素
      expect(screen.getByText('支付成功', { selector: 'span.text-xl' })).toBeInTheDocument()
    })

    // 推进时间以触发跳转
    jest.advanceTimersByTime(1600)
  })
})