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

const mockListTasks = vi.fn()
const mockGetTask = vi.fn()
const mockCreateTask = vi.fn()
const mockUpdateTask = vi.fn()
const mockDeleteTask = vi.fn()
const mockBulkUpdateStatus = vi.fn()
const mockRecordTaskAudit = vi.fn()
const mockRecordTaskSystemMessage = vi.fn()
const mockGetTaskStatusById = vi.fn()
const mockGetUnsatisfiedBlockers = vi.fn()
const mockMaybeEnqueueTaskStatusInvoice = vi.fn()
const mockEnqueueTaskStatusInvoicesForBulk = vi.fn()
const mockBumpProjectRevision = vi.fn()
const mockAssertTenantOwnsProject = vi.fn()
const mockAssertTenantOwnsTaskStatus = vi.fn()
const mockAssertActiveTenantAssignee = vi.fn()
const mockPublishRealtimeEvent = vi.fn()

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

vi.mock('../src/lib/system-roles', () => ({
  isOwnerOrAdminRole: () => true,
}))

vi.mock('@zync/db/queries', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@zync/db/queries')>()
  return {
    ...actual,
    listTasks: (...args: unknown[]) => mockListTasks(...args),
    getTask: (...args: unknown[]) => mockGetTask(...args),
    createTask: (...args: unknown[]) => mockCreateTask(...args),
    updateTask: (...args: unknown[]) => mockUpdateTask(...args),
    deleteTask: (...args: unknown[]) => mockDeleteTask(...args),
    bulkUpdateStatus: (...args: unknown[]) => mockBulkUpdateStatus(...args),
    recordTaskAudit: (...args: unknown[]) => mockRecordTaskAudit(...args),
    recordTaskSystemMessage: (...args: unknown[]) => mockRecordTaskSystemMessage(...args),
    getTaskStatusById: (...args: unknown[]) => mockGetTaskStatusById(...args),
    getUnsatisfiedBlockers: (...args: unknown[]) => mockGetUnsatisfiedBlockers(...args),
    maybeEnqueueTaskStatusInvoice: (...args: unknown[]) => mockMaybeEnqueueTaskStatusInvoice(...args),
    enqueueTaskStatusInvoicesForBulk: (...args: unknown[]) => mockEnqueueTaskStatusInvoicesForBulk(...args),
    bumpProjectRevision: (...args: unknown[]) => mockBumpProjectRevision(...args),
    assertTenantOwnsProject: (...args: unknown[]) => mockAssertTenantOwnsProject(...args),
    assertTenantOwnsTaskStatus: (...args: unknown[]) => mockAssertTenantOwnsTaskStatus(...args),
    assertActiveTenantAssignee: (...args: unknown[]) => mockAssertActiveTenantAssignee(...args),
    invalidTenantReferenceBody: (field: string) => ({ error: `invalid_${field}` }),
  }
})

vi.mock('@zync/realtime/server', () => ({
  publishRealtimeEvent: (...args: unknown[]) => mockPublishRealtimeEvent(...args),
}))

import { tasksRoute } from '../src/routes/tasks'

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:read', 'tasks:write', 'tasks:assign'],
    })
    c.set('db', {})
    c.env = { REALTIME_QUEUE: 'queue', QUEUE: 'queue' } as AppEnv['Bindings']
    await next()
  })
  app.route('/api/tasks', tasksRoute)
  return app
}

