/**
 * Per-task enqueue-acties zijn gedeprecateerd. cancelClaudeJobAction blijft
 * actief — gebruikt voor het annuleren van losse jobs (bv. idea-jobs).
 */
import { describe, it, expect, vi, beforeEach } from 'vitest'

const {
  mockGetSession,
  mockFindFirstJob,
  mockUpdateJob,
  mockUpdateManyJob,
  mockUpdateManySprintTaskExecution,
  mockTransaction,
  mockExecuteRaw,
} = vi.hoisted(() => {
  const mockUpdateManyJob = vi.fn()
  const mockUpdateManySprintTaskExecution = vi.fn()
  const mockTransaction = vi.fn()
  return {
    mockGetSession: vi.fn(),
    mockFindFirstJob: vi.fn(),
    mockUpdateJob: vi.fn(),
    mockUpdateManyJob,
    mockUpdateManySprintTaskExecution,
    mockTransaction,
    mockExecuteRaw: vi.fn().mockResolvedValue(undefined),
  }
})

vi.mock('next/cache', () => ({ revalidatePath: vi.fn() }))
vi.mock('@/lib/auth', () => ({ getSession: mockGetSession }))
vi.mock('@/lib/prisma', () => ({
  prisma: {
    claudeJob: {
      findFirst: mockFindFirstJob,
      update: mockUpdateJob,
      updateMany: mockUpdateManyJob,
    },
    sprintTaskExecution: {
      updateMany: mockUpdateManySprintTaskExecution,
    },
    $transaction: mockTransaction,
    $executeRaw: mockExecuteRaw,
  },
}))

import {
  enqueueClaudeJobAction,
  enqueueAllTodoJobsAction,
  cancelClaudeJobAction,
  restartClaudeJobAction,
} from '@/actions/claude-jobs'

const SESSION_USER = { userId: 'user-1', isDemo: false }

beforeEach(() => {
  vi.clearAllMocks()
  mockExecuteRaw.mockResolvedValue(undefined)
  mockTransaction.mockImplementation(async (fn: (tx: unknown) => Promise<unknown>) =>
    fn({
      claudeJob: { updateMany: mockUpdateManyJob },
      sprintTaskExecution: { updateMany: mockUpdateManySprintTaskExecution },
    })
  )
})

describe('enqueueClaudeJobAction (deprecated)', () => {
  it('retourneert een deprecation-error', async () => {
    const result = await enqueueClaudeJobAction('task-1')
    expect(result).toMatchObject({ error: expect.stringContaining('Start Sprint') })
  })
})

describe('enqueueAllTodoJobsAction (deprecated)', () => {
  it('retourneert een deprecation-error', async () => {
    const result = await enqueueAllTodoJobsAction('prod-1')
    expect(result).toMatchObject({ error: expect.stringContaining('Start Sprint') })
  })
})

describe('cancelClaudeJobAction', () => {
  it('cancelt een actieve job', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    mockFindFirstJob.mockResolvedValue({
      id: 'job-1',
      status: 'QUEUED',
      task_id: 'task-1',
      product_id: 'prod-1',
    })
    mockUpdateJob.mockResolvedValue(undefined)

    const result = await cancelClaudeJobAction('job-1')

    expect(result).toEqual({ success: true })
    expect(mockUpdateJob).toHaveBeenCalledWith({
      where: { id: 'job-1' },
      data: expect.objectContaining({ status: 'CANCELLED' }),
    })
  })

  it('weigert demo-sessie', async () => {
    mockGetSession.mockResolvedValue({ userId: 'demo', isDemo: true })

    const result = await cancelClaudeJobAction('job-1')
    expect(result).toMatchObject({ error: expect.stringContaining('demo') })
    expect(mockUpdateJob).not.toHaveBeenCalled()
  })

  it('retourneert error als job niet gevonden', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    mockFindFirstJob.mockResolvedValue(null)

    const result = await cancelClaudeJobAction('nonexistent')
    expect(result).toMatchObject({ error: expect.stringContaining('niet gevonden') })
  })

  it('weigert wanneer job niet meer actief is', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    mockFindFirstJob.mockResolvedValue({
      id: 'job-1',
      status: 'DONE',
      task_id: 'task-1',
      product_id: 'prod-1',
    })

    const result = await cancelClaudeJobAction('job-1')
    expect(result).toMatchObject({ error: expect.stringContaining('actieve') })
  })
})

