/**
 * Uitgebreide integratie-stijl tests voor previewEnqueueAllAction en
 * enqueueClaudeJobsBatchAction. Gebruikt realistische seed-data:
 * 2 PBIs, elk met 1 story, elk 2 taken (4 taken totaal in PBI-volgorde).
 */
import { describe, it, expect, vi, beforeEach } from 'vitest'

const {
  mockGetSession,
  mockFindFirstProduct,
  mockFindFirstSprint,
  mockFindManyTask,
  mockTransaction,
  mockExecuteRaw,
} = vi.hoisted(() => ({
  mockGetSession: vi.fn(),
  mockFindFirstProduct: vi.fn(),
  mockFindFirstSprint: vi.fn(),
  mockFindManyTask: vi.fn(),
  mockTransaction: vi.fn(),
  mockExecuteRaw: vi.fn().mockResolvedValue(undefined),
}))

vi.mock('next/cache', () => ({ revalidatePath: vi.fn() }))
vi.mock('@/lib/auth', () => ({ getSession: mockGetSession }))
vi.mock('@/lib/prisma', () => ({
  prisma: {
    task: { findMany: mockFindManyTask },
    product: { findFirst: mockFindFirstProduct },
    sprint: { findFirst: mockFindFirstSprint },
    claudeJob: { create: vi.fn() },
    $executeRaw: mockExecuteRaw,
    $transaction: mockTransaction,
  },
}))

import { previewEnqueueAllAction, enqueueClaudeJobsBatchAction } from '@/actions/claude-jobs'

const SESSION_USER = { userId: 'user-1', isDemo: false }
const SESSION_DEMO = { userId: 'demo-1', isDemo: true }
const PRODUCT_ID = 'product-1'
const SPRINT_ID = 'sprint-1'

// --- Seed helpers ---
const makePbi1Task = (id: string, status = 'TO_DO') => ({
  id,
  title: `PBI-1 Taak ${id}`,
  status,
  story: {
    id: 'story-pbi1',
    title: 'Story van PBI 1',
    code: 'ST-1',
    pbi: { id: 'pbi-1', status: 'READY', priority: 1, sort_order: 1.0 },
  },
})

const makePbi2Task = (id: string, status = 'TO_DO', pbiStatus = 'READY') => ({
  id,
  title: `PBI-2 Taak ${id}`,
  status,
  story: {
    id: 'story-pbi2',
    title: 'Story van PBI 2',
    code: 'ST-2',
    pbi: { id: 'pbi-2', status: pbiStatus, priority: 2, sort_order: 2.0 },
  },
})

const makeBatchTask = (id: string, hasActiveJob = false) => ({
  id,
  claude_jobs: hasActiveJob ? [{ id: 'job-active' }] : [],
})

// Canonical seed: [pbi1-t1, pbi1-t2, pbi2-t1, pbi2-t2]
const SEED_ALL_TODO = [
  makePbi1Task('pbi1-t1'),
  makePbi1Task('pbi1-t2'),
  makePbi2Task('pbi2-t1'),
  makePbi2Task('pbi2-t2'),
]

beforeEach(() => {
  vi.clearAllMocks()
  mockExecuteRaw.mockResolvedValue(undefined)
  mockGetSession.mockResolvedValue(SESSION_USER)
  mockFindFirstProduct.mockResolvedValue({ id: PRODUCT_ID })
  mockFindFirstSprint.mockResolvedValue({ id: SPRINT_ID })
})

