import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { mount, flushPromises } from '@vue/test-utils'
import { nextTick } from 'vue'
import { message } from 'ant-design-vue'
import { ApiError } from '@/types/api'
import { useUserStore } from '@/stores/user'

// jsdom 不支持 matchMedia，Ant Design Vue 响应式布局需要
Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: (query: string) => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: () => {},
    removeListener: () => {},
    addEventListener: () => {},
    removeEventListener: () => {},
    dispatchEvent: () => false,
  }),
})

vi.mock('@/utils/request', () => ({
  api: {
    get: vi.fn(),
    post: vi.fn(),
  },
}))

const mockPush = vi.fn()
const mockRoute = { query: {} }

vi.mock('vue-router', () => ({
  useRouter: () => ({ push: mockPush }),
  useRoute: () => mockRoute,
}))

// Stub Brand component to avoid asset loading
vi.mock('@/components/Brand.vue', () => ({
  default: { template: '<div class="brand-stub" />' },
}))

function getSetupState(w: ReturnType<typeof mount>) {
  // @ts-expect-error setupState is Vue internal, not in public types
  return w.getCurrentComponent().setupState
}

describe('Login Page', () => {
  let wrapper: ReturnType<typeof mount>

  beforeEach(() => {
    setActivePinia(createPinia())
    localStorage.clear()
    vi.restoreAllMocks()
    mockPush.mockReset()
    mockRoute.query = {}
    vi.useFakeTimers()
  })

  afterEach(() => {
    wrapper?.unmount()
    vi.useRealTimers()
  })

  async function mountPage() {
    const { default: LoginPage } = await import('../login.vue')

    wrapper = mount(LoginPage, {
      attachTo: document.body,
      global: {
        stubs: {
          UserOutlined: { template: '<span />' },
          LockOutlined: { template: '<span />' },
        },
      },
    })
    await flushPromises()
    await nextTick()
    return wrapper
  }

  it('renders login form with username, password inputs and submit button', async () => {
    await mountPage()
    const html = wrapper.html()
    // 验证输入框存在
    expect(html).toContain('请输入用户名')
    expect(html).toContain('请输入密码')
    // 验证提交按钮存在
    expect(html).toContain('登录')
  })

  // 辅助：填写表单并提交，绕过 Ant Design 表单验证
  async function submitLogin(
    fields: { username: string; password: string; remember?: boolean },
  ) {
    const vm = getSetupState(wrapper)
    vm.formState.username = fields.username
    vm.formState.password = fields.password
    if (fields.remember !== undefined) vm.formState.remember = fields.remember
    vm.formRef = { validate: vi.fn().mockResolvedValue(true) }
    await vm.handleSubmit()
    await flushPromises()
  }

  it('calls /api/v1/login, sets token/user and redirects to /', async () => {
    const { api } = await import('@/utils/request')
    const mockResponse = {
      access_token: 'a.b.c',
      refresh_token: 'r.r.r',
      user: { id: 1, username: 'admin', email: 'a@b.com', role: 'admin', status: 1 },
    }
    vi.mocked(api.post).mockResolvedValueOnce(mockResponse)

    await mountPage()
    const store = useUserStore()
    await submitLogin({ username: 'admin', password: 'password' })

    expect(api.post).toHaveBeenCalledWith('/api/v1/login', {
      username: 'admin',
      password: 'password',
    })
    expect(store.token).toBe('a.b.c')
    expect(store.user).toEqual(mockResponse.user)

    vi.advanceTimersByTime(500)
    expect(mockPush).toHaveBeenCalledWith('/')
  })

  it('passes remember=false to setToken when unchecked', async () => {
    const { api } = await import('@/utils/request')
    vi.mocked(api.post).mockResolvedValueOnce({
      access_token: 'a.b.c',
      refresh_token: 'r.r.r',
      user: { id: 1, username: 'u', email: 'u@u.com', role: 'user', status: 1 },
    })

    await mountPage()
    await submitLogin({ username: 'user', password: 'pass', remember: false })

    expect(localStorage.getItem('access_token')).toBeNull()
    expect(localStorage.getItem('user')).toBeNull()
  })

  it('redirects to safe path from query param', async () => {
    const { api } = await import('@/utils/request')
    vi.mocked(api.post).mockResolvedValueOnce({
      access_token: 'a.b.c',
      refresh_token: 'r.r.r',
      user: { id: 1, username: 'u', email: 'u@u.com', role: 'user', status: 1 },
    })
    mockRoute.query = { redirect: '/dashboard' }

    await mountPage()
    await submitLogin({ username: 'user', password: 'pass' })

    vi.advanceTimersByTime(500)
    expect(mockPush).toHaveBeenCalledWith('/dashboard')
  })

  it('blocks //evil redirect and falls back to /', async () => {
    const { api } = await import('@/utils/request')
    vi.mocked(api.post).mockResolvedValueOnce({
      access_token: 'a.b.c',
      refresh_token: 'r.r.r',
      user: { id: 1, username: 'u', email: 'u@u.com', role: 'user', status: 1 },
    })
    mockRoute.query = { redirect: '//evil.com' }

    await mountPage()
    await submitLogin({ username: 'user', password: 'pass' })

    vi.advanceTimersByTime(500)
    expect(mockPush).toHaveBeenCalledWith('/')
  })

  it('blocks http:// redirect and falls back to /', async () => {
    const { api } = await import('@/utils/request')
    vi.mocked(api.post).mockResolvedValueOnce({
      access_token: 'a.b.c',
      refresh_token: 'r.r.r',
      user: { id: 1, username: 'u', email: 'u@u.com', role: 'user', status: 1 },
    })
    mockRoute.query = { redirect: 'http://evil.com' }

    await mountPage()
    await submitLogin({ username: 'user', password: 'pass' })

    vi.advanceTimersByTime(500)
    expect(mockPush).toHaveBeenCalledWith('/')
  })

  it('shows error message on ApiError', async () => {
    const { api } = await import('@/utils/request')
    vi.mocked(api.post).mockRejectedValueOnce(new ApiError('用户名或密码错误', 401))
    const errorSpy = vi.spyOn(message, 'error').mockImplementation(() => ({}) as never)

    await mountPage()
    await submitLogin({ username: 'user', password: 'wrong' })

    expect(errorSpy).toHaveBeenCalledWith('用户名或密码错误')
    errorSpy.mockRestore()
  })

  it('shows network error message on generic error', async () => {
    const { api } = await import('@/utils/request')
    vi.mocked(api.post).mockRejectedValueOnce(new Error('Network failure'))
    const errorSpy = vi.spyOn(message, 'error').mockImplementation(() => ({}) as never)

    await mountPage()
    await submitLogin({ username: 'user', password: 'pass' })

    expect(errorSpy).toHaveBeenCalledWith('登录失败，请检查网络连接')
    errorSpy.mockRestore()
  })

  it('navigates to register page', async () => {
    await mountPage()
    const vm = getSetupState(wrapper)
    vm.goToRegister()

    expect(mockPush).toHaveBeenCalledWith('/register')
  })
})
