// @vitest-environment node
import { afterEach, describe, expect, it, vi } from 'vitest'

const cmsApi = vi.fn()
vi.mock('../../lib/api.js', () => ({
  cmsApi: (...args: unknown[]) => cmsApi(...args),
}))

const { fetchPages, fetchPagesDataset, bulkUpdatePages, bulkChangePageTags } =
  await import('../../lib/pages-service.js')

afterEach(() => {
  cmsApi.mockReset()
})

function pageDoc(
  id: string,
  over: Record<string, unknown> = {},
  data: Record<string, unknown> = {},
) {
  return {
    id,
    collection: 'pages',
    title: id,
    slug: id,
    status: 'PUBLISHED',
    createdById: 'u1',
    updatedById: 'u1',
    publishedAt: '2026-01-01T00:00:00Z',
    scheduledAt: null,
    createdAt: '2026-01-01T00:00:00Z',
    updatedAt: '2026-01-01T00:00:00Z',
    data: { path: `/${id}`, ...data },
    ...over,
  }
}

// Route the three reads fetchPagesDataset performs (users, tags, pages).
function routeReads(pages: unknown[], tags: unknown[] = []) {
  cmsApi.mockImplementation((endpoint: string) => {
    if (endpoint.startsWith('/users')) {
      return Promise.resolve({
        data: { users: [{ id: 'u1', name: 'Jane Doe', email: 'jane@example.com' }] },
        status: 200,
      })
    }
    if (endpoint.startsWith('/collections/page-tags')) {
      return Promise.resolve({ data: { docs: tags, total: tags.length }, status: 200 })
    }
    if (endpoint.startsWith('/collections/pages')) {
      return Promise.resolve({ data: { docs: pages, total: pages.length }, status: 200 })
    }
    return Promise.resolve({ data: null, status: 200 })
  })
}

describe('fetchPagesDataset', () => {
  it('returns every page plus tag counts derived from the full set', async () => {
    routeReads(
      [
        pageDoc('p1', { status: 'DRAFT' }, { tags: ['a'] }),
        pageDoc('p2', {}, { tags: ['a', 'b'] }),
        pageDoc('p3'), // untagged
      ],
      [
        { id: 't1', slug: 'a', title: 'Alpha', data: { color: 'green', sortOrder: 0 } },
        { id: 't2', slug: 'b', title: 'Beta', data: { sortOrder: 1 } },
      ],
    )

    const ds = await fetchPagesDataset()
    expect(ds.pages).toHaveLength(3)
    expect(ds.tags.find((t) => t.slug === 'a')?.pageCount).toBe(2)
    expect(ds.tags.find((t) => t.slug === 'b')?.pageCount).toBe(1)
    expect(ds.authors).toHaveLength(1)
  })
})

describe('fetchPages counts', () => {
  it('reports grandTotal as the unfiltered count, independent of tag membership', async () => {
    // p3 is untagged: a tag-sum total would undercount (the I-1 bug). The
    // grandTotal must reflect all three pages.
    routeReads(
      [pageDoc('p1', {}, { tags: ['a'] }), pageDoc('p2', {}, { tags: ['a'] }), pageDoc('p3')],
      [{ id: 't1', slug: 'a', title: 'Alpha', data: {} }],
    )

    const res = await fetchPages()
    expect(res.grandTotal).toBe(3)
    expect(res.total).toBe(3)
  })

  it('paginates the flat view and keeps total/grandTotal stable', async () => {
    routeReads([pageDoc('p1'), pageDoc('p2'), pageDoc('p3')])

    const first = await fetchPages({ page: 1, pageSize: 2 })
    expect(first.pages).toHaveLength(2)
    expect(first.total).toBe(3)
    expect(first.grandTotal).toBe(3)

    const second = await fetchPages({ page: 2, pageSize: 2 })
    expect(second.pages).toHaveLength(1)
    expect(second.total).toBe(3)
    expect(second.grandTotal).toBe(3)
  })

  it('total reflects the filter while grandTotal does not', async () => {
    routeReads([
      pageDoc('p1', { status: 'DRAFT' }),
      pageDoc('p2', { status: 'PUBLISHED' }),
      pageDoc('p3', { status: 'PUBLISHED' }),
    ])

    const res = await fetchPages({ status: 'DRAFT' })
    expect(res.total).toBe(1)
    expect(res.grandTotal).toBe(3)
  })
})

describe('bulk operations', () => {
  it('counts ok/failed across a batch and issues one request per id', async () => {
    cmsApi.mockImplementation((endpoint: string) =>
      Promise.resolve(
        endpoint.includes('bad') ? { error: 'nope', status: 500 } : { data: {}, status: 200 },
      ),
    )

    const result = await bulkUpdatePages(['a', 'bad', 'c'], { status: 'PUBLISHED' })
    expect(result).toEqual({ ok: 2, failed: 1 })
    expect(cmsApi).toHaveBeenCalledTimes(3)
  })

  it('does not drop or double-process work above the concurrency limit', async () => {
    cmsApi.mockResolvedValue({ data: {}, status: 200 })
    const ids = Array.from({ length: 12 }, (_, i) => `p${i}`)
    const result = await bulkUpdatePages(ids, { status: 'DRAFT' })
    expect(result).toEqual({ ok: 12, failed: 0 })
    expect(cmsApi).toHaveBeenCalledTimes(12)
  })

  it('treats a thrown request as a failure rather than aborting the batch', async () => {
    let call = 0
    cmsApi.mockImplementation(() => {
      call++
      if (call === 2) return Promise.reject(new Error('network'))
      return Promise.resolve({ data: {}, status: 200 })
    })

    const result = await bulkChangePageTags(
      [
        { id: 'a', tags: [], primaryTagId: null },
        { id: 'b', tags: [], primaryTagId: null },
        { id: 'c', tags: [], primaryTagId: null },
      ],
      { add: ['x'] },
    )
    expect(result.ok).toBe(2)
    expect(result.failed).toBe(1)
  })
})
