// ============================================================
// Login 页面测试
// ============================================================

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { MemoryRouter } from 'react-router-dom'

import Login from '@/pages/Login'

// ── Mock data ────────────────────────────────────────────────

const mockLoginResponse = {
  token: 'jwt-token-123',
  refresh_token: 'refresh-token-456',
  account: {
    id: 'acc-001',
    username: 'testadmin',
    email: 'admin@zclaw.ai',
    display_name: 'Admin',
    role: 'super_admin',
    status: 'active',
    totp_enabled: false,
    last_login_at: null,
    created_at: '2026-01-01T00:00:00Z',
    llm_routing: 'relay',
  },
}

const mockAccount = {
  id: 'acc-001',
  username: 'testadmin',
  email: 'admin@zclaw.ai',
  display_name: 'Admin',
  role: 'super_admin',
  status: 'active',
  totp_enabled: false,
  last_login_at: null,
  created_at: '2026-01-01T00:00:00Z',
  llm_routing: 'relay',
}

// ── Hoisted mocks ────────────────────────────────────────────

const { mockLogin, mockNavigate, mockAuthServiceLogin } = vi.hoisted(() => ({
  mockLogin: vi.fn(),
  mockNavigate: vi.fn(),
  mockAuthServiceLogin: vi.fn(),
}))

vi.mock('@/stores/authStore', () => ({
  useAuthStore: Object.assign(
    vi.fn((selector: (s: Record<string, unknown>) => unknown) =>
      selector({ login: mockLogin }),
    ),
    { getState: () => ({ token: null, refreshToken: null, logout: vi.fn() }) },
  ),
}))

vi.mock('@/services/auth', () => ({
  authService: {
    login: mockAuthServiceLogin,
  },
}))

vi.mock('react-router-dom', async () => {
  const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom')
  return {
    ...actual,
    useNavigate: () => mockNavigate,
  }
})

beforeEach(() => {
  mockLogin.mockClear()
  mockNavigate.mockClear()
  mockAuthServiceLogin.mockClear()
})

// ── Helper: render with providers ────────────────────────────

function renderLogin(initialEntries = ['/login']) {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
    },
  })
  return render(
    <QueryClientProvider client={queryClient}>
      <MemoryRouter initialEntries={initialEntries}>
        <Login />
      </MemoryRouter>
    </QueryClientProvider>,
  )
}

/** Click the LoginForm submit button (Ant Design renders "登 录" with a space) */
function getSubmitButton(): HTMLElement {
  const btn = document.querySelector<HTMLButtonElement>(
    'button.ant-btn-primary[type="button"]',
  )
  if (!btn) throw new Error('Submit button not found')
  return btn
}

// ── Tests ────────────────────────────────────────────────────

describe('Login page', () => {
  it('renders the login form with username and password fields', () => {
    renderLogin()

    expect(screen.getByText('登录到 ZCLAW')).toBeInTheDocument()
    expect(screen.getByPlaceholderText('请输入用户名')).toBeInTheDocument()
    expect(screen.getByPlaceholderText('请输入密码')).toBeInTheDocument()
    const submitButton = getSubmitButton()
    expect(submitButton).toBeTruthy()
  })

  it('shows the ZCLAW brand logo', () => {
    renderLogin()

    expect(screen.getByText('Z')).toBeInTheDocument()
    expect(screen.getByText(/ZCLAW Admin/)).toBeInTheDocument()
  })

  it('successful login calls authStore.login and navigates to /', async () => {
    const user = userEvent.setup()
    mockAuthServiceLogin.mockResolvedValue(mockLoginResponse)

    renderLogin()

    await user.type(screen.getByPlaceholderText('请输入用户名'), 'testadmin')
    await user.type(screen.getByPlaceholderText('请输入密码'), 'password123')
    await user.click(getSubmitButton())

    await waitFor(() => {
      expect(mockLogin).toHaveBeenCalledWith(
        'jwt-token-123',
        'refresh-token-456',
        mockAccount,
      )
    })

    expect(mockNavigate).toHaveBeenCalledWith('/', { replace: true })
  })

  it('navigates to redirect path after login', async () => {
    const user = userEvent.setup()
    mockAuthServiceLogin.mockResolvedValue(mockLoginResponse)

    renderLogin(['/login?from=/settings'])

    await user.type(screen.getByPlaceholderText('请输入用户名'), 'testadmin')
    await user.type(screen.getByPlaceholderText('请输入密码'), 'password123')
    await user.click(getSubmitButton())

    await waitFor(() => {
      expect(mockNavigate).toHaveBeenCalledWith('/settings', { replace: true })
    })
  })

  it('shows TOTP field when server returns TOTP-related error', async () => {
    const user = userEvent.setup()
    const error = new Error('请输入两步验证码 (TOTP)')
    Object.assign(error, { status: 403 })
    mockAuthServiceLogin.mockRejectedValue(error)

    renderLogin()

    // Initially no TOTP field
    expect(screen.queryByPlaceholderText('请输入 6 位验证码')).not.toBeInTheDocument()

    await user.type(screen.getByPlaceholderText('请输入用户名'), 'testadmin')
    await user.type(screen.getByPlaceholderText('请输入密码'), 'password123')
    await user.click(getSubmitButton())

    // After TOTP error, TOTP field appears
    await waitFor(() => {
      expect(screen.getByPlaceholderText('请输入 6 位验证码')).toBeInTheDocument()
    })
  })

  it('shows error message on invalid credentials', async () => {
    const user = userEvent.setup()
    const error = new Error('用户名或密码错误')
    mockAuthServiceLogin.mockRejectedValue(error)

    renderLogin()

    await user.type(screen.getByPlaceholderText('请输入用户名'), 'wrong')
    await user.type(screen.getByPlaceholderText('请输入密码'), 'wrong')
    await user.click(getSubmitButton())

    await waitFor(() => {
      expect(screen.getByText('用户名或密码错误')).toBeInTheDocument()
    })
  })

  it('does not call authStore.login on failed login', async () => {
    const user = userEvent.setup()
    const error = new Error('用户名或密码错误')
    mockAuthServiceLogin.mockRejectedValue(error)

    renderLogin()

    await user.type(screen.getByPlaceholderText('请输入用户名'), 'wrong')
    await user.type(screen.getByPlaceholderText('请输入密码'), 'wrong')
    await user.click(getSubmitButton())

    await waitFor(() => {
      expect(screen.getByText('用户名或密码错误')).toBeInTheDocument()
    })

    expect(mockLogin).not.toHaveBeenCalled()
    expect(mockNavigate).not.toHaveBeenCalled()
  })
})
