from pathlib import PurePosixPath

import pytest

import aioftp


@pytest.mark.asyncio
async def test_remove_single_file(pair_factory):
    async with pair_factory() as pair:
        await pair.make_server_files("foo.txt")
        assert await pair.server_paths_exists("foo.txt")
        await pair.client.remove_file("foo.txt")
        assert await pair.server_paths_exists("foo.txt") is False


@pytest.mark.asyncio
async def test_recursive_remove(pair_factory):
    async with pair_factory() as pair:
        paths = ["foo/bar.txt", "foo/baz.txt", "foo/bar_dir/foo.baz"]
        await pair.make_server_files(*paths)
        await pair.client.remove("foo")
        assert await pair.server_paths_exists(*paths) is False


@pytest.mark.asyncio
async def test_file_download(pair_factory):
    async with pair_factory() as pair:
        await pair.make_server_files("foo", size=1, atom=b"foobar")
        async with pair.client.download_stream("foo") as stream:
            data = await stream.read()
        assert data == b"foobar"


@pytest.mark.asyncio
async def test_file_upload(pair_factory):
    async with pair_factory() as pair:
        async with pair.client.upload_stream("foo") as stream:
            await stream.write(b"foobar")
        async with pair.client.download_stream("foo") as stream:
            data = await stream.read()
        assert data == b"foobar"


@pytest.mark.asyncio
async def test_file_append(pair_factory):
    async with pair_factory() as pair:
        await pair.make_server_files("foo", size=1, atom=b"foobar")
        async with pair.client.append_stream("foo") as stream:
            await stream.write(b"foobar")
        async with pair.client.download_stream("foo") as stream:
            data = await stream.read()
        assert data == b"foobar" * 2


@pytest.mark.asyncio
async def test_upload_folder(pair_factory):
    async with pair_factory() as pair:
        paths = ["foo/bar", "foo/baz"]
        await pair.make_client_files(*paths)
        assert await pair.server_paths_exists(*paths) is False
        await pair.client.upload("foo")
        assert await pair.server_paths_exists(*paths)


@pytest.mark.asyncio
async def test_upload_folder_into(pair_factory):
    async with pair_factory() as pair:
        paths = ["foo/bar", "foo/baz"]
        await pair.make_client_files(*paths)
        assert await pair.server_paths_exists("bar", "baz") is False
        await pair.client.upload("foo", write_into=True)
        assert await pair.server_paths_exists("bar", "baz")


@pytest.mark.asyncio
async def test_upload_folder_into_another(pair_factory):
    async with pair_factory() as pair:
        paths = ["foo/bar", "foo/baz"]
        await pair.make_client_files(*paths)
        assert await pair.server_paths_exists("bar/bar", "bar/baz") is False
        await pair.client.upload("foo", "bar", write_into=True)
        assert await pair.server_paths_exists("bar/bar", "bar/baz")


@pytest.mark.asyncio
async def test_download_folder(pair_factory):
    async with pair_factory() as pair:
        paths = ["foo/bar", "foo/baz"]
        await pair.make_server_files(*paths)
        assert await pair.client_paths_exists(*paths) is False
        await pair.client.download("foo")
        assert await pair.client_paths_exists(*paths)


@pytest.mark.asyncio
async def test_download_folder_into(pair_factory):
    async with pair_factory() as pair:
        paths = ["foo/bar", "foo/baz"]
        await pair.make_server_files(*paths)
        assert await pair.client_paths_exists("bar", "baz") is False
        await pair.client.download("foo", write_into=True)
        assert await pair.client_paths_exists("bar", "baz")


@pytest.mark.asyncio
async def test_download_folder_into_another(pair_factory):
    async with pair_factory() as pair:
        paths = ["foo/bar", "foo/baz"]
        await pair.make_server_files(*paths)
        assert await pair.client_paths_exists("bar/bar", "bar/baz") is False
        await pair.client.download("foo", "bar", write_into=True)
        assert await pair.client_paths_exists("bar/bar", "bar/baz")


@pytest.mark.asyncio
async def test_upload_file_over(pair_factory):
    async with pair_factory() as pair:
        await pair.make_client_files("foo", size=1, atom=b"client")
        await pair.make_server_files("foo", size=1, atom=b"server")
        async with pair.client.download_stream("foo") as stream:
            assert await stream.read() == b"server"
        await pair.client.upload("foo")
        async with pair.client.download_stream("foo") as stream:
            assert await stream.read() == b"client"


@pytest.mark.asyncio
async def test_download_file_over(pair_factory):
    async with pair_factory() as pair:
        await pair.make_client_files("foo", size=1, atom=b"client")
        await pair.make_server_files("foo", size=1, atom=b"server")
        async with pair.client.path_io.open(PurePosixPath("foo")) as f:
            assert await f.read() == b"client"
        await pair.client.download("foo")
        async with pair.client.path_io.open(PurePosixPath("foo")) as f:
            assert await f.read() == b"server"


@pytest.mark.asyncio
async def test_upload_file_write_into(pair_factory):
    async with pair_factory() as pair:
        await pair.make_client_files("foo", size=1, atom=b"client")
        await pair.make_server_files("bar", size=1, atom=b"server")
        async with pair.client.download_stream("bar") as stream:
            assert await stream.read() == b"server"
        await pair.client.upload("foo", "bar", write_into=True)
        async with pair.client.download_stream("bar") as stream:
            assert await stream.read() == b"client"


@pytest.mark.asyncio
async def test_upload_tree(pair_factory):
    async with pair_factory() as pair:
        await pair.make_client_files("foo/bar/baz", size=1, atom=b"client")
        await pair.client.upload("foo", "bar", write_into=True)
        files = await pair.client.list(recursive=True)
        assert len(files) == 3


@pytest.mark.asyncio
async def test_download_file_write_into(pair_factory):
    async with pair_factory() as pair:
        await pair.make_client_files("foo", size=1, atom=b"client")
        await pair.make_server_files("bar", size=1, atom=b"server")
        async with pair.client.path_io.open(PurePosixPath("foo")) as f:
            assert await f.read() == b"client"
        await pair.client.download("bar", "foo", write_into=True)
        async with pair.client.path_io.open(PurePosixPath("foo")) as f:
            assert await f.read() == b"server"


@pytest.mark.asyncio
async def test_upload_file_os_error(pair_factory, Server,
                                    expect_codes_in_exception):

    class OsErrorPathIO(aioftp.MemoryPathIO):

        @aioftp.pathio.universal_exception
        async def write(self, fout, data):
            raise OSError("test os error")

    s = Server(path_io_factory=OsErrorPathIO)
    async with pair_factory(None, s) as pair:
        with expect_codes_in_exception("451"):
            async with pair.client.upload_stream("foo") as stream:
                await stream.write(b"foobar")


@pytest.mark.asyncio
async def test_upload_path_unreachable(pair_factory,
                                       expect_codes_in_exception):
    async with pair_factory() as pair:
        with expect_codes_in_exception("550"):
            async with pair.client.upload_stream("foo/bar/foo") as stream:
                await stream.write(b"foobar")


@pytest.mark.asyncio
async def test_stat_when_no_mlst(pair_factory):
    class CustomServer(aioftp.Server):
        def __getattribute__(self, name):
            if name == "mlst":
                raise AttributeError
            return super().__getattribute__(name)

    async with pair_factory(server_factory=CustomServer) as pair:
        await pair.make_server_files("foo")
        info = await pair.client.stat("foo")
        assert info["type"] == "file"
