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

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

import { api } from '@/utils/request'

const mockWriteText = vi.fn().mockResolvedValue(undefined)
Object.defineProperty(navigator, 'clipboard', {
  value: { writeText: mockWriteText },
  writable: true,
  configurable: true,
})

const mockKeys: ApiKeyRecord[] = [
  {
    id: 1,
    user_id: 1,
    name: '测试Key',
    key_prefix: 'a1b2c3d4',
    last_used_at: null,
    expires_at: null,
    enabled: true,
    created_at: '2026-04-01T10:00:00',
  },
  {
    id: 2,
    user_id: 1,
    name: '过期Key',
    key_prefix: 'e5f6g7h8',
    last_used_at: '2026-04-10T08:00:00',
    expires_at: '2025-01-01T00:00:00',
    enabled: true,
    created_at: '2025-12-01T10:00:00',
  },
]

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

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

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

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

  function setupUser(apiKeyEnabled: boolean) {
    const store = useUserStore()
    store.setToken(fakeJwtWithRole('admin'), 'r.p.s', true)
    store.setUser({
      id: 1,
      username: 'testuser',
      email: 'test@example.com',
      role: 'admin',
      status: 1,
      api_key_enabled: apiKeyEnabled,
    })
  }

  function setupApi(keys: ApiKeyRecord[] = []) {
    vi.mocked(api.get).mockImplementation((url: string) => {
      if (url === '/api/v1/me/api-keys') return Promise.resolve(keys) as never
      return Promise.resolve([]) as never
    })
    vi.mocked(api.post).mockResolvedValue({
      plain_key: 'dk_test_plain_key_123',
      api_key: { ...mockKeys[0], id: 99, name: '新Key' },
    } as never)
    vi.mocked(api.patch).mockImplementation(() => Promise.resolve(undefined) as never)
    vi.mocked(api.delete).mockImplementation(() => Promise.resolve(undefined) as never)
  }

  async function mountPanel() {
    wrapper = mount(ApiKeyPanel, { attachTo: document.body })
    await flushPromises()
    await nextTick()
    return wrapper
  }

  function queryBody(selector: string) {
    return document.body.querySelectorAll(selector)
  }

  describe('渲染模式', () => {
    it('enabled + 有Key → 显示完整面板（表格+数量tag+生成按钮）', async () => {
      setupUser(true)
      setupApi(mockKeys)
      await mountPanel()

      const html = document.body.innerHTML
      expect(html).toContain('2/10')
      expect(queryBody('.ant-table').length).toBeGreaterThan(0)
      const btns = Array.from(queryBody('.ant-btn-primary'))
      expect(btns.some((btn) => btn.textContent?.includes('生成 API Key'))).toBe(true)
    })

    it('enabled + 无Key → 显示引导空状态', async () => {
      setupUser(true)
      setupApi([])
      await mountPanel()

      expect(queryBody('.ant-empty').length).toBeGreaterThan(0)
      const btns = Array.from(queryBody('.ant-btn-primary'))
      expect(btns.some((b) => b.textContent?.includes('生成第一个 API Key'))).toBe(true)
    })

    it('disabled + 无Key → 显示未启用提示', async () => {
      setupUser(false)
      setupApi([])
      await mountPanel()

      expect(document.body.innerHTML).toContain('API Key 功能未启用，请联系管理员开启')
    })

    it('disabled + 有Key → 显示警告Alert + 只读表格', async () => {
      setupUser(false)
      setupApi(mockKeys)
      await mountPanel()

      expect(queryBody('.ant-alert-warning').length).toBeGreaterThan(0)
      expect(queryBody('.ant-table').length).toBeGreaterThan(0)
      const btns = Array.from(queryBody('.ant-btn-primary'))
      expect(btns.some((btn) => btn.textContent?.includes('生成 API Key'))).toBe(false)
      expect(queryBody('.ant-switch').length).toBe(0)
    })
  })

  describe('交互', () => {
    it('点击生成按钮 → 打开创建 Modal', async () => {
      setupUser(true)
      setupApi(mockKeys)
      await mountPanel()

      const btn = Array.from(queryBody('.ant-btn-primary')).find(
        (b) => b.textContent?.includes('生成 API Key'),
      ) as HTMLElement
      btn.click()
      await flushPromises()
      await nextTick()

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

    it('提交创建 → 调用 createKey API → 显示明文 Modal', async () => {
      setupUser(true)
      setupApi(mockKeys)
      await mountPanel()

      const vm = getSetupState(wrapper)
      vm.createForm.name = '新Key'
      vm.createModalOpen = true
      await nextTick()
      await flushPromises()

      const okBtn = document.body.querySelector(
        '.ant-modal-footer .ant-btn-primary',
      ) as HTMLElement
      okBtn?.click()
      await flushPromises()
      await nextTick()
      await flushPromises()

      expect(vi.mocked(api.post)).toHaveBeenCalledWith('/api/v1/me/api-keys', {
        name: '新Key',
        expires_at: undefined,
      })
      const html = document.body.innerHTML
      expect(html).toContain('dk_test_plain_key_123')
      expect(html).toContain('此密钥仅显示一次')
    })

    it('复制按钮 → 调用 clipboard.writeText', async () => {
      setupUser(true)
      setupApi(mockKeys)
      await mountPanel()

      const vm = getSetupState(wrapper)
      vm.plainKey = 'dk_secret_key_abc'
      vm.plainKeyModalOpen = true
      await nextTick()
      await flushPromises()

      const copyBtn = Array.from(queryBody('.ant-btn-primary')).find(
        (b) => b.textContent?.includes('复制'),
      ) as HTMLElement
      copyBtn?.click()
      await flushPromises()

      expect(mockWriteText).toHaveBeenCalledWith('dk_secret_key_abc')
    })

    it('关闭明文 Modal → plainKey 清空', async () => {
      setupUser(true)
      setupApi(mockKeys)
      await mountPanel()

      const vm = getSetupState(wrapper)
      vm.plainKey = 'dk_secret_key'
      vm.plainKeyModalOpen = true
      await nextTick()

      vm.handlePlainKeyModalClose()
      await nextTick()

      expect(vm.plainKey).toBe('')
      expect(vm.plainKeyModalOpen).toBe(false)
    })

    it('Toggle switch → 调用 toggleKey API', async () => {
      setupUser(true)
      setupApi(mockKeys)
      await mountPanel()

      expect(queryBody('.ant-switch').length).toBe(2)

      await getSetupState(wrapper).handleToggle(mockKeys[0], false)
      await flushPromises()

      expect(vi.mocked(api.patch)).toHaveBeenCalledWith(
        '/api/v1/me/api-keys/1/toggle',
        { enabled: false },
      )
    })

    it('删除 → 调用 deleteKey API', async () => {
      setupUser(true)
      setupApi(mockKeys)
      const successSpy = vi.spyOn(message, 'success')
      await mountPanel()

      await getSetupState(wrapper).handleDelete(mockKeys[0])
      await flushPromises()

      expect(vi.mocked(api.delete)).toHaveBeenCalledWith('/api/v1/me/api-keys/1')
      expect(successSpy).toHaveBeenCalledWith('已删除')
    })
  })

  describe('边界测试', () => {
    it('达到10个上限 → 生成按钮禁用', async () => {
      setupUser(true)
      const tenKeys: ApiKeyRecord[] = Array.from({ length: 10 }, (_, i) => ({
        id: i + 1,
        user_id: 1,
        name: `Key ${i + 1}`,
        key_prefix: `prefix${String(i).padStart(2, '0')}`,
        last_used_at: null,
        expires_at: null,
        enabled: true,
        created_at: '2026-04-01T10:00:00',
      }))
      setupApi(tenKeys)
      await mountPanel()

      expect(document.body.innerHTML).toContain('10/10')
      const disabledBtn = document.body.querySelector('.ant-btn-primary[disabled]')
      expect(disabledBtn).toBeTruthy()
    })

    it('过期Key → 行变灰 + 已过期标签 + Switch 禁用', async () => {
      setupUser(true)
      setupApi(mockKeys)
      await mountPanel()

      expect(queryBody('.expired-row').length).toBeGreaterThanOrEqual(1)
      expect(document.body.innerHTML).toContain('已过期')
      const switches = Array.from(queryBody('.ant-switch'))
      const disabledSwitches = switches.filter((s) =>
        s.classList.contains('ant-switch-disabled'),
      )
      expect(disabledSwitches.length).toBeGreaterThanOrEqual(1)
    })

    it('onMounted → 调用 fetchMyKeys', async () => {
      setupUser(true)
      setupApi([])
      await mountPanel()

      expect(vi.mocked(api.get)).toHaveBeenCalledWith('/api/v1/me/api-keys')
    })
  })
})
