import path from 'path'
import assert from 'assert'
import { NextConfig } from 'next'
import { InstallCommand, NextInstance, PackageJson } from './next-modes/base'
import { NextDevInstance } from './next-modes/next-dev'
import { NextStartInstance } from './next-modes/next-start'
import { NextDeployInstance } from './next-modes/next-deploy'

// increase timeout to account for yarn install time
jest.setTimeout((process.platform === 'win32' ? 240 : 180) * 1000)

const testsFolder = path.join(__dirname, '..')

let testFile
const testFileRegex = /\.test\.(js|tsx?)/

const visitedModules = new Set()
const checkParent = (mod) => {
  if (!mod?.parent || visitedModules.has(mod)) return
  testFile = mod.parent.filename || ''
  visitedModules.add(mod)

  if (!testFileRegex.test(testFile)) {
    checkParent(mod.parent)
  }
}
checkParent(module)

process.env.TEST_FILE_PATH = testFile

let testMode = process.env.NEXT_TEST_MODE

if (!testFileRegex.test(testFile)) {
  throw new Error(
    `e2e-utils imported from non-test file ${testFile} (must end with .test.(js,ts,tsx)`
  )
}

const testFolderModes = ['e2e', 'development', 'production']

const testModeFromFile = testFolderModes.find((mode) =>
  testFile.startsWith(path.join(testsFolder, mode))
)

if (testModeFromFile === 'e2e') {
  const validE2EModes = ['dev', 'start', 'deploy']

  if (!process.env.NEXT_TEST_JOB && !testMode) {
    console.warn('Warn: no NEXT_TEST_MODE set, using default of start')
    testMode = 'start'
  }
  assert(
    validE2EModes.includes(testMode),
    `NEXT_TEST_MODE must be one of ${validE2EModes.join(
      ', '
    )} for e2e tests but received ${testMode}`
  )
} else if (testModeFromFile === 'development') {
  testMode = 'dev'
} else if (testModeFromFile === 'production') {
  testMode = 'start'
}

if (testMode === 'dev') {
  ;(global as any).isNextDev = true
} else if (testMode === 'deploy') {
  ;(global as any).isNextDeploy = true
} else {
  ;(global as any).isNextStart = true
}

if (!testMode) {
  throw new Error(
    `No 'NEXT_TEST_MODE' set in environment, this is required for e2e-utils`
  )
}
console.log(`Using test mode: ${testMode} in test folder ${testModeFromFile}`)

/**
 * FileRef is wrapper around a file path that is meant be copied
 * to the location where the next instance is being created
 */
export class FileRef {
  public fsPath: string

  constructor(path: string) {
    this.fsPath = path
  }
}

let nextInstance: NextInstance | undefined = undefined

if (typeof afterAll === 'function') {
  afterAll(async () => {
    if (nextInstance) {
      await nextInstance.destroy()
      throw new Error(
        `next instance not destroyed before exiting, make sure to call .destroy() after the tests after finished`
      )
    }
  })
}

/**
 * Sets up and manages a Next.js instance in the configured
 * test mode. The next instance will be isolated from the monorepo
 * to prevent relying on modules that shouldn't be
 */
export async function createNext(opts: {
  files: {
    [filename: string]: string | FileRef
  }
  dependencies?: {
    [name: string]: string
  }
  nextConfig?: NextConfig
  skipStart?: boolean
  installCommand?: InstallCommand
  buildCommand?: string
  packageJson?: PackageJson
  startCommand?: string
  packageLockPath?: string
}): Promise<NextInstance> {
  try {
    if (nextInstance) {
      throw new Error(`createNext called without destroying previous instance`)
    }

    if (testMode === 'dev') {
      // next dev
      nextInstance = new NextDevInstance(opts)
    } else if (testMode === 'deploy') {
      // Vercel
      nextInstance = new NextDeployInstance(opts)
    } else {
      // next build + next start
      nextInstance = new NextStartInstance(opts)
    }

    nextInstance.on('destroy', () => {
      nextInstance = undefined
    })

    await nextInstance.setup()

    if (!opts.skipStart) {
      await nextInstance.start()
    }
    return nextInstance!
  } catch (err) {
    require('console').error('Failed to create next instance', err)
    try {
      nextInstance.destroy()
    } catch (_) {}
    process.exit(1)
  }
}