describe('tasks detail routes', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockAssertTenantOwnsProject.mockResolvedValue(true)
    mockAssertTenantOwnsTaskStatus.mockResolvedValue(true)
    mockAssertActiveTenantAssignee.mockResolvedValue(true)
    mockGetUnsatisfiedBlockers.mockResolvedValue([])
    mockMaybeEnqueueTaskStatusInvoice.mockResolvedValue(undefined)
    mockBumpProjectRevision.mockResolvedValue(undefined)
    mockPublishRealtimeEvent.mockResolvedValue(undefined)
    mockRecordTaskAudit.mockResolvedValue(undefined)
    mockRecordTaskSystemMessage.mockResolvedValue(undefined)
  })

  it('loads the tasks route module and serves the detail handler', async () => {
    mockGetTask.mockResolvedValue({
      id: 'task-1',
      tenant_id: 'tenant-1',
      project_id: 'project-1',
      status_id: '11111111-1111-4111-8111-111111111111',
      title: 'Ship detail route',
      description: null,
      priority: 'medium',
      assignee_id: null,
      reporter_id: 'user-1',
      due_date: null,
      estimated_hours: null,
      source: 'manual',
      external_id: null,
      position: 1,
      labels: [],
      created_at: '2026-07-01T00:00:00.000Z',
      updated_at: '2026-07-01T00:00:00.000Z',
    })

    const res = await appFor().request('/api/tasks/task-1')

    expect(res.status).toBe(200)
  })

  it('returns a task for GET /api/tasks/:id', async () => {
    mockGetTask.mockResolvedValue({
      id: 'task-1',
      tenant_id: 'tenant-1',
      project_id: 'project-1',
      status_id: '11111111-1111-4111-8111-111111111111',
      title: 'Ship detail route',
      description: null,
      priority: 'medium',
      assignee_id: null,
      reporter_id: 'user-1',
      due_date: null,
      estimated_hours: null,
      source: 'manual',
      external_id: null,
      position: 1,
      labels: [],
      created_at: '2026-07-01T00:00:00.000Z',
      updated_at: '2026-07-01T00:00:00.000Z',
    })

    const res = await appFor().request('/api/tasks/task-1')

    expect(res.status).toBe(200)
    expect(mockGetTask).toHaveBeenCalledWith({}, 'tenant-1', 'task-1')
  })

  it('rejects invalid task description JSON on PATCH', async () => {
    mockGetTask.mockResolvedValue({
      id: 'task-1',
      tenant_id: 'tenant-1',
      project_id: null,
      status_id: '11111111-1111-4111-8111-111111111111',
      title: 'Task',
      description: null,
      priority: 'medium',
      assignee_id: null,
      reporter_id: 'user-1',
      due_date: null,
      estimated_hours: null,
      source: 'manual',
      external_id: null,
      position: 1,
      labels: [],
      created_at: '2026-07-01T00:00:00.000Z',
      updated_at: '2026-07-01T00:00:00.000Z',
    })

    const res = await appFor().request('/api/tasks/task-1', {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        description: { type: 'doc', content: [{ type: 'script' }] },
      }),
    })

    expect(res.status).toBe(422)
    expect(mockUpdateTask).not.toHaveBeenCalled()
  })

  it('publishes specific status-change side effects on PATCH', async () => {
    mockGetTask.mockResolvedValue({
      id: 'task-1',
      tenant_id: 'tenant-1',
      project_id: 'project-1',
      status_id: '11111111-1111-4111-8111-111111111111',
      title: 'Task',
      description: null,
      priority: 'medium',
      assignee_id: null,
      reporter_id: 'user-1',
      due_date: null,
      estimated_hours: null,
      source: 'manual',
      external_id: null,
      position: 1,
      labels: [],
      created_at: '2026-07-01T00:00:00.000Z',
      updated_at: '2026-07-01T00:00:00.000Z',
    })
    mockUpdateTask.mockResolvedValue({
      id: 'task-1',
      tenant_id: 'tenant-1',
      project_id: 'project-1',
      status_id: '22222222-2222-4222-8222-222222222222',
      title: 'Task',
      description: null,
      priority: 'medium',
      assignee_id: null,
      reporter_id: 'user-1',
      due_date: null,
      estimated_hours: null,
      source: 'manual',
      external_id: null,
      position: 1,
      labels: [],
      created_at: '2026-07-01T00:00:00.000Z',
      updated_at: '2026-07-01T00:00:00.000Z',
    })
    mockGetTaskStatusById
      .mockResolvedValueOnce({ id: '11111111-1111-4111-8111-111111111111', name: 'TODO' })
      .mockResolvedValueOnce({ id: '22222222-2222-4222-8222-222222222222', name: 'IN_PROGRESS' })

    const res = await appFor().request('/api/tasks/task-1', {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ status_id: '22222222-2222-4222-8222-222222222222' }),
    })

    expect(res.status).toBe(200)
    expect(mockRecordTaskAudit).toHaveBeenCalledWith(
      {},
      expect.objectContaining({
        action: 'status_changed',
        previousValue: { id: '11111111-1111-4111-8111-111111111111', name: 'TODO' },
        newValue: { id: '22222222-2222-4222-8222-222222222222', name: 'IN_PROGRESS' },
      }),
    )
    expect(mockRecordTaskSystemMessage).toHaveBeenCalledWith(
      {},
      expect.objectContaining({
        text: 'Status changed from TODO to IN_PROGRESS by user-1',
      }),
    )
    expect(mockPublishRealtimeEvent).toHaveBeenCalledWith(
      'queue',
      expect.objectContaining({
        type: 'task.status_changed',
        payload: expect.objectContaining({
          taskId: 'task-1',
          fromStatus: '11111111-1111-4111-8111-111111111111',
          toStatus: '22222222-2222-4222-8222-222222222222',
        }),
      }),
    )
  })
})
