import path from 'path'
import { assert } from 'vitest'

import { WriteEntry } from '../../../src/sandbox/filesystem'
import { isDebug, sandboxTest } from '../../setup.js'

sandboxTest('write file', async ({ sandbox }) => {
  const filename = 'test_write.txt'
  const content = 'This is a test file.'

  // Attempt to write with undefined path and content
  await sandbox.files
    // @ts-ignore
    .write(undefined, content)
    .then((e) => {
      assert.isUndefined(e)
    })
    .catch((err) => {
      assert.instanceOf(err, Error)
      assert.include(err.message, 'Path or files are required')
    })

  const info = await sandbox.files.write(filename, content)
  assert.isFalse(Array.isArray(info))
  assert.equal(info.name, filename)
  assert.equal(info.type, 'file')
  assert.equal(info.path, `/home/user/${filename}`)

  const exists = await sandbox.files.exists(filename)
  assert.isTrue(exists)
  const readContent = await sandbox.files.read(filename)
  assert.equal(readContent, content)

  if (isDebug) {
    await sandbox.files.remove(filename)
  }
})

sandboxTest('write multiple files', async ({ sandbox }) => {
  const numTestFiles = 10

  // Attempt to write with empty files array
  const emptyInfo = await sandbox.files.write([])
  assert.isTrue(Array.isArray(emptyInfo))
  assert.equal(emptyInfo.length, 0)

  // Attempt to write with undefined path and file array
  await sandbox.files
    // @ts-ignore
    .write(undefined, [
      { path: 'one_test_file.txt', data: 'This is a test file.' },
    ])
    .then((e) => {
      assert.isUndefined(e)
    })
    .catch((err) => {
      assert.instanceOf(err, Error)
      assert.include(err.message, 'Path or files are required')
    })

  // Attempt to write with path and file array
  await sandbox.files
    // @ts-ignore
    .write('/path/to/file', [
      { path: 'one_test_file.txt', data: 'This is a test file.' },
    ])
    .then((e) => {
      assert.isUndefined(e)
    })
    .catch((err) => {
      assert.instanceOf(err, Error)
      assert.include(
        err.message,
        'Cannot specify both path and array of files. You have to specify either path and data for a single file or an array for multiple files.'
      )
    })

  // Attempt to write with one file in array
  const info = await sandbox.files.write([
    { path: 'one_test_file.txt', data: 'This is a test file.' },
  ])
  assert.isTrue(Array.isArray(info))
  assert.equal(info[0].name, 'one_test_file.txt')
  assert.equal(info[0].type, 'file')
  assert.equal(info[0].path, '/home/user/one_test_file.txt')

  // Attempt to write with multiple files in array
  const files: WriteEntry[] = []

  for (let i = 0; i < numTestFiles; i++) {
    let path = ''
    if (i % 2 == 0) {
      path = `/${i}/multi_test_file${i}.txt`
    } else {
      path = `/home/user/multi_test_file${i}.txt`
    }

    files.push({
      path: path,
      data: `This is a test file ${i}.`,
    })
  }

  const infos = await sandbox.files.write(files)

  assert.isTrue(Array.isArray(infos))
  assert.equal(infos.length, files.length)

  // Attempt to write with multiple files in array
  for (let i = 0; i < files.length; i++) {
    const file = files[i]
    const info = infos[i]

    assert.equal(info.name, path.basename(file.path))
    assert.equal(info.path, file.path)
    assert.equal(info.type, 'file')

    const exists = await sandbox.files.exists(file.path)
    assert.isTrue(exists)
    const readContent = await sandbox.files.read(file.path)
    assert.equal(readContent, file.data)
  }

  if (isDebug) {
    for (const file of files) {
      await sandbox.files.remove(file.path)
    }
  }
})

sandboxTest('write file', async ({ sandbox }) => {
  const filename = 'test_write.txt'
  const content = 'This is a test file.'

  const info = await sandbox.files.write(filename, content)
  assert.isFalse(Array.isArray(info))
  assert.equal(info.name, filename)
  assert.equal(info.type, 'file')
  assert.equal(info.path, `/home/user/${filename}`)

  const exists = await sandbox.files.exists(filename)
  assert.isTrue(exists)
  const readContent = await sandbox.files.read(filename)
  assert.equal(readContent, content)

  if (isDebug) {
    await sandbox.files.remove(filename)
  }
})

sandboxTest('overwrite file', async ({ sandbox }) => {
  const filename = 'test_overwrite.txt'
  const initialContent = 'Initial content.'
  const newContent = 'New content.'

  await sandbox.files.write(filename, initialContent)
  await sandbox.files.write(filename, newContent)
  const readContent = await sandbox.files.read(filename)
  assert.equal(readContent, newContent)

  if (isDebug) {
    await sandbox.files.remove(filename)
  }
})

sandboxTest('write to non-existing directory', async ({ sandbox }) => {
  const filename = 'non_existing_dir/test_write.txt'
  const content = 'This should succeed too.'

  await sandbox.files.write(filename, content)
  const exists = await sandbox.files.exists(filename)
  assert.isTrue(exists)
  const readContent = await sandbox.files.read(filename)
  assert.equal(readContent, content)

  if (isDebug) {
    await sandbox.files.remove(filename)
  }
})

sandboxTest('writeFiles with empty array', async ({ sandbox }) => {
  const emptyInfo = await sandbox.files.writeFiles([])
  assert.isTrue(Array.isArray(emptyInfo))
  assert.equal(emptyInfo.length, 0)
})

