import { describe, it, expect, vi, beforeEach } from 'vitest'

vi.mock('../../../server/models/wikiArticle.js', () => ({
  default: {
    find: vi.fn(),
    findByIdAndUpdate: vi.fn(),
    updateMany: vi.fn()
  }
}))

vi.mock('../../../server/utils/mongoose.js', () => ({ connectDB: vi.fn() }))

import WikiArticle from '../../../server/models/wikiArticle.js'
import indexHandler from '../../../server/api/admin/wiki/index.get.js'
import patchHandler from '../../../server/api/admin/wiki/[id].patch.js'
import batchTagHandler from '../../../server/api/admin/wiki/batch-tag.post.js'
import { createMockEvent } from '../helpers/createMockEvent.js'

// --- GET /api/admin/wiki ---

describe('GET /api/admin/wiki', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    requireAdmin.mockResolvedValue({ _id: 'admin-1', role: 'admin' })
  })

  it('returns all articles with collection, title, tags', async () => {
    const articles = [
      { _id: 'a1', collection: 'General', title: 'Alpha', tags: ['co-ops'], url: '/doc/alpha', outlineId: 'o1', publishedAt: new Date() },
      { _id: 'a2', collection: 'General', title: 'Beta', tags: [], url: '/doc/beta', outlineId: 'o2', publishedAt: new Date() }
    ]
    WikiArticle.find.mockReturnValue({
      select: () => ({
        sort: () => ({
          lean: () => Promise.resolve(articles)
        })
      })
    })

    const event = createMockEvent({ method: 'GET', path: '/api/admin/wiki' })
    const result = await indexHandler(event)

    expect(result).toEqual(articles)
    expect(WikiArticle.find).toHaveBeenCalledWith({})
  })

  it('filters by collection', async () => {
    WikiArticle.find.mockReturnValue({
      select: () => ({
        sort: () => ({
          lean: () => Promise.resolve([])
        })
      })
    })

    const event = createMockEvent({
      method: 'GET',
      path: '/api/admin/wiki?collection=Guides'
    })
    await indexHandler(event)

    expect(WikiArticle.find).toHaveBeenCalledWith({ collection: 'Guides' })
  })

  it('searches by title (case-insensitive)', async () => {
    WikiArticle.find.mockReturnValue({
      select: () => ({
        sort: () => ({
          lean: () => Promise.resolve([])
        })
      })
    })

    const event = createMockEvent({
      method: 'GET',
      path: '/api/admin/wiki?search=cooperative'
    })
    await indexHandler(event)

    expect(WikiArticle.find).toHaveBeenCalledWith({
      title: { $regex: 'cooperative', $options: 'i' }
    })
  })

  it('requires admin auth (403)', async () => {
    requireAdmin.mockRejectedValue(
      createError({ statusCode: 403, statusMessage: 'Admin access required' })
    )

    const event = createMockEvent({ method: 'GET', path: '/api/admin/wiki' })
    await expect(indexHandler(event)).rejects.toMatchObject({ statusCode: 403 })
  })
})

// --- PATCH /api/admin/wiki/:id ---

describe('PATCH /api/admin/wiki/:id', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    requireAdmin.mockResolvedValue({ _id: 'admin-1', role: 'admin' })
    validateBody.mockImplementation(async (event) => readBody(event))
  })

  it('sets tags on an article', async () => {
    const updated = {
      _id: 'a1',
      title: 'Alpha',
      tags: ['co-ops', 'governance'],
      collection: 'General'
    }
    WikiArticle.findByIdAndUpdate.mockResolvedValue(updated)

    const event = createMockEvent({
      method: 'PATCH',
      path: '/api/admin/wiki/a1',
      body: { tags: ['co-ops', 'governance'] }
    })
    // Simulate getRouterParam returning the id
    event.context.params = { id: 'a1' }

    const result = await patchHandler(event)

    expect(result).toEqual(updated)
    expect(WikiArticle.findByIdAndUpdate).toHaveBeenCalledWith(
      'a1',
      { tags: ['co-ops', 'governance'] },
      { new: true }
    )
  })

  it('returns 404 for invalid article ID', async () => {
    WikiArticle.findByIdAndUpdate.mockResolvedValue(null)

    const event = createMockEvent({
      method: 'PATCH',
      path: '/api/admin/wiki/nonexistent',
      body: { tags: ['test'] }
    })
    event.context.params = { id: 'nonexistent' }

    await expect(patchHandler(event)).rejects.toMatchObject({ statusCode: 404 })
  })

  it('requires admin auth (403)', async () => {
    requireAdmin.mockRejectedValue(
      createError({ statusCode: 403, statusMessage: 'Admin access required' })
    )

    const event = createMockEvent({
      method: 'PATCH',
      path: '/api/admin/wiki/a1',
      body: { tags: ['test'] }
    })
    event.context.params = { id: 'a1' }

    await expect(patchHandler(event)).rejects.toMatchObject({ statusCode: 403 })
  })
})

// --- POST /api/admin/wiki/batch-tag ---

describe('POST /api/admin/wiki/batch-tag', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    requireAdmin.mockResolvedValue({ _id: 'admin-1', role: 'admin' })
    validateBody.mockImplementation(async (event) => readBody(event))
  })

  it('adds tags to multiple articles (add/merge semantics)', async () => {
    WikiArticle.updateMany.mockResolvedValue({ modifiedCount: 3 })

    const event = createMockEvent({
      method: 'POST',
      path: '/api/admin/wiki/batch-tag',
      body: {
        articleIds: ['a1', 'a2', 'a3'],
        addTags: ['governance', 'finance']
      }
    })

    const result = await batchTagHandler(event)

    expect(result).toEqual({ modified: 3 })
    expect(WikiArticle.updateMany).toHaveBeenCalledWith(
      { _id: { $in: ['a1', 'a2', 'a3'] } },
      { $addToSet: { tags: { $each: ['governance', 'finance'] } } }
    )
  })

  it('removes tags from multiple articles', async () => {
    WikiArticle.updateMany.mockResolvedValue({ modifiedCount: 2 })

    const event = createMockEvent({
      method: 'POST',
      path: '/api/admin/wiki/batch-tag',
      body: {
        collection: 'Guides',
        removeTags: ['outdated']
      }
    })

    const result = await batchTagHandler(event)

    expect(result).toEqual({ modified: 2 })
    expect(WikiArticle.updateMany).toHaveBeenCalledWith(
      { collection: 'Guides' },
      { $pull: { tags: { $in: ['outdated'] } } }
    )
  })

  it('does not duplicate existing tags on add', async () => {
    // $addToSet guarantees no duplicates — verify the operator is used
    WikiArticle.updateMany.mockResolvedValue({ modifiedCount: 1 })

    const event = createMockEvent({
      method: 'POST',
      path: '/api/admin/wiki/batch-tag',
      body: {
        articleIds: ['a1'],
        addTags: ['co-ops']
      }
    })

    await batchTagHandler(event)

    // Confirm $addToSet is used, not $push
    const call = WikiArticle.updateMany.mock.calls[0]
    expect(call[1]).toHaveProperty('$addToSet')
    expect(call[1]).not.toHaveProperty('$push')
  })

  it('requires admin auth (403)', async () => {
    requireAdmin.mockRejectedValue(
      createError({ statusCode: 403, statusMessage: 'Admin access required' })
    )

    const event = createMockEvent({
      method: 'POST',
      path: '/api/admin/wiki/batch-tag',
      body: {
        articleIds: ['a1'],
        addTags: ['test']
      }
    })

    await expect(batchTagHandler(event)).rejects.toMatchObject({ statusCode: 403 })
  })
})
