import datetime
from io import BytesIO, StringIO

import pytest

from e2b import AsyncVolume
from e2b.exceptions import NotFoundException, VolumeException


class TestWriteFileAndReadFile:
    async def test_write_and_read_text_file(self, async_volume: AsyncVolume):
        path = "/test.txt"
        content = "Hello, World!"

        await async_volume.write_file(path, content)
        read_content = await async_volume.read_file(path, format="text")

        assert read_content == content

    async def test_write_and_read_bytes(self, async_volume: AsyncVolume):
        path = "/test-bytes.txt"
        content = "Test bytes content"
        content_bytes = content.encode("utf-8")

        await async_volume.write_file(path, content_bytes)
        read_bytes = await async_volume.read_file(path, format="bytes")

        assert read_bytes == content_bytes

    async def test_write_and_read_binary_data(self, async_volume: AsyncVolume):
        path = "/binary.bin"
        binary_data = bytes([0, 1, 2, 3, 4])

        await async_volume.write_file(path, binary_data)
        read_bytes = await async_volume.read_file(path, format="bytes")

        assert read_bytes == binary_data

    async def test_write_and_read_stream(self, async_volume: AsyncVolume):
        path = "/test-stream.txt"
        content = "Test stream content"
        stream = BytesIO(content.encode("utf-8"))

        await async_volume.write_file(path, stream)
        read_stream = await async_volume.read_file(path, format="stream")
        chunks = []
        async for chunk in read_stream:
            chunks.append(chunk)
        read_content = b"".join(chunks).decode("utf-8")

        assert read_content == content

    async def test_write_and_read_text_stream(self, async_volume: AsyncVolume):
        path = "/test-text-stream.txt"
        content = "Test text stream content"
        stream = StringIO(content)

        await async_volume.write_file(path, stream)
        read_content = await async_volume.read_file(path, format="text")

        assert read_content == content

    async def test_write_and_read_empty_file(self, async_volume: AsyncVolume):
        path = "/empty.txt"
        content = ""

        await async_volume.write_file(path, content)
        read_content = await async_volume.read_file(path, format="text")

        assert read_content == content

    async def test_overwrite_with_force(self, async_volume: AsyncVolume):
        path = "/overwrite.txt"
        initial_content = "Initial content"
        new_content = "New content"

        await async_volume.write_file(path, initial_content)
        await async_volume.write_file(path, new_content, force=True)
        read_content = await async_volume.read_file(path, format="text")

        assert read_content == new_content

    async def test_write_existing_file_without_force_raises(
        self, async_volume: AsyncVolume
    ):
        path = "/no-overwrite.txt"
        initial_content = "Initial content"
        new_content = "New content"

        await async_volume.write_file(path, initial_content)
        with pytest.raises(VolumeException):
            await async_volume.write_file(path, new_content, force=False)

    async def test_write_file_with_metadata(self, async_volume: AsyncVolume):
        path = "/metadata.txt"
        content = "File with metadata"

        await async_volume.write_file(path, content, uid=1000, gid=1000, mode=0o644)

        entry_info = await async_volume.get_info(path)
        assert entry_info.type.value == "file"
        assert entry_info.uid == 1000
        assert entry_info.gid == 1000
        assert entry_info.mode == 0o644

    async def test_write_file_in_nested_directory(self, async_volume: AsyncVolume):
        dir_path = "/nested/deep/path"
        file_path = f"{dir_path}/file.txt"
        content = "Nested file content"

        await async_volume.make_dir(dir_path, force=True)
        await async_volume.write_file(file_path, content)
        read_content = await async_volume.read_file(file_path, format="text")

        assert read_content == content


class TestGetInfo:
    async def test_get_info_for_file(self, async_volume: AsyncVolume):
        path = "/info-file.txt"
        content = "File for info test"

        await async_volume.write_file(path, content)
        info = await async_volume.get_info(path)

        assert info.name == "info-file.txt"
        assert info.type.value == "file"
        assert info.path == path
        assert isinstance(info.mtime, datetime.datetime)
        assert isinstance(info.ctime, datetime.datetime)

    async def test_get_info_for_directory(self, async_volume: AsyncVolume):
        path = "/info-dir"

        await async_volume.make_dir(path)
        info = await async_volume.get_info(path)

        assert info.name == "info-dir"
        assert info.type.value == "directory"
        assert info.path == path

    async def test_exists_returns_false_for_nonexistent(
        self, async_volume: AsyncVolume
    ):
        assert await async_volume.exists("/non-existent.txt") is False


