import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'

const mocks = vi.hoisted(() => {
  const create = vi.fn()
  const ensureAPIKey = vi.fn(() => 'test-api-key')
  const spawnConnectedTerminal = vi.fn()

  return {
    create,
    ensureAPIKey,
    spawnConnectedTerminal,
  }
})

vi.mock('e2b', () => ({
  Sandbox: {
    create: mocks.create,
  },
}))

vi.mock('../../../src/api', () => ({
  ensureAPIKey: mocks.ensureAPIKey,
}))

vi.mock('src/utils/urls', () => ({
  printDashboardSandboxInspectUrl: vi.fn(),
}))

vi.mock('src/terminal', () => ({
  spawnConnectedTerminal: mocks.spawnConnectedTerminal,
}))

describe('sandbox create lifecycle options', () => {
  beforeEach(() => {
    vi.resetModules()
    vi.clearAllMocks()

    mocks.create.mockResolvedValue({
      sandboxId: 'sandbox-id',
      setTimeout: vi.fn().mockResolvedValue(undefined),
    })
    mocks.spawnConnectedTerminal.mockResolvedValue(undefined)
  })

  afterEach(() => {
    vi.useRealTimers()
    vi.restoreAllMocks()
  })

  test('passes ontimeout and autoresume to Sandbox.create', async () => {
    const exitSpy = vi
      .spyOn(process, 'exit')
      .mockImplementation((() => undefined) as never)

    const { createCommand } = await import(
      '../../../src/commands/sandbox/create'
    )
    await createCommand('create', 'cr', false).parseAsync(
      [
        'base',
        '--detach',
        '--lifecycle.ontimeout',
        'pause',
        '--lifecycle.autoresume',
      ],
      { from: 'user' }
    )

    expect(mocks.create).toHaveBeenCalledWith('base', {
      apiKey: 'test-api-key',
      lifecycle: {
        onTimeout: 'pause',
        autoResume: true,
      },
    })
    expect(exitSpy).toHaveBeenCalledWith(0)
  })

  test('passes ontimeout without autoresume to Sandbox.create', async () => {
    const exitSpy = vi
      .spyOn(process, 'exit')
      .mockImplementation((() => undefined) as never)

    const { createCommand } = await import(
      '../../../src/commands/sandbox/create'
    )
    await createCommand('create', 'cr', false).parseAsync(
      ['base', '--detach', '--lifecycle.ontimeout', 'kill'],
      { from: 'user' }
    )

    expect(mocks.create).toHaveBeenCalledWith('base', {
      apiKey: 'test-api-key',
      lifecycle: {
        onTimeout: 'kill',
      },
    })
    expect(exitSpy).toHaveBeenCalledWith(0)
  })

  test('passes timeout seconds to Sandbox.create as milliseconds', async () => {
    const exitSpy = vi
      .spyOn(process, 'exit')
      .mockImplementation((() => undefined) as never)

    const { createCommand } = await import(
      '../../../src/commands/sandbox/create'
    )
    await createCommand('create', 'cr', false).parseAsync(
      ['base', '--detach', '--timeout', '120'],
      { from: 'user' }
    )

    expect(mocks.create).toHaveBeenCalledWith('base', {
      apiKey: 'test-api-key',
      timeoutMs: 120_000,
    })
    expect(exitSpy).toHaveBeenCalledWith(0)
  })

  test('preserves explicit timeout after attached terminal closes', async () => {
    vi.useFakeTimers()
    const exitSpy = vi
      .spyOn(process, 'exit')
      .mockImplementation((() => undefined) as never)
    const sandbox = {
      sandboxId: 'sandbox-id',
      setTimeout: vi.fn().mockResolvedValue(undefined),
    }
    mocks.create.mockResolvedValue(sandbox)

    const { createCommand } = await import(
      '../../../src/commands/sandbox/create'
    )
    await createCommand('create', 'cr', false).parseAsync(
      ['base', '--timeout', '120'],
      { from: 'user' }
    )

    expect(mocks.spawnConnectedTerminal).toHaveBeenCalledWith(sandbox)
    expect(sandbox.setTimeout).toHaveBeenCalledWith(120_000)
    expect(sandbox.setTimeout).not.toHaveBeenCalledWith(1_000)
    expect(exitSpy).toHaveBeenCalledWith(0)
    vi.useRealTimers()
  })

  test('rejects autoresume without pause on timeout', async () => {
    const exitSpy = vi
      .spyOn(process, 'exit')
      .mockImplementation((() => undefined) as never)
    const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})

    const { createCommand } = await import(
      '../../../src/commands/sandbox/create'
    )
    await createCommand('create', 'cr', false).parseAsync(
      [
        'base',
        '--detach',
        '--lifecycle.ontimeout',
        'kill',
        '--lifecycle.autoresume',
      ],
      { from: 'user' }
    )

    expect(mocks.create).not.toHaveBeenCalled()
    expect(consoleSpy).toHaveBeenCalledWith(
      expect.objectContaining({
        message: '--lifecycle.autoresume requires --lifecycle.ontimeout pause',
      })
    )
    expect(exitSpy).toHaveBeenCalledWith(1)
  })

  test('rejects invalid ontimeout option', async () => {
    const exitSpy = vi
      .spyOn(process, 'exit')
      .mockImplementation((() => undefined) as never)

    const { createCommand } = await import(
      '../../../src/commands/sandbox/create'
    )
    await expect(
      createCommand('create', 'cr', false).parseAsync(
        ['base', '--detach', '--lifecycle.ontimeout', 'hibernate'],
        { from: 'user' }
      )
    ).rejects.toThrow('--lifecycle.ontimeout must be "pause" or "kill"')

    expect(mocks.create).not.toHaveBeenCalled()
    expect(exitSpy).toHaveBeenCalledWith(1)
  })

  test('rejects zero timeout before creating sandbox', async () => {
    const exitSpy = vi
      .spyOn(process, 'exit')
      .mockImplementation((() => undefined) as never)

    const { createCommand } = await import(
      '../../../src/commands/sandbox/create'
    )
    await expect(
      createCommand('create', 'cr', false).parseAsync(
        ['base', '--detach', '--timeout', '0'],
        { from: 'user' }
      )
    ).rejects.toThrow('--timeout must be at least 30 seconds')

    expect(mocks.create).not.toHaveBeenCalled()
    expect(exitSpy).toHaveBeenCalledWith(1)
  })

  test('rejects timeout values shorter than the keep-alive interval', async () => {
    const exitSpy = vi
      .spyOn(process, 'exit')
      .mockImplementation((() => undefined) as never)

    const { createCommand } = await import(
      '../../../src/commands/sandbox/create'
    )
    await expect(
      createCommand('create', 'cr', false).parseAsync(
        ['base', '--detach', '--timeout', '29.999'],
        { from: 'user' }
      )
    ).rejects.toThrow('--timeout must be at least 30 seconds')

    expect(mocks.create).not.toHaveBeenCalled()
    expect(exitSpy).toHaveBeenCalledWith(1)
  })

  test('passes lifecycle and timeout together to Sandbox.create', async () => {
    const exitSpy = vi
      .spyOn(process, 'exit')
      .mockImplementation((() => undefined) as never)

    const { createCommand } = await import(
      '../../../src/commands/sandbox/create'
    )
    await createCommand('create', 'cr', false).parseAsync(
      [
        'base',
        '--detach',
        '--timeout',
        '120',
        '--lifecycle.ontimeout',
        'pause',
        '--lifecycle.autoresume',
      ],
      { from: 'user' }
    )

    expect(mocks.create).toHaveBeenCalledWith('base', {
      apiKey: 'test-api-key',
      lifecycle: {
        onTimeout: 'pause',
        autoResume: true,
      },
      timeoutMs: 120_000,
    })
    expect(exitSpy).toHaveBeenCalledWith(0)
  })
})
