import { spawnSync } from 'node:child_process'
import { existsSync } from 'node:fs'
import path from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import globalSetup from '../../e2e/global-setup'

vi.mock('node:child_process', async (importOriginal) => {
  const actual = await importOriginal<typeof import('node:child_process')>()
  const spawnSync = vi.fn()
  return { ...actual, spawnSync, default: { ...actual, spawnSync } }
})
vi.mock('node:fs', async (importOriginal) => {
  const actual = await importOriginal<typeof import('node:fs')>()
  const existsSync = vi.fn()
  return { ...actual, existsSync, default: { ...actual, existsSync } }
})

const spawn = vi.mocked(spawnSync)
const exists = vi.mocked(existsSync)
const artifacts = [
  'dist/main/index.cjs',
  'dist/preload/preload.cjs',
  'dist/core/plugin/host/quick-js-worker.cjs',
  'dist/renderer/index.html',
].map((artifact) => path.join(process.cwd(), artifact))

beforeEach(() => {
  vi.resetAllMocks()
  vi.stubEnv('MOTRIX_E2E_FORCE_BUILD', '')
  spawn.mockReturnValue({ status: 0 } as ReturnType<typeof spawnSync>)
  exists.mockImplementation((file) => artifacts.includes(String(file)))
})

afterEach(() => {
  vi.unstubAllEnvs()
})

describe('Playwright global setup', () => {
  it('hydrates Electron before reusing already built artifacts', async () => {
    await globalSetup()

    expect(spawn).toHaveBeenCalledExactlyOnceWith(
      'pnpm',
      ['run', 'ensure:electron-runtime'],
      expect.objectContaining({
        cwd: process.cwd(),
        shell: process.platform === 'win32',
        stdio: 'inherit',
      })
    )
    expect(new Set(exists.mock.calls.map(([file]) => file))).toEqual(
      new Set(artifacts)
    )
    expect(spawn.mock.invocationCallOrder[0]).toBeLessThan(
      exists.mock.invocationCallOrder[0]!
    )
  })

  it.each([
    [{ error: new Error('cannot spawn pnpm') }, 'cannot spawn pnpm'],
    [{ signal: 'SIGKILL', status: null }, 'was killed by SIGKILL'],
    [{ status: 2 }, 'exited with status 2'],
    [{ status: null }, 'exited with status unknown'],
  ])(
    'stops before artifact checks when hydration fails: %j',
    async (result, message) => {
      spawn.mockReturnValue(result as ReturnType<typeof spawnSync>)

      await expect(globalSetup()).rejects.toThrow(message)
      expect(spawn).toHaveBeenCalledTimes(1)
      expect(exists).not.toHaveBeenCalled()
    }
  )

  it.each(artifacts)(
    'hydrates and rebuilds when the required artifact %s is missing',
    async (missing) => {
      exists.mockImplementation(
        (file) => artifacts.includes(String(file)) && String(file) !== missing
      )

      await globalSetup()

      expect(spawn.mock.calls.map((call) => call[1])).toEqual([
        ['run', 'ensure:electron-runtime'],
        ['build'],
      ])
    }
  )

  it('rebuilds on request even when every artifact exists', async () => {
    vi.stubEnv('MOTRIX_E2E_FORCE_BUILD', '1')

    await globalSetup()

    expect(spawn.mock.calls.map((call) => call[1])).toEqual([
      ['run', 'ensure:electron-runtime'],
      ['build'],
    ])
  })

  it('propagates a failed rebuild instead of accepting existing artifacts', async () => {
    vi.stubEnv('MOTRIX_E2E_FORCE_BUILD', '1')
    spawn.mockReturnValueOnce({ status: 0 } as ReturnType<typeof spawnSync>)
    spawn.mockReturnValueOnce({ status: 9 } as ReturnType<typeof spawnSync>)

    await expect(globalSetup()).rejects.toThrow(
      'pnpm build exited with status 9'
    )
  })
})