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

vi.mock('next/cache', () => ({ revalidatePath: vi.fn() }))
vi.mock('next/headers', () => ({
  cookies: vi.fn().mockResolvedValue({
    set: vi.fn(),
    get: vi.fn(),
    delete: vi.fn(),
  }),
}))
vi.mock('iron-session', () => ({
  getIronSession: vi.fn().mockResolvedValue({ userId: 'user-1', isDemo: false }),
}))
vi.mock('@/lib/session', () => ({
  sessionOptions: { cookieName: 'test', password: 'test' },
}))
vi.mock('@/lib/product-access', () => ({
  productAccessFilter: vi.fn().mockReturnValue({}),
  getAccessibleProduct: vi.fn().mockResolvedValue({ id: 'product-1' }),
}))
vi.mock('@/lib/rate-limit', () => ({
  enforceUserRateLimit: vi.fn().mockReturnValue(null),
}))
vi.mock('@/lib/code-server', () => ({
  createWithCodeRetry: vi.fn(),
  generateNextSprintCode: vi.fn(),
}))
vi.mock('@/lib/active-sprint', () => ({
  setActiveSprintInSettings: vi.fn().mockResolvedValue(undefined),
}))
vi.mock('@/lib/prisma', () => ({
  prisma: {
    sprint: {
      findFirst: vi.fn(),
      update: vi.fn(),
    },
    story: {
      findMany: vi.fn(),
      updateMany: vi.fn(),
    },
    task: {
      findMany: vi.fn(),
      updateMany: vi.fn(),
    },
    $transaction: vi.fn(),
  },
}))

import { prisma } from '@/lib/prisma'
import { updateSprintAction } from '@/actions/sprints'

type Mocked = {
  sprint: {
    findFirst: ReturnType<typeof vi.fn>
    update: ReturnType<typeof vi.fn>
  }
}
const mockPrisma = prisma as unknown as Mocked

beforeEach(() => {
  vi.clearAllMocks()
  mockPrisma.sprint.findFirst.mockReset().mockResolvedValue({
    id: 'sprint-1',
    product_id: 'product-1',
  })
  mockPrisma.sprint.update.mockReset().mockResolvedValue({})
})

describe('updateSprintAction', () => {
  it('updates sprint_goal alone', async () => {
    const result = await updateSprintAction({
      sprintId: 'sprint-1',
      fields: { goal: 'Nieuw doel' },
    })

    expect('success' in result).toBe(true)
    expect(mockPrisma.sprint.update).toHaveBeenCalledWith({
      where: { id: 'sprint-1' },
      data: { sprint_goal: 'Nieuw doel' },
    })
  })

  it('updates dates only', async () => {
    await updateSprintAction({
      sprintId: 'sprint-1',
      fields: { startAt: '2026-06-01', endAt: '2026-06-14' },
    })

    expect(mockPrisma.sprint.update).toHaveBeenCalledWith({
      where: { id: 'sprint-1' },
      data: {
        start_date: new Date('2026-06-01'),
        end_date: new Date('2026-06-14'),
      },
    })
  })

  it('accepts null to clear a date', async () => {
    await updateSprintAction({
      sprintId: 'sprint-1',
      fields: { startAt: null },
    })

    expect(mockPrisma.sprint.update).toHaveBeenCalledWith({
      where: { id: 'sprint-1' },
      data: { start_date: null },
    })
  })

  it('rejects when sprint not accessible', async () => {
    mockPrisma.sprint.findFirst.mockResolvedValue(null)

    const result = await updateSprintAction({
      sprintId: 'sprint-1',
      fields: { goal: 'x' },
    })

    expect('error' in result).toBe(true)
    if ('error' in result) {
      expect(result.code).toBe(403)
    }
    expect(mockPrisma.sprint.update).not.toHaveBeenCalled()
  })

  it('rejects empty goal', async () => {
    const result = await updateSprintAction({
      sprintId: 'sprint-1',
      fields: { goal: '' },
    })

    expect('error' in result).toBe(true)
    expect(mockPrisma.sprint.update).not.toHaveBeenCalled()
  })

  it('rejects when no fields are supplied', async () => {
    const result = await updateSprintAction({
      sprintId: 'sprint-1',
      fields: {},
    })

    // Schema-refine should reject; OR action treats empty data as no-op success.
    // Current implementation: refine forces minstens één veld → 422 error.
    expect('error' in result).toBe(true)
    if ('error' in result) {
      expect(result.code).toBe(422)
    }
  })
})
