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 { useUserManageStore, type UserRecord } from '@/stores/user-manage'
import { useUserStore } from '@/stores/user'
import { fakeJwtWithRole } from '@/test/helpers'

// 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(),
    put: vi.fn(),
    patch: vi.fn(),
  },
}))

const mockUsers: UserRecord[] = [
  { id: 1, username: 'admin', email: 'admin@test.com', status: 1, role_id: 1, ext: null, created_at: '2026-01-01T00:00:00Z', updated_at: '2026-01-01T00:00:00Z', api_key_enabled: true },
  { id: 2, username: 'user1', email: 'user1@test.com', status: 0, role_id: 2, ext: { note: 'test' }, created_at: '2026-01-02T00:00:00Z', updated_at: '2026-01-02T00:00:00Z', api_key_enabled: false },
]

const mockPaginatedResponse = {
  items: mockUsers,
  total: 2,
  page: 1,
  per_page: 10,
}

describe('useUserManageStore', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
    vi.restoreAllMocks()
  })

  describe('initial state', () => {
    it('starts with empty users and default pagination', () => {
      const store = useUserManageStore()

      expect(store.users).toEqual([])
      expect(store.loading).toBe(false)
      expect(store.pagination.page).toBe(1)
      expect(store.pagination.per_page).toBe(10)
      expect(store.pagination.total).toBe(0)
      expect(store.filters.username).toBe('')
      expect(store.filters.email).toBe('')
      expect(store.filters.status).toBeUndefined()
      expect(store.filters.role_id).toBeUndefined()
    })
  })

  describe('fetchUsers', () => {
    it('loads data and updates state', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockResolvedValueOnce(mockPaginatedResponse)

      const store = useUserManageStore()
      await store.fetchUsers()

      expect(api.get).toHaveBeenCalledWith('/api/v1/users', {
        page: 1,
        per_page: 10,
        username: undefined,
        email: undefined,
        status: undefined,
        role_id: undefined,
      })
      expect(store.users).toEqual(mockUsers)
      expect(store.pagination.total).toBe(2)
      expect(store.loading).toBe(false)
    })

    it('sends filter params when set', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockResolvedValueOnce({
        items: [mockUsers[0]],
        total: 1,
        page: 1,
        per_page: 10,
      })

      const store = useUserManageStore()
      store.filters.username = 'admin'
      store.filters.status = 1
      store.filters.role_id = 2
      await store.fetchUsers()

      expect(api.get).toHaveBeenCalledWith('/api/v1/users', expect.objectContaining({
        username: 'admin',
        status: 1,
        role_id: 2,
      }))
      expect(store.users).toHaveLength(1)
    })

    it('converts empty string filters to undefined', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockResolvedValueOnce(mockPaginatedResponse)

      const store = useUserManageStore()
      store.filters.username = ''
      store.filters.email = ''
      await store.fetchUsers()

      expect(api.get).toHaveBeenCalledWith('/api/v1/users', expect.objectContaining({
        username: undefined,
        email: undefined,
      }))
    })

    it('respects pagination params', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockResolvedValueOnce({ ...mockPaginatedResponse, page: 2 })

      const store = useUserManageStore()
      store.pagination.page = 2
      store.pagination.per_page = 20
      await store.fetchUsers()

      expect(api.get).toHaveBeenCalledWith('/api/v1/users', expect.objectContaining({
        page: 2,
        per_page: 20,
      }))
      expect(store.pagination.page).toBe(2)
    })

    it('sets loading to false and shows error message on failure', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockRejectedValueOnce(new Error('Network error'))
      const errorSpy = vi.spyOn(message, 'error').mockImplementation(() => ({}) as never)

      const store = useUserManageStore()
      await store.fetchUsers()
      expect(store.loading).toBe(false)
      expect(store.users).toEqual([])
      expect(errorSpy).toHaveBeenCalledWith('Network error')
      errorSpy.mockRestore()
    })
  })

  describe('resetFilters', () => {
    it('clears all filters and resets page to 1', () => {
      const store = useUserManageStore()
      store.filters.username = 'test'
      store.filters.email = 'test@test.com'
      store.filters.status = 1
      store.filters.role_id = 3
      store.pagination.page = 3

      store.resetFilters()

      expect(store.filters.username).toBe('')
      expect(store.filters.email).toBe('')
      expect(store.filters.status).toBeUndefined()
      expect(store.filters.role_id).toBeUndefined()
      expect(store.pagination.page).toBe(1)
    })
  })
})

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

  beforeEach(() => {
    setActivePinia(createPinia())
    vi.restoreAllMocks()
    document.body.innerHTML = ''
  })

  afterEach(() => {
    wrapper?.unmount()
    document.body.innerHTML = ''
  })

  const mockRoles = [
    { id: 1, name: 'administrator', description: '管理员' },
    { id: 2, name: 'accessor', description: '访问者' },
  ]

  async function mountPage(opts: { rolesError?: boolean } = {}) {
    const { api } = await import('@/utils/request')
    vi.mocked(api.get).mockImplementation((url: string) => {
      if (url === '/api/v1/roles') {
        if (opts.rolesError) return Promise.reject(new Error('roles fail'))
        return Promise.resolve(mockRoles) as never
      }
      // use fresh copies to prevent cross-test mutation of mockUsers
      return Promise.resolve({ ...mockPaginatedResponse, items: mockUsers.map(u => ({ ...u })) }) as never
    })

    // 设置 admin 用户以显示操作按钮（isAdmin 从 JWT 派生）
    const userStore = useUserStore()
    userStore.setToken(fakeJwtWithRole('administrator'), 'rt')
    userStore.setUser({ id: 1, username: 'admin', email: 'a@a.com', role: 'administrator', status: 1 })

    const { default: UsersPage } = await import('../index.vue')

    wrapper = mount(UsersPage, {
      attachTo: document.body,
      global: {
        stubs: {
          PlusOutlined: { template: '<span />' },
          SearchOutlined: { template: '<span />' },
          ReloadOutlined: { template: '<span />' },
          UserFormModal: { template: '<div class="form-modal-stub" />' },
        },
      },
    })
    await flushPromises()
    await nextTick()
    return { wrapper, api }
  }

  it('renders page title', async () => {
    const { wrapper } = await mountPage()
    expect(wrapper.find('h2').text()).toBe('用户管理')
  }, 10000)

  it('renders filter form and action buttons', async () => {
    await mountPage()
    const buttons = Array.from(document.body.querySelectorAll('.ant-btn'))
    const buttonTexts = buttons.map((b) => b.textContent?.trim())

    expect(buttonTexts.some((t) => t?.includes('搜索'))).toBe(true)
    expect(buttonTexts.some((t) => t?.includes('重置'))).toBe(true)
    expect(buttonTexts.some((t) => t?.includes('新建用户'))).toBe(true)
  })

  it('calls fetchUsers on mount', async () => {
    const { api } = await mountPage()
    expect(api.get).toHaveBeenCalledWith('/api/v1/users', expect.any(Object))
  })

  it('renders table with correct number of rows', async () => {
    await mountPage()
    const rows = document.body.querySelectorAll('.ant-table-tbody tr.ant-table-row')
    expect(rows).toHaveLength(mockUsers.length)
  })

  it('displays status tags with correct colors', async () => {
    await mountPage()
    const tags = document.body.querySelectorAll('.ant-tag')
    const tagArray = Array.from(tags)

    const greenTags = tagArray.filter((t) => t.classList.toString().includes('green'))
    const redTags = tagArray.filter((t) => t.classList.toString().includes('red'))
    expect(greenTags.length).toBeGreaterThanOrEqual(1)
    expect(redTags.length).toBeGreaterThanOrEqual(1)
  })

  it('hides create/edit/toggle buttons for non-admin user', async () => {
    const { api } = await import('@/utils/request')
    vi.mocked(api.get).mockResolvedValue(mockPaginatedResponse)

    // 设置非 admin 用户（JWT 中 role 为 accessor）
    const userStore = useUserStore()
    userStore.setToken(fakeJwtWithRole('accessor'), 'rt')
    userStore.setUser({ id: 2, username: 'viewer', email: 'v@v.com', role: 'accessor', status: 1 })

    const { default: UsersPage } = await import('../index.vue')

    wrapper = mount(UsersPage, {
      attachTo: document.body,
      global: {
        stubs: {
          PlusOutlined: { template: '<span />' },
          SearchOutlined: { template: '<span />' },
          ReloadOutlined: { template: '<span />' },
          UserFormModal: { template: '<div class="form-modal-stub" />' },
        },
      },
    })
    await flushPromises()
    await nextTick()

    const buttons = Array.from(document.body.querySelectorAll('.ant-btn'))
    const buttonTexts = buttons.map((b) => b.textContent?.trim())

    expect(buttonTexts.some((t) => t?.includes('新建用户'))).toBe(false)
    expect(buttonTexts.some((t) => t?.includes('编辑'))).toBe(false)
    expect(buttonTexts.some((t) => t?.includes('禁用'))).toBe(false)
    // 查看按钮仍可见
    expect(buttonTexts.some((t) => t?.includes('查看'))).toBe(true)
  })

  it('opens detail drawer when clicking view button', async () => {
    const { api } = await mountPage()
    vi.mocked(api.get).mockResolvedValueOnce(mockUsers[0])

    const buttons = Array.from(document.body.querySelectorAll('.ant-btn'))
    const viewBtn = buttons.find((b) => b.textContent?.trim() === '查看')
    expect(viewBtn).toBeDefined()

    viewBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
    await flushPromises()
    await nextTick()

    const drawer = document.body.querySelector('.ant-drawer')
    expect(drawer).toBeTruthy()
  })

  it('renders role filter select with options', async () => {
    await mountPage()
    const labels = Array.from(document.body.querySelectorAll('.ant-form-item-label label'))
    const roleLabel = labels.find((l) => l.textContent?.includes('角色'))
    expect(roleLabel).toBeTruthy()
  })

  it('page still works when role list fails to load', async () => {
    await mountPage({ rolesError: true })

    // 页面标题仍正常渲染
    expect(wrapper.find('h2').text()).toBe('用户管理')
    // 表格仍正常渲染
    const rows = document.body.querySelectorAll('.ant-table-tbody tr.ant-table-row')
    expect(rows).toHaveLength(mockUsers.length)
  })

  describe('API Key Switch', () => {
    it('renders Switch with correct checked state based on api_key_enabled', async () => {
      await mountPage()
      const switches = document.body.querySelectorAll('.ant-switch')
      // 第1行 api_key_enabled=true → checked
      expect(switches[0]?.classList.contains('ant-switch-checked')).toBe(true)
      // 第2行 api_key_enabled=false → unchecked
      expect(switches[1]?.classList.contains('ant-switch-checked')).toBe(false)
    })

    it('calls api.patch directly when enabling and shows success', async () => {
      const { api } = await mountPage()
      vi.mocked(api.patch).mockResolvedValueOnce(undefined)
      const successSpy = vi.spyOn(message, 'success').mockImplementation(() => ({}) as never)

      const store = useUserManageStore()
      const record = store.users[1]! // api_key_enabled=false

      // Invoke the component's handleToggleApiKeyEnabled via setupState
      const handleToggle = (wrapper.getCurrentComponent() as any).setupState.handleToggleApiKeyEnabled
      await handleToggle(record, true)

      expect(api.patch).toHaveBeenCalledWith(
        `/api/v1/users/${record.id}/api-key-enabled`,
        { api_key_enabled: true },
      )
      expect(record.api_key_enabled).toBe(true)
      expect(successSpy).toHaveBeenCalledWith('操作成功')
      successSpy.mockRestore()
    })

    it('calls api.patch with false on disable and shows success', async () => {
      const { api } = await mountPage()
      vi.mocked(api.patch).mockResolvedValueOnce(undefined)
      const successSpy = vi.spyOn(message, 'success').mockImplementation(() => ({}) as never)

      const store = useUserManageStore()
      const record = store.users[0]! // api_key_enabled=true

      const handleToggle = (wrapper.getCurrentComponent() as any).setupState.handleToggleApiKeyEnabled
      await handleToggle(record, false)

      expect(api.patch).toHaveBeenCalledWith(
        `/api/v1/users/${record.id}/api-key-enabled`,
        { api_key_enabled: false },
      )
      expect(record.api_key_enabled).toBe(false)
      expect(successSpy).toHaveBeenCalledWith('操作成功')
      successSpy.mockRestore()
    })

    it('rolls back api_key_enabled and shows error on patch failure', async () => {
      const { api } = await mountPage()
      vi.mocked(api.patch).mockRejectedValueOnce(new Error('服务器错误'))
      const errorSpy = vi.spyOn(message, 'error').mockImplementation(() => ({}) as never)

      const store = useUserManageStore()
      const record = store.users[0]! // api_key_enabled=true

      const handleToggle = (wrapper.getCurrentComponent() as any).setupState.handleToggleApiKeyEnabled
      await handleToggle(record, false)

      // record is the same reactive ref as store.users[0]; rollback should restore it
      expect(record.api_key_enabled).toBe(true)
      expect(errorSpy).toHaveBeenCalledWith('服务器错误')
      errorSpy.mockRestore()
    })
  })
})