// =============================================================
//  previewEnqueueAllAction
// =============================================================
describe('previewEnqueueAllAction — 2 PBI scenario', () => {
  it('geen blocker: alle 4 TO_DO taken → tasks=[4], blockerIndex=null', async () => {
    mockFindManyTask.mockResolvedValue(SEED_ALL_TODO)

    const result = await previewEnqueueAllAction(PRODUCT_ID)

    expect(result).toMatchObject({ blockerIndex: null, blockerReason: null })
    if (!('error' in result)) {
      expect(result.tasks).toHaveLength(4)
      expect(result.tasks.map(t => t.id)).toEqual(['pbi1-t1', 'pbi1-t2', 'pbi2-t1', 'pbi2-t2'])
    }
  })

  it('3e taak (pbi2-t1) REVIEW → blockerIndex=2, reden=task-review, tasks=[3]', async () => {
    mockFindManyTask.mockResolvedValue([
      makePbi1Task('pbi1-t1'),
      makePbi1Task('pbi1-t2'),
      makePbi2Task('pbi2-t1', 'REVIEW'),
      makePbi2Task('pbi2-t2'),
    ])

    const result = await previewEnqueueAllAction(PRODUCT_ID)

    expect(result).toMatchObject({ blockerIndex: 2, blockerReason: 'task-review' })
    if (!('error' in result)) {
      expect(result.tasks).toHaveLength(3)
      expect(result.tasks[2].id).toBe('pbi2-t1')
    }
  })

  it('PBI 1 BLOCKED → blockerIndex=0, reden=pbi-blocked, tasks=[1]', async () => {
    mockFindManyTask.mockResolvedValue([
      makePbi1Task('pbi1-t1', 'TO_DO'),
      makePbi1Task('pbi1-t2', 'TO_DO'),
      makePbi2Task('pbi2-t1'),
      makePbi2Task('pbi2-t2'),
    ].map((t, i) => i < 2 ? { ...t, story: { ...t.story, pbi: { ...t.story.pbi, status: 'BLOCKED' } } } : t))

    const result = await previewEnqueueAllAction(PRODUCT_ID)

    expect(result).toMatchObject({ blockerIndex: 0, blockerReason: 'pbi-blocked' })
    if (!('error' in result)) expect(result.tasks).toHaveLength(1)
  })

  it('ACTIVE job op pbi1-t1 → geskipt door where-clause, geen blocker bij resterende 3', async () => {
    // Simuleert dat pbi1-t1 een actieve job heeft: de where-clause sluit die taak uit
    mockFindManyTask.mockResolvedValue([
      makePbi1Task('pbi1-t2'),
      makePbi2Task('pbi2-t1'),
      makePbi2Task('pbi2-t2'),
    ])

    const result = await previewEnqueueAllAction(PRODUCT_ID)

    expect(result).toMatchObject({ blockerIndex: null, blockerReason: null })
    if (!('error' in result)) {
      expect(result.tasks).toHaveLength(3)
      expect(result.tasks[0].id).toBe('pbi1-t2')
    }
  })

  it('ACTIVE job op pbi1-t1 AND pbi2-t1 REVIEW → blockerIndex=1 in resterende array', async () => {
    mockFindManyTask.mockResolvedValue([
      makePbi1Task('pbi1-t2'),
      makePbi2Task('pbi2-t1', 'REVIEW'),
      makePbi2Task('pbi2-t2'),
    ])

    const result = await previewEnqueueAllAction(PRODUCT_ID)

    expect(result).toMatchObject({ blockerIndex: 1, blockerReason: 'task-review' })
    if (!('error' in result)) expect(result.tasks).toHaveLength(2)
  })

  it('demo-user → error, findMany niet aangeroepen', async () => {
    mockGetSession.mockResolvedValue(SESSION_DEMO)

    const result = await previewEnqueueAllAction(PRODUCT_ID)

    expect(result).toMatchObject({ error: expect.stringContaining('demo') })
    expect(mockFindManyTask).not.toHaveBeenCalled()
  })
})

// =============================================================
//  enqueueClaudeJobsBatchAction
// =============================================================
describe('enqueueClaudeJobsBatchAction — 2 PBI scenario', () => {
  it('happy path: 2 taskIds → 2 QUEUED ClaudeJobs in invoervolgorde', async () => {
    mockFindManyTask.mockResolvedValue([
      makeBatchTask('pbi1-t1'),
      makeBatchTask('pbi2-t1'),
    ])
    mockTransaction.mockResolvedValue([
      { id: 'job-a', task_id: 'pbi1-t1' },
      { id: 'job-b', task_id: 'pbi2-t1' },
    ])

    const result = await enqueueClaudeJobsBatchAction(PRODUCT_ID, ['pbi1-t1', 'pbi2-t1'])

    expect(result).toEqual({ success: true, count: 2 })
    expect(mockExecuteRaw).toHaveBeenCalledTimes(2)
  })

  it('IDOR: taskId van niet-toegewezen story → error, geen transaction', async () => {
    // Authorized tasks bevat maar 1 van de 2 gevraagde IDs
    mockFindManyTask.mockResolvedValue([makeBatchTask('pbi1-t1')])

    const result = await enqueueClaudeJobsBatchAction(PRODUCT_ID, ['pbi1-t1', 'other-user-task'])

    expect(result).toMatchObject({ error: expect.stringContaining('niet toegankelijk') })
    expect(mockTransaction).not.toHaveBeenCalled()
  })

  it('taak met ACTIVE job wordt overgeslagen (idempotent)', async () => {
    mockFindManyTask.mockResolvedValue([
      makeBatchTask('pbi1-t1'),
      makeBatchTask('pbi1-t2', true), // heeft actieve job → skip
      makeBatchTask('pbi2-t1'),
    ])
    mockTransaction.mockResolvedValue([
      { id: 'job-a', task_id: 'pbi1-t1' },
      { id: 'job-b', task_id: 'pbi2-t1' },
    ])

    const result = await enqueueClaudeJobsBatchAction(PRODUCT_ID, ['pbi1-t1', 'pbi1-t2', 'pbi2-t1'])

    expect(result).toEqual({ success: true, count: 2 })
    expect(mockExecuteRaw).toHaveBeenCalledTimes(2)
  })

  it('demo-user → error, geen transaction', async () => {
    mockGetSession.mockResolvedValue(SESSION_DEMO)

    const result = await enqueueClaudeJobsBatchAction(PRODUCT_ID, ['t1'])

    expect(result).toMatchObject({ error: expect.stringContaining('demo') })
    expect(mockTransaction).not.toHaveBeenCalled()
  })
})