class TestUpdateMetadata:
    async def test_update_file_metadata(self, async_volume: AsyncVolume):
        path = "/metadata-update.txt"
        await async_volume.write_file(path, "Content")

        updated = await async_volume.update_metadata(
            path, uid=1001, gid=1001, mode=0o755
        )

        assert updated.path == path
        assert updated.type.value == "file"
        assert updated.uid == 1001
        assert updated.gid == 1001
        assert updated.mode == 0o755

    async def test_update_metadata_nonexistent_raises(self, async_volume: AsyncVolume):
        with pytest.raises(NotFoundException):
            await async_volume.update_metadata("/non-existent.txt", mode=0o644)


class TestMakeDir:
    async def test_create_directory(self, async_volume: AsyncVolume):
        path = "/test-dir"

        await async_volume.make_dir(path)
        info = await async_volume.get_info(path)

        assert info.type.value == "directory"
        assert info.path == path

    async def test_create_nested_directories_with_force(
        self, async_volume: AsyncVolume
    ):
        path = "/nested/deep/directory"

        await async_volume.make_dir(path, force=True)
        info = await async_volume.get_info(path)

        assert info.type.value == "directory"

    async def test_create_existing_directory_without_force_raises(
        self, async_volume: AsyncVolume
    ):
        path = "/existing-dir"

        await async_volume.make_dir(path)
        with pytest.raises(VolumeException):
            await async_volume.make_dir(path, force=False)

    async def test_create_directory_with_metadata(self, async_volume: AsyncVolume):
        path = "/dir-with-metadata"

        await async_volume.make_dir(path, uid=1000, gid=1000, mode=0o755)

        info = await async_volume.get_info(path)
        assert info.type.value == "directory"
        assert info.uid == 1000
        assert info.gid == 1000
        assert info.mode & 0o777 == 0o755


class TestList:
    async def test_list_directory_contents(self, async_volume: AsyncVolume):
        await async_volume.write_file("/file1.txt", "Content 1")
        await async_volume.write_file("/file2.txt", "Content 2")
        await async_volume.make_dir("/dir1")

        entries = await async_volume.list("/")

        assert len(entries) >= 3
        file_names = sorted([e.name for e in entries])
        assert "file1.txt" in file_names
        assert "file2.txt" in file_names
        assert "dir1" in file_names

    async def test_list_nested_directory(self, async_volume: AsyncVolume):
        await async_volume.make_dir("/nested", force=True)
        await async_volume.write_file("/nested/file.txt", "Content")

        entries = await async_volume.list("/nested")

        assert len(entries) >= 1
        assert any(e.name == "file.txt" for e in entries)

    @pytest.mark.skip(reason="depth option not yet supported")
    async def test_list_with_depth(self, async_volume: AsyncVolume):
        await async_volume.make_dir("/deep/nested/structure", force=True)
        await async_volume.write_file("/deep/nested/structure/file.txt", "Content")

        entries = await async_volume.list("/deep", depth=2)

        assert len(entries) > 0

    async def test_list_nonexistent_raises(self, async_volume: AsyncVolume):
        with pytest.raises(NotFoundException):
            await async_volume.list("/non-existent")


class TestRemove:
    async def test_remove_file(self, async_volume: AsyncVolume):
        path = "/to-remove.txt"
        await async_volume.write_file(path, "Content")

        await async_volume.remove(path)

        assert await async_volume.exists(path) is False

    async def test_remove_directory(self, async_volume: AsyncVolume):
        path = "/to-remove-dir"
        await async_volume.make_dir(path)

        await async_volume.remove(path)

        assert await async_volume.exists(path) is False

    async def test_remove_directory_recursively(self, async_volume: AsyncVolume):
        dir_path = "/recursive-dir"
        await async_volume.make_dir(f"{dir_path}/nested", force=True)
        await async_volume.write_file(f"{dir_path}/nested/file.txt", "Content")

        await async_volume.remove(dir_path)

        assert await async_volume.exists(dir_path) is False

    async def test_remove_nonexistent_raises(self, async_volume: AsyncVolume):
        with pytest.raises(NotFoundException):
            await async_volume.remove("/non-existent.txt")


class TestFileOperationsLifecycle:
    async def test_directory_with_multiple_files(self, async_volume: AsyncVolume):
        dir_path = "/multi-file-dir"
        await async_volume.make_dir(dir_path)

        files = ["file1.txt", "file2.txt", "file3.txt"]
        for file_name in files:
            await async_volume.write_file(
                f"{dir_path}/{file_name}", f"Content of {file_name}"
            )

        entries = await async_volume.list(dir_path)
        assert len(entries) >= len(files)

        for file_name in files:
            content = await async_volume.read_file(
                f"{dir_path}/{file_name}", format="text"
            )
            assert content == f"Content of {file_name}"

        await async_volume.remove(dir_path)
        assert await async_volume.exists(dir_path) is False
