import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { mount, flushPromises, VueWrapper } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { nextTick } from 'vue'
import { useRouteGroupStore } from '@/stores/route-group'
import type { RouteRecord } from '@/stores/route-group'
import RouteGroupsPage from '../index.vue'

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

const { api } = await import('@/utils/request')

// Ant Design Vue jsdom 兼容
Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: vi.fn().mockImplementation((query: string) => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: vi.fn(),
    removeListener: vi.fn(),
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
    dispatchEvent: vi.fn(),
  })),
})

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

const mockRoutes: RouteRecord[] = [
  { 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/orders', description: '订单列表', group_id: 2, group_name: '订单管理' },
  { id: 4, method: 'GET', path: '/api/v1/products', description: '产品列表', group_id: null, group_name: null },
  { id: 5, method: 'GET', path: '/api/v1/users/{id}', description: '用户详情', group_id: 1, group_name: '用户管理' },
]

// ─── Store 测试 ───

describe('useRouteGroupStore', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
    vi.clearAllMocks()
  })

  describe('routeTree', () => {
    it('groups all routes by first path segment as directories', () => {
      const store = useRouteGroupStore()
      store.routes = [...mockRoutes]

      const tree = store.routeTree
      const topKeys = tree.map((n) => n.key)
      // 按资源名分组为目录节点
      expect(topKeys).toContain('/users')
      expect(topKeys).toContain('/orders')
      expect(topKeys).toContain('/products')
      expect(tree).toHaveLength(3)
      // 所有顶层节点都是目录
      expect(tree.every((n) => !n.isLeaf)).toBe(true)
    })

    it('places same-path different-method routes inside same directory', () => {
      const store = useRouteGroupStore()
      store.routes = [...mockRoutes]

      const usersDir = store.routeTree.find((n) => n.key === '/users')!
      const userLeaves = usersDir.children!.filter((n) => n.isLeaf && n.title === 'users')
      expect(userLeaves).toHaveLength(2)
      expect(userLeaves.map((n) => n.method)).toEqual(expect.arrayContaining(['GET', 'POST']))
    })

    it('nests multi-segment routes under same directory', () => {
      const store = useRouteGroupStore()
      store.routes = [...mockRoutes]

      const usersDir = store.routeTree.find((n) => n.key === '/users')!
      // users 目录下：GET users, POST users, GET {id}
      expect(usersDir.children).toHaveLength(3)
      const idLeaf = usersDir.children!.find((n) => n.key === 'route:5')
      expect(idLeaf).toBeDefined()
      expect(idLeaf!.title).toBe('{id}')
    })

    it('creates leaf nodes with route metadata', () => {
      const store = useRouteGroupStore()
      store.routes = [...mockRoutes]

      const productsDir = store.routeTree.find((n) => n.key === '/products')!
      const productsLeaf = productsDir.children!.find((n) => n.key === 'route:4')
      expect(productsLeaf).toBeDefined()
      expect(productsLeaf!.isLeaf).toBe(true)
      expect(productsLeaf!.routeId).toBe(4)
      expect(productsLeaf!.method).toBe('GET')
      expect(productsLeaf!.fullPath).toBe('/api/v1/products')
    })

    it('returns empty array when no routes', () => {
      const store = useRouteGroupStore()
      expect(store.routeTree).toEqual([])
    })
  })

  describe('checkedRouteKeys', () => {
    it('returns keys for routes matching selected group', () => {
      const store = useRouteGroupStore()
      store.routes = [...mockRoutes]
      store.selectedGroupId = 1

      expect(store.checkedRouteKeys).toEqual(
        expect.arrayContaining(['route:1', 'route:2', 'route:5']),
      )
      expect(store.checkedRouteKeys).toHaveLength(3)
    })

    it('returns empty array when no group selected', () => {
      const store = useRouteGroupStore()
      store.routes = [...mockRoutes]
      store.selectedGroupId = null

      expect(store.checkedRouteKeys).toEqual([])
    })

    it('returns empty array for ungrouped selection', () => {
      const store = useRouteGroupStore()
      store.routes = [...mockRoutes]
      store.selectedGroupId = 'ungrouped'

      expect(store.checkedRouteKeys).toEqual([])
    })
  })

  describe('batchAssignRoutes', () => {
    it('calls API and refreshes data', async () => {
      const store = useRouteGroupStore()
      vi.mocked(api.put).mockResolvedValueOnce(undefined)
      vi.mocked(api.get)
        .mockResolvedValueOnce(mockGroups)
        .mockResolvedValueOnce(mockRoutes)

      await store.batchAssignRoutes(1, [1, 2, 3])

      expect(api.put).toHaveBeenCalledWith(
        '/api/v1/route-groups/1/routes',
        { route_ids: [1, 2, 3] },
      )
      expect(api.get).toHaveBeenCalledWith('/api/v1/route-groups')
      expect(api.get).toHaveBeenCalledWith('/api/v1/routes', undefined)
    })
  })

  describe('syncRoutes', () => {
    it('calls sync API and refreshes groups + routes', async () => {
      const store = useRouteGroupStore()
      vi.mocked(api.post).mockResolvedValueOnce(undefined)
      vi.mocked(api.get)
        .mockResolvedValueOnce(mockGroups)
        .mockResolvedValueOnce(mockRoutes)

      await store.syncRoutes()

      expect(api.post).toHaveBeenCalledWith('/api/v1/routes/sync')
      expect(api.get).toHaveBeenCalledWith('/api/v1/route-groups')
      expect(api.get).toHaveBeenCalledWith('/api/v1/routes', undefined)
    })
  })
})