describe('restartClaudeJobAction', () => {
  const FAILED_JOB = {
    id: 'job-1',
    status: 'FAILED',
    kind: 'TASK_IMPLEMENTATION',
    task_id: 'task-1',
    idea_id: null,
    sprint_run_id: null,
    product_id: 'prod-1',
  }

  it('reset een FAILED job naar QUEUED (happy path)', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    mockFindFirstJob.mockResolvedValue(FAILED_JOB)
    mockUpdateManyJob.mockResolvedValue({ count: 1 })

    const result = await restartClaudeJobAction('job-1')

    expect(result).toEqual({ success: true })
    expect(mockUpdateManyJob).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({ id: 'job-1', status: { in: ['FAILED', 'CANCELLED', 'SKIPPED'] } }),
        data: expect.objectContaining({ status: 'QUEUED' }),
      })
    )
    expect(mockExecuteRaw).toHaveBeenCalled()
  })

  it('reset een CANCELLED job naar QUEUED', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    mockFindFirstJob.mockResolvedValue({ ...FAILED_JOB, status: 'CANCELLED' })
    mockUpdateManyJob.mockResolvedValue({ count: 1 })

    const result = await restartClaudeJobAction('job-1')
    expect(result).toEqual({ success: true })
  })

  it('reset een SKIPPED job naar QUEUED', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    mockFindFirstJob.mockResolvedValue({ ...FAILED_JOB, status: 'SKIPPED' })
    mockUpdateManyJob.mockResolvedValue({ count: 1 })

    const result = await restartClaudeJobAction('job-1')
    expect(result).toEqual({ success: true })
  })

  it('weigert demo-sessie', async () => {
    mockGetSession.mockResolvedValue({ userId: 'demo', isDemo: true })

    const result = await restartClaudeJobAction('job-1')
    expect(result).toMatchObject({ error: expect.stringContaining('demo') })
    expect(mockUpdateManyJob).not.toHaveBeenCalled()
  })

  it('retourneert error als job niet gevonden', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    mockFindFirstJob.mockResolvedValue(null)

    const result = await restartClaudeJobAction('job-1')
    expect(result).toMatchObject({ error: expect.stringContaining('niet gevonden') })
  })

  it('weigert wanneer job een niet-restartbare status heeft', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    mockFindFirstJob.mockResolvedValue({ ...FAILED_JOB, status: 'DONE' })

    const result = await restartClaudeJobAction('job-1')
    expect(result).toMatchObject({ error: expect.stringContaining('mislukte') })
    expect(mockUpdateManyJob).not.toHaveBeenCalled()
  })

  it('retourneert error bij race-conditie (updateMany count === 0)', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    mockFindFirstJob.mockResolvedValue(FAILED_JOB)
    mockUpdateManyJob.mockResolvedValue({ count: 0 })

    const result = await restartClaudeJobAction('job-1')
    expect(result).toMatchObject({ error: expect.stringContaining('gewijzigd') })
  })

  it('reset ook SprintTaskExecution-rows bij SPRINT_IMPLEMENTATION', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    mockFindFirstJob.mockResolvedValue({
      ...FAILED_JOB,
      kind: 'SPRINT_IMPLEMENTATION',
      sprint_run_id: 'run-1',
    })
    mockUpdateManyJob.mockResolvedValue({ count: 1 })
    mockUpdateManySprintTaskExecution.mockResolvedValue({ count: 3 })

    const result = await restartClaudeJobAction('job-1')

    expect(result).toEqual({ success: true })
    expect(mockUpdateManySprintTaskExecution).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { sprint_job_id: 'job-1' },
        data: expect.objectContaining({ status: 'PENDING' }),
      })
    )
  })

  it('reset geen SprintTaskExecution-rows bij TASK_IMPLEMENTATION', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    mockFindFirstJob.mockResolvedValue(FAILED_JOB)
    mockUpdateManyJob.mockResolvedValue({ count: 1 })

    await restartClaudeJobAction('job-1')

    expect(mockUpdateManySprintTaskExecution).not.toHaveBeenCalled()
  })
})
