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 RolePermissionModal from '@/components/RolePermissionModal.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(),
    delete: vi.fn(),
  },
}))

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

const mockRole = {
  id: 2,
  name: 'editor',
  label: '编辑者',
  description: '可以编辑内容的角色',
  users_count: 3,
  route_groups_count: 2,
}

const mockGroups = [
  { id: 1, name: 'user-mgmt', label: '用户管理', description: '', sort_order: 1, route_count: 5 },
  { id: 2, name: 'product-mgmt', label: '产品管理', description: '', sort_order: 2, route_count: 3 },
  { id: 3, name: 'order-mgmt', label: '订单管理', description: '', sort_order: 3, route_count: 4 },
]

const mockRoutes = [
  { id: 1, method: 'GET', path: '/api/v1/users', description: '用户列表', group_id: 1, group_name: '用户管理' },
  { id: 2, method: 'POST', path: '/api/v1/users', description: '创建用户', group_id: 1, group_name: '用户管理' },
  { id: 3, method: 'GET', path: '/api/v1/products', description: '产品列表', group_id: 2, group_name: '产品管理' },
]

const mockOverrides = [
  { route_id: 1, method: 'GET', path: '/api/v1/users', allowed: true },
]

function setupMocks(overrides: {
  groupIds?: number[]
  roleOverrides?: typeof mockOverrides
} = {}) {
  vi.mocked(api.get).mockImplementation((url: string) => {
    if (url.match(/\/roles\/\d+\/route-groups/)) {
      const ids = overrides.groupIds ?? [1, 2]
      return Promise.resolve(ids.map((id) => ({ id }))) as never
    }
    if (url.match(/\/roles\/\d+\/route-overrides/)) return Promise.resolve(overrides.roleOverrides ?? mockOverrides) as never
    if (url === '/api/v1/route-groups') return Promise.resolve(mockGroups) as never
    if (url === '/api/v1/routes') return Promise.resolve(mockRoutes) as never
    return Promise.resolve([]) as never
  })
  vi.mocked(api.put).mockResolvedValue(undefined as never)
}

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

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

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

  async function mountModal(props = {}) {
    wrapper = mount(RolePermissionModal, {
      props: {
        open: true,
        role: mockRole,
        ...props,
      },
      attachTo: document.body,
    })
    await flushPromises()
    await nextTick()
    return wrapper
  }

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

  describe('Modal 渲染', () => {
    it('标题包含角色名', async () => {
      setupMocks()
      await mountModal()
      const title = document.body.querySelector('.ant-modal-title')
      expect(title?.textContent).toContain('编辑者')
    })

    it('显示两个 tab', async () => {
      setupMocks()
      await mountModal()
      const tabs = queryBody('.ant-tabs-tab')
      expect(tabs.length).toBe(2)
      expect(tabs[0]?.textContent).toContain('路由组授权')
      expect(tabs[1]?.textContent).toContain('单条路由覆盖')
    })

    it('open=false 不渲染内容', async () => {
      setupMocks()
      await mountModal({ open: false })
      const modal = document.body.querySelector('.ant-modal')
      expect(modal).toBeNull()
    })
  })

  describe('Tab 1: 路由组授权', () => {
    it('加载路由组 checkbox', async () => {
      setupMocks()
      await mountModal()
      const checkboxes = queryBody('.ant-checkbox-wrapper')
      expect(checkboxes.length).toBe(3)
      expect(checkboxes[0]?.textContent).toContain('用户管理')
      expect(checkboxes[1]?.textContent).toContain('产品管理')
      expect(checkboxes[2]?.textContent).toContain('订单管理')
    })

    it('预勾选已授权的路由组', async () => {
      setupMocks({ groupIds: [1, 2] })
      await mountModal()
      const checked = queryBody('.ant-checkbox-wrapper-checked')
      expect(checked.length).toBe(2)
    })

    it('保存组授权调用正确 API', async () => {
      setupMocks({ groupIds: [1] })
      await mountModal()

      const saveBtn = Array.from(queryBody('.ant-btn-primary')).find(
        (btn) => btn.textContent?.includes('保存组授权'),
      ) as HTMLElement
      expect(saveBtn).toBeTruthy()
      saveBtn.click()
      await flushPromises()

      expect(vi.mocked(api.put)).toHaveBeenCalledWith(
        '/api/v1/roles/2/route-groups',
        { group_ids: [1] },
      )
    })

    it('保存成功显示 success message', async () => {
      setupMocks({ groupIds: [1] })
      const spy = vi.spyOn(message, 'success')
      await mountModal()

      const saveBtn = Array.from(queryBody('.ant-btn-primary')).find(
        (btn) => btn.textContent?.includes('保存组授权'),
      ) as HTMLElement
      saveBtn.click()
      await flushPromises()

      expect(spy).toHaveBeenCalledWith('路由组授权保存成功')
    })

    it('保存失败显示 error message', async () => {
      setupMocks({ groupIds: [1] })
      vi.mocked(api.put).mockRejectedValueOnce(new Error('保存出错'))
      const spy = vi.spyOn(message, 'error')
      await mountModal()

      const saveBtn = Array.from(queryBody('.ant-btn-primary')).find(
        (btn) => btn.textContent?.includes('保存组授权'),
      ) as HTMLElement
      saveBtn.click()
      await flushPromises()

      expect(spy).toHaveBeenCalledWith('保存出错')
    })
  })

  describe('Tab 2: 单条路由覆盖', () => {
    async function switchToOverridesTab() {
      const tabs = queryBody('.ant-tabs-tab')
      const overrideTab = tabs[1] as HTMLElement
      overrideTab.click()
      await flushPromises()
      await nextTick()
    }

    it('加载覆盖列表表格', async () => {
      setupMocks()
      await mountModal()
      await switchToOverridesTab()

      const rows = queryBody('.ant-table-tbody tr')
      expect(rows.length).toBeGreaterThanOrEqual(1)
    })

    it('method tag 使用正确颜色', async () => {
      setupMocks()
      await mountModal()
      await switchToOverridesTab()

      const tags = queryBody('.ant-table-tbody .ant-tag')
      if (tags.length > 0) {
        expect(tags[0]?.className).toContain('green')
      }
    })

    it('移除覆盖按钮可用', async () => {
      setupMocks()
      await mountModal()
      await switchToOverridesTab()

      const removeBtn = Array.from(queryBody('.ant-btn')).find(
        (btn) => btn.textContent?.includes('移除'),
      )
      expect(removeBtn).toBeTruthy()
    })

    it('保存覆盖调用正确 API', async () => {
      setupMocks()
      await mountModal()
      await switchToOverridesTab()

      const saveBtn = Array.from(queryBody('.ant-btn-primary')).find(
        (btn) => btn.textContent?.includes('保存覆盖'),
      ) as HTMLElement
      expect(saveBtn).toBeTruthy()
      saveBtn.click()
      await flushPromises()

      expect(vi.mocked(api.put)).toHaveBeenCalledWith(
        '/api/v1/roles/2/route-overrides',
        { overrides: mockOverrides },
      )
    })
  })

  describe('生命周期', () => {
    it('打开时触发数据加载', async () => {
      setupMocks()
      await mountModal()

      expect(vi.mocked(api.get)).toHaveBeenCalled()
      const calls = vi.mocked(api.get).mock.calls.map((c) => c[0])
      expect(calls.some((url) => url.includes('/route-groups'))).toBe(true)
      expect(calls.some((url) => url.includes('/route-overrides'))).toBe(true)
    })

    it('API 错误不阻塞渲染', async () => {
      vi.mocked(api.get).mockRejectedValue(new Error('网络错误'))
      await mountModal()

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