import { describe, expect } from 'vitest'
import { NotFoundError, VolumeError, VolumeFileType } from '../../src'
import { volumeTest } from '../setup'

describe('Volume File Operations', () => {
  describe('writeFile and readFile', () => {
    volumeTest('should write and read a text file', async ({ volume }) => {
      const path = '/test.txt'
      const content = 'Hello, World!'

      const stat = await volume.writeFile(path, content)
      expect(stat.name).toBe('test.txt')
      expect(stat.type).toBe(VolumeFileType.FILE)
      expect(stat.path).toBe(path)
      expect(stat.atime).toBeInstanceOf(Date)
      expect(stat.mtime).toBeInstanceOf(Date)
      expect(stat.ctime).toBeInstanceOf(Date)

      const readContent = await volume.readFile(path, { format: 'text' })
      expect(readContent).toBe(content)
    })

    volumeTest(
      'should write and read a file with bytes format',
      async ({ volume }) => {
        const path = '/test-bytes.txt'
        const content = 'Test bytes content'
        const contentBytes = new TextEncoder().encode(content)

        await volume.writeFile(path, contentBytes.buffer)
        const readBytes = await volume.readFile(path, { format: 'bytes' })

        expect(readBytes).toEqual(contentBytes)
      }
    )

    volumeTest('should write and read binary data', async ({ volume }) => {
      const path = '/binary.bin'
      const binaryData = new Uint8Array([0, 1, 2, 3, 4])

      await volume.writeFile(path, binaryData.buffer)
      const readBytes = await volume.readFile(path, { format: 'bytes' })

      expect(readBytes).toEqual(binaryData)
    })

    volumeTest(
      'should write and read a file with blob format',
      async ({ volume }) => {
        const path = '/test-blob.txt'
        const content = 'Test blob content'
        const blob = new Blob([content], { type: 'text/plain' })

        await volume.writeFile(path, blob)
        const readBlob = await volume.readFile(path, { format: 'blob' })

        expect(await readBlob.text()).toBe(content)
      }
    )

    volumeTest(
      'should write and read a file from a ReadableStream',
      async ({ volume }) => {
        const path = '/test-stream.txt'
        const content = 'Test stream content'
        const stream = new Blob([content]).stream()

        await volume.writeFile(path, stream)
        const readContent = await volume.readFile(path, { format: 'text' })

        expect(readContent).toBe(content)
      }
    )

    volumeTest('should write and read an empty file', async ({ volume }) => {
      const path = '/empty.txt'
      const content = ''

      await volume.writeFile(path, content)
      const readContent = await volume.readFile(path, { format: 'text' })

      expect(readContent).toBe(content)
    })

    volumeTest(
      'should read an empty file in all formats',
      async ({ volume }) => {
        const path = '/empty-formats.txt'
        await volume.writeFile(path, '')

        const bytes = await volume.readFile(path, { format: 'bytes' })
        expect(bytes).toBeInstanceOf(Uint8Array)
        expect(bytes.length).toBe(0)

        const blob = await volume.readFile(path, { format: 'blob' })
        expect(blob).toBeInstanceOf(Blob)
        expect(blob.size).toBe(0)

        const stream = await volume.readFile(path, { format: 'stream' })
        expect(stream).toBeInstanceOf(ReadableStream)
        const chunks: Uint8Array[] = []
        for await (const chunk of stream as unknown as AsyncIterable<Uint8Array>) {
          chunks.push(chunk)
        }
        expect(chunks.reduce((n, c) => n + c.length, 0)).toBe(0)
      }
    )

    volumeTest(
      'should overwrite an existing file with force option',
      async ({ volume }) => {
        const path = '/overwrite.txt'
        const initialContent = 'Initial content'
        const newContent = 'New content'

        await volume.writeFile(path, initialContent)
        await volume.writeFile(path, newContent, { force: true })
        const readContent = await volume.readFile(path, { format: 'text' })

        expect(readContent).toBe(newContent)
      }
    )

    volumeTest(
      'should throw VolumeError when writing to existing file with force: false',
      async ({ volume }) => {
        const path = '/no-overwrite.txt'
        const initialContent = 'Initial content'
        const newContent = 'New content'

        await volume.writeFile(path, initialContent)
        await expect(
          volume.writeFile(path, newContent, { force: false })
        ).rejects.toThrow(VolumeError)
      }
    )

    volumeTest(
      'should write file with metadata (uid, gid, mode)',
      async ({ volume }) => {
        const path = '/metadata.txt'
        const content = 'File with metadata'

        const stat = await volume.writeFile(path, content, {
          uid: 1000,
          gid: 1000,
          mode: 0o644,
        })

        expect(stat.type).toBe(VolumeFileType.FILE)
        expect(stat.uid).toBe(1000)
        expect(stat.gid).toBe(1000)
        expect(stat.mode).toBe(0o644)
      }
    )

    volumeTest('should write file in nested directory', async ({ volume }) => {
      const dirPath = '/nested/deep/path'
      const filePath = `${dirPath}/file.txt`
      const content = 'Nested file content'

      await volume.makeDir(dirPath, { force: true })
      await volume.writeFile(filePath, content)
      const readContent = await volume.readFile(filePath, { format: 'text' })

      expect(readContent).toBe(content)
    })
  })

  describe('getInfo', () => {
    volumeTest('should get info for a file', async ({ volume }) => {
      const path = '/info-file.txt'
      const content = 'File for info test'

      await volume.writeFile(path, content)
      const info = await volume.getInfo(path)

      expect(info.name).toBe('info-file.txt')
      expect(info.type).toBe(VolumeFileType.FILE)
      expect(info.path).toBe(path)
      expect(info.atime).toBeInstanceOf(Date)
      expect(info.mtime).toBeInstanceOf(Date)
      expect(info.ctime).toBeInstanceOf(Date)
    })

    volumeTest('should get info for a directory', async ({ volume }) => {
      const path = '/info-dir'

      await volume.makeDir(path)
      const info = await volume.getInfo(path)

      expect(info.name).toBe('info-dir')
      expect(info.type).toBe(VolumeFileType.DIRECTORY)
      expect(info.path).toBe(path)
    })

    volumeTest(
      'should return false from exists for non-existent file',
      async ({ volume }) => {
        expect(await volume.exists('/non-existent.txt')).toBe(false)
      }
    )
  })

  describe('updateMetadata', () => {
    volumeTest('should update file metadata', async ({ volume }) => {
      const path = '/metadata-update.txt'
      await volume.writeFile(path, 'Content')

      const updated = await volume.updateMetadata(path, {
        uid: 1001,
        gid: 1001,
        mode: 0o755,
      })

      expect(updated.path).toBe(path)
      expect(updated.type).toBe(VolumeFileType.FILE)
      expect(updated.uid).toBe(1001)
      expect(updated.gid).toBe(1001)
      expect(updated.mode).toBe(0o755)
    })

    volumeTest(
      'should throw NotFoundError when updating non-existent file',
      async ({ volume }) => {
        await expect(
          volume.updateMetadata('/non-existent.txt', { mode: 0o644 })
        ).rejects.toThrow(NotFoundError)
      }
    )
  })

  describe('makeDir', () => {
    volumeTest('should create a directory', async ({ volume }) => {
      const path = '/test-dir'

      const stat = await volume.makeDir(path)

      expect(stat.type).toBe(VolumeFileType.DIRECTORY)
      expect(stat.path).toBe(path)
      expect(stat.atime).toBeInstanceOf(Date)
      expect(stat.mtime).toBeInstanceOf(Date)
      expect(stat.ctime).toBeInstanceOf(Date)
    })

    volumeTest(
      'should create nested directories with force option',
      async ({ volume }) => {
        const path = '/nested/deep/directory'

        const stat = await volume.makeDir(path, { force: true })

        expect(stat.type).toBe(VolumeFileType.DIRECTORY)
      }
    )

    volumeTest(
      'should throw VolumeError when creating existing directory with force: false',
      async ({ volume }) => {
        const path = '/existing-dir'

        await volume.makeDir(path)
        await expect(volume.makeDir(path, { force: false })).rejects.toThrow(
          VolumeError
        )
      }
    )

    volumeTest('should create directory with metadata', async ({ volume }) => {
      const path = '/dir-with-metadata'

      const stat = await volume.makeDir(path, {
        uid: 1000,
        gid: 1000,
        mode: 0o755,
      })

      expect(stat.type).toBe(VolumeFileType.DIRECTORY)
      expect(stat.uid).toBe(1000)
      expect(stat.gid).toBe(1000)
      expect(stat.mode & 0o777).toBe(0o755)
    })
  })

  describe('list', () => {
    volumeTest('should list directory contents', async ({ volume }) => {
      await volume.writeFile('/file1.txt', 'Content 1')
      await volume.writeFile('/file2.txt', 'Content 2')
      await volume.makeDir('/dir1')

      const entries = await volume.list('/')

      expect(entries.length).toBeGreaterThanOrEqual(3)
      const fileNames = entries.map((e) => e.name).sort()
      expect(fileNames).toContain('file1.txt')
      expect(fileNames).toContain('file2.txt')
      expect(fileNames).toContain('dir1')
    })

    volumeTest('should list nested directory contents', async ({ volume }) => {
      await volume.makeDir('/nested', { force: true })
      await volume.writeFile('/nested/file.txt', 'Content')

      const entries = await volume.list('/nested')

      expect(entries.length).toBeGreaterThanOrEqual(1)
      expect(entries.some((e) => e.name === 'file.txt')).toBe(true)
    })

    volumeTest.skip('should list with depth option', async ({ volume }) => {
      await volume.makeDir('/deep/nested/structure', { force: true })
      await volume.writeFile('/deep/nested/structure/file.txt', 'Content')

      const entries = await volume.list('/deep', { depth: 2 })

      expect(entries.length).toBeGreaterThan(0)
    })

    volumeTest(
      'should throw NotFoundError for non-existent directory',
      async ({ volume }) => {
        await expect(volume.list('/non-existent')).rejects.toThrow(
          NotFoundError
        )
      }
    )
  })

  describe('remove', () => {
    volumeTest('should remove a file', async ({ volume }) => {
      const path = '/to-remove.txt'
      await volume.writeFile(path, 'Content')

      await volume.remove(path)

      expect(await volume.exists(path)).toBe(false)
    })

    volumeTest('should remove a directory', async ({ volume }) => {
      const path = '/to-remove-dir'
      await volume.makeDir(path)

      await volume.remove(path)

      expect(await volume.exists(path)).toBe(false)
    })

    volumeTest('should remove a directory recursively', async ({ volume }) => {
      const dirPath = '/recursive-dir'
      await volume.makeDir(`${dirPath}/nested`, { force: true })
      await volume.writeFile(`${dirPath}/nested/file.txt`, 'Content')

      await volume.remove(dirPath)

      expect(await volume.exists(dirPath)).toBe(false)
    })

    volumeTest(
      'should throw NotFoundError when removing non-existent file',
      async ({ volume }) => {
        await expect(volume.remove('/non-existent.txt')).rejects.toThrow(
          NotFoundError
        )
      }
    )
  })

  describe('file operations lifecycle', () => {
    volumeTest(
      'should handle directory with multiple files',
      async ({ volume }) => {
        const dirPath = '/multi-file-dir'
        await volume.makeDir(dirPath)

        const files = ['file1.txt', 'file2.txt', 'file3.txt']
        for (const fileName of files) {
          await volume.writeFile(
            `${dirPath}/${fileName}`,
            `Content of ${fileName}`
          )
        }

        const entries = await volume.list(dirPath)
        expect(entries.length).toBeGreaterThanOrEqual(files.length)

        for (const fileName of files) {
          const content = await volume.readFile(`${dirPath}/${fileName}`, {
            format: 'text',
          })
          expect(content).toBe(`Content of ${fileName}`)
        }

        await volume.remove(dirPath)
        expect(await volume.exists(dirPath)).toBe(false)
      }
    )
  })
})