// ─── Page 组件测试 ───

describe('RouteGroupsPage', () => {
  let wrapper: VueWrapper

  async function mountPage() {
    vi.mocked(api.get)
      .mockResolvedValueOnce(mockGroups)
      .mockResolvedValueOnce(mockRoutes)

    wrapper = mount(RouteGroupsPage, {
      attachTo: document.body,
      global: {
        stubs: {
          PlusOutlined: { template: '<span class="icon-plus" />' },
          EditOutlined: { template: '<span class="icon-edit" />' },
          DeleteOutlined: { template: '<span class="icon-delete" />' },
          SaveOutlined: { template: '<span class="icon-save" />' },
          SyncOutlined: { template: '<span class="icon-sync" />' },
        },
      },
    })

    await flushPromises()
    await nextTick()
  }

  beforeEach(() => {
    setActivePinia(createPinia())
    vi.clearAllMocks()
  })

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

  it('shows empty state when no group is selected', async () => {
    await mountPage()

    expect(wrapper.text()).toContain('请选择路由组')
    expect(wrapper.find('.ant-empty').exists()).toBe(true)
  })

  it('shows ungrouped routes table when ungrouped is selected', async () => {
    await mountPage()

    const store = useRouteGroupStore()
    store.selectedGroupId = 'ungrouped'
    await nextTick()

    expect(wrapper.text()).toContain('未分组路由')
    expect(wrapper.find('.ant-table').exists()).toBe(true)
  })

  it('shows route tree when a group is selected', async () => {
    await mountPage()

    const store = useRouteGroupStore()
    store.selectedGroupId = 1
    await nextTick()

    expect(wrapper.text()).toContain('用户管理')
    expect(wrapper.find('.ant-tree').exists()).toBe(true)
  })

  it('shows save button when a group is selected', async () => {
    await mountPage()

    const store = useRouteGroupStore()
    store.selectedGroupId = 1
    await nextTick()

    expect(wrapper.text()).toContain('保存分配')
  })

  it('renders group list in left panel', async () => {
    await mountPage()

    expect(wrapper.text()).toContain('用户管理')
    expect(wrapper.text()).toContain('订单管理')
    expect(wrapper.text()).toContain('未分组')
  })

  it('renders 同步路由 button and calls syncRoutes on click', async () => {
    await mountPage()

    const syncBtn = wrapper.findAll('.ant-btn').find((b) => b.text().includes('同步路由'))
    expect(syncBtn).toBeDefined()

    vi.mocked(api.post).mockResolvedValueOnce(undefined)
    vi.mocked(api.get)
      .mockResolvedValueOnce(mockGroups)
      .mockResolvedValueOnce(mockRoutes)

    await syncBtn!.trigger('click')
    await flushPromises()

    expect(api.post).toHaveBeenCalledWith('/api/v1/routes/sync')
  })

  it('calls batchAssignRoutes on save button click', async () => {
    await mountPage()

    const store = useRouteGroupStore()
    store.selectedGroupId = 1
    await nextTick()

    // 模拟 batchAssignRoutes 成功
    vi.mocked(api.put).mockResolvedValueOnce(undefined)
    vi.mocked(api.get)
      .mockResolvedValueOnce(mockGroups)
      .mockResolvedValueOnce(mockRoutes)

    // 点击保存按钮
    const saveBtn = wrapper.findAll('.ant-btn').find((b) => b.text().includes('保存分配'))
    expect(saveBtn).toBeDefined()
    await saveBtn!.trigger('click')
    await flushPromises()

    expect(api.put).toHaveBeenCalledWith(
      '/api/v1/route-groups/1/routes',
      expect.objectContaining({ route_ids: expect.any(Array) }),
    )
  })
})
