import { beforeEach, describe, expect, it, vi } from 'vitest'
import { Hono } from 'hono'
import type { AppEnv } from '../src/types'

const mockCreateTask = vi.fn()
const mockUpdateTask = vi.fn()
const mockDeleteTask = vi.fn()
const mockBulkUpdateStatus = vi.fn()
const mockGetTask = vi.fn()
const mockGetProjectIdsForTasks = vi.fn()
const mockBumpProjectRevision = vi.fn()
const mockGetTaskStatusById = vi.fn()

vi.mock('../src/middleware/guards', () => ({
  requirePermission: () => async (_c: unknown, next: () => Promise<void>) => next(),
}))

vi.mock('@zync/realtime/server', () => ({
  publishRealtimeEvent: vi.fn(),
}))

vi.mock('@zync/db/queries', () => ({
  listTasks: vi.fn(),
  createTask: (...args: unknown[]) => mockCreateTask(...args),
  updateTask: (...args: unknown[]) => mockUpdateTask(...args),
  deleteTask: (...args: unknown[]) => mockDeleteTask(...args),
  bulkUpdateStatus: (...args: unknown[]) => mockBulkUpdateStatus(...args),
  recordTaskAudit: vi.fn(),
  recordTaskSystemMessage: vi.fn(),
  getTaskStatusById: (...args: unknown[]) => mockGetTaskStatusById(...args),
  getUnsatisfiedBlockers: vi.fn().mockResolvedValue([]),
  maybeEnqueueTaskStatusInvoice: vi.fn(),
  enqueueTaskStatusInvoicesForBulk: vi.fn(),
  assertTenantOwnsProject: vi.fn().mockResolvedValue(true),
  assertTenantOwnsTaskStatus: vi.fn().mockResolvedValue(true),
  assertActiveTenantAssignee: vi.fn().mockResolvedValue(true),
  invalidTenantReferenceBody: vi.fn((field: string) => ({ error: `invalid_${field}` })),
  getTask: (...args: unknown[]) => mockGetTask(...args),
  getProjectIdsForTasks: (...args: unknown[]) => mockGetProjectIdsForTasks(...args),
  bumpProjectRevision: (...args: unknown[]) => mockBumpProjectRevision(...args),
}))

function appFor() {
  const app = new Hono<AppEnv>()
  app.use('*', async (c, next) => {
    c.set('session', {
      type: 'user',
      sub: 'user-1',
      tid: 'tenant-1',
      role: 'OWNER',
      permissions: ['tasks:write', 'tasks:delete', 'tasks:assign'],
    })
    c.set('db', {})
    await next()
  })
  return app
}

describe('tasks route critical-path invalidation', () => {
  const taskId = '11111111-1111-4111-8111-111111111111'
  const taskId2 = '22222222-2222-4222-8222-222222222222'
  const projectId1 = '33333333-3333-4333-8333-333333333333'
  const projectId2 = '44444444-4444-4444-8444-444444444444'
  const statusId1 = '55555555-5555-4555-8555-555555555555'
  const statusId2 = '66666666-6666-4666-8666-666666666666'

  beforeEach(() => {
    vi.clearAllMocks()
    mockCreateTask.mockResolvedValue({ id: taskId, project_id: projectId1 })
    mockUpdateTask.mockResolvedValue({ id: taskId, project_id: projectId2 })
    mockDeleteTask.mockResolvedValue(undefined)
    mockBulkUpdateStatus.mockResolvedValue(2)
    mockGetTask.mockResolvedValue({ id: taskId, project_id: projectId1 })
    mockGetProjectIdsForTasks.mockResolvedValue([projectId1, projectId2])
    mockBumpProjectRevision.mockResolvedValue(undefined)
    mockGetTaskStatusById.mockResolvedValue({ id: statusId2, name: 'In Progress' })
  })

  it('bumps the task project revision after create', async () => {
    const { tasksRoute } = await import('../src/routes/tasks')
    const app = appFor()
    app.route('/api/tasks', tasksRoute)

    const res = await app.request(
      '/api/tasks',
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          title: 'Task',
          project_id: projectId1,
          status_id: statusId1,
        }),
      },
      { KV: {} } as AppEnv['Bindings'],
    )

    expect(res.status).toBe(201)
    expect(mockBumpProjectRevision).toHaveBeenCalledWith({ KV: {} }, 'tenant-1', projectId1)
  })

  it('bumps both old and new project revisions after moving a task across projects', async () => {
    const { tasksRoute } = await import('../src/routes/tasks')
    const app = appFor()
    app.route('/api/tasks', tasksRoute)

    const res = await app.request(
      `/api/tasks/${taskId}`,
      {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          project_id: projectId2,
          status_id: statusId2,
        }),
      },
      { KV: {} } as AppEnv['Bindings'],
    )

    expect(res.status).toBe(200)
    expect(mockGetTask).toHaveBeenCalledWith({}, 'tenant-1', taskId)
    expect(mockBumpProjectRevision).toHaveBeenNthCalledWith(1, { KV: {} }, 'tenant-1', projectId1)
    expect(mockBumpProjectRevision).toHaveBeenNthCalledWith(2, { KV: {} }, 'tenant-1', projectId2)
  })

  it('bumps the task project revision after delete', async () => {
    const { tasksRoute } = await import('../src/routes/tasks')
    const app = appFor()
    app.route('/api/tasks', tasksRoute)

    const res = await app.request(
      `/api/tasks/${taskId}`,
      { method: 'DELETE' },
      { KV: {} } as AppEnv['Bindings'],
    )

    expect(res.status).toBe(204)
    expect(mockGetTask).toHaveBeenCalledWith({}, 'tenant-1', taskId)
    expect(mockBumpProjectRevision).toHaveBeenCalledWith({ KV: {} }, 'tenant-1', projectId1)
  })

  it('bumps each affected project revision after bulk status updates', async () => {
    const { tasksRoute } = await import('../src/routes/tasks')
    const app = appFor()
    app.route('/api/tasks', tasksRoute)

    const res = await app.request(
      '/api/tasks/bulk',
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          taskIds: [taskId, taskId2],
          statusId: statusId2,
        }),
      },
      { KV: {} } as AppEnv['Bindings'],
    )

    expect(res.status).toBe(200)
    expect(mockGetProjectIdsForTasks).toHaveBeenCalledWith({}, 'tenant-1', [taskId, taskId2])
    expect(mockBumpProjectRevision).toHaveBeenNthCalledWith(1, { KV: {} }, 'tenant-1', projectId1)
    expect(mockBumpProjectRevision).toHaveBeenNthCalledWith(2, { KV: {} }, 'tenant-1', projectId2)
  })
})