sandboxTest('writeFiles with multiple files', async ({ sandbox }) => {
  const numTestFiles = 10
  const files: WriteEntry[] = []

  for (let i = 0; i < numTestFiles; i++) {
    const filePath = `writefiles_test_${i}.txt`

    files.push({
      path: filePath,
      data: `This is a test file ${i}.`,
    })
  }

  const infos = await sandbox.files.writeFiles(files)

  assert.isTrue(Array.isArray(infos))
  assert.equal(infos.length, files.length)

  for (let i = 0; i < files.length; i++) {
    const file = files[i]
    const info = infos[i]

    assert.equal(info.name, path.basename(file.path))
    assert.equal(info.path, `/home/user/${file.path}`)
    assert.equal(info.type, 'file')

    const exists = await sandbox.files.exists(info.path)
    assert.isTrue(exists)

    const readContent = await sandbox.files.read(info.path)
    assert.equal(readContent, file.data)
  }

  if (isDebug) {
    for (const file of files) {
      await sandbox.files.remove(file.path)
    }
  }
})

sandboxTest('writeFiles with different data types', async ({ sandbox }) => {
  const textData = 'Text string data'
  const arrayBufferData = new TextEncoder().encode('ArrayBuffer data').buffer
  const blobData = new Blob(['Blob data'], { type: 'text/plain' })
  const streamContent = 'ReadableStream data'
  const encoder = new TextEncoder()
  const streamData = new ReadableStream({
    start(controller) {
      controller.enqueue(encoder.encode(streamContent))
      controller.close()
    },
  })

  const files: WriteEntry[] = [
    { path: 'writefiles_text.txt', data: textData },
    { path: 'writefiles_arraybuffer.txt', data: arrayBufferData },
    { path: 'writefiles_blob.txt', data: blobData },
    { path: 'writefiles_stream.txt', data: streamData },
  ]

  const infos = await sandbox.files.writeFiles(files)

  assert.equal(infos.length, 4)

  // Verify text file
  const textContent = await sandbox.files.read('writefiles_text.txt')
  assert.equal(textContent, textData)

  // Verify ArrayBuffer file
  const arrayBufferContent = await sandbox.files.read(
    'writefiles_arraybuffer.txt'
  )
  assert.equal(arrayBufferContent, 'ArrayBuffer data')

  // Verify Blob file
  const blobContent = await sandbox.files.read('writefiles_blob.txt')
  assert.equal(blobContent, 'Blob data')

  // Verify ReadableStream file
  const streamFileContent = await sandbox.files.read('writefiles_stream.txt')
  assert.equal(streamFileContent, streamContent)

  if (isDebug) {
    for (const file of files) {
      await sandbox.files.remove(file.path)
    }
  }
})

sandboxTest('writeFiles creates parent directories', async ({ sandbox }) => {
  const files: WriteEntry[] = [
    {
      path: 'writefiles_nested_dir/nested/file1.txt',
      data: 'Content in nested directory',
    },
  ]

  const infos = await sandbox.files.writeFiles(files)

  assert.equal(infos.length, 1)
  assert.equal(
    infos[0].path,
    '/home/user/writefiles_nested_dir/nested/file1.txt'
  )

  const exists = await sandbox.files.exists(infos[0].path)
  assert.isTrue(exists)

  const content = await sandbox.files.read(infos[0].path)
  assert.equal(content, 'Content in nested directory')

  if (isDebug) {
    await sandbox.files.remove(files[0].path)
  }
})

sandboxTest('writeFiles overwrites existing files', async ({ sandbox }) => {
  const filename = 'writefiles_overwrite.txt'
  const initialContent = 'Initial content'
  const newContent = 'New content'

  // Write initial file
  await sandbox.files.writeFiles([{ path: filename, data: initialContent }])

  let readContent = await sandbox.files.read(filename)
  assert.equal(readContent, initialContent)

  // Overwrite with new content
  await sandbox.files.writeFiles([{ path: filename, data: newContent }])

  readContent = await sandbox.files.read(filename)
  assert.equal(readContent, newContent)

  if (isDebug) {
    await sandbox.files.remove(filename)
  }
})

sandboxTest(
  'write ReadableStream with octet stream upload',
  async ({ sandbox }) => {
    const filename = 'test_write_octet_stream.bin'
    const content = 'Streamed octet-stream upload. '.repeat(10_000)
    const stream = new Blob([content]).stream()

    const info = await sandbox.files.write(filename, stream, {
      useOctetStream: true,
    })
    assert.equal(info.path, `/home/user/${filename}`)

    const readContent = await sandbox.files.read(filename)
    assert.equal(readContent, content)

    if (isDebug) {
      await sandbox.files.remove(filename)
    }
  }
)

sandboxTest(
  'write ReadableStream with octet stream upload and gzip',
  async ({ sandbox }) => {
    const filename = 'test_write_octet_stream_gzip.bin'
    const content = 'Streamed gzipped octet-stream upload. '.repeat(10_000)
    const stream = new Blob([content]).stream()

    const info = await sandbox.files.write(filename, stream, {
      useOctetStream: true,
      gzip: true,
    })
    assert.equal(info.path, `/home/user/${filename}`)

    const readContent = await sandbox.files.read(filename)
    assert.equal(readContent, content)

    if (isDebug) {
      await sandbox.files.remove(filename)
    }
  }
)
