from pathlib import Path
from typing import Union

import pytest
from _pytest.fixtures import SubRequest
from _pytest.tmpdir import TempPathFactory

from anyio import ClosedResourceError, EndOfStream
from anyio.abc import ByteReceiveStream
from anyio.streams.file import FileReadStream, FileStreamAttribute, FileWriteStream

pytestmark = pytest.mark.anyio


class TestFileReadStream:
    @pytest.fixture(scope="class")
    def file_path(self, tmp_path_factory: TempPathFactory) -> Path:
        path = tmp_path_factory.mktemp("filestream") / "data.txt"
        path.write_text("Hello")
        return path

    @pytest.fixture(params=[False, True], ids=["str", "path"])
    def file_path_or_str(
        self, request: SubRequest, file_path: Path
    ) -> Union[Path, str]:
        return file_path if request.param else str(file_path)

    async def _run_filestream_test(self, stream: ByteReceiveStream) -> None:
        assert await stream.receive(3) == b"Hel"
        assert await stream.receive(3) == b"lo"
        with pytest.raises(EndOfStream):
            await stream.receive(1)

    async def test_read_file_as_path(self, file_path_or_str: Union[Path, str]) -> None:
        async with await FileReadStream.from_path(file_path_or_str) as stream:
            await self._run_filestream_test(stream)

    async def test_read_file(self, file_path: Path) -> None:
        with file_path.open("rb") as file:
            async with FileReadStream(file) as stream:
                await self._run_filestream_test(stream)

    async def test_read_after_close(self, file_path: Path) -> None:
        async with await FileReadStream.from_path(file_path) as stream:
            pass

        with pytest.raises(ClosedResourceError):
            await stream.receive()

    async def test_seek(self, file_path: Path) -> None:
        with file_path.open("rb") as file:
            async with FileReadStream(file) as stream:
                await stream.seek(2)
                assert await stream.tell() == 2
                data = await stream.receive()
                assert data == b"llo"
                assert await stream.tell() == 5

    async def test_extra_attributes(self, file_path: Path) -> None:
        async with await FileReadStream.from_path(file_path) as stream:
            path = stream.extra(FileStreamAttribute.path)
            assert path == file_path

            fileno = stream.extra(FileStreamAttribute.fileno)
            assert fileno > 2

            file = stream.extra(FileStreamAttribute.file)
            assert file.fileno() == fileno


class TestFileWriteStream:
    @pytest.fixture
    def file_path(self, tmp_path: Path) -> Path:
        return tmp_path / "written_data.txt"

    async def test_write_file(self, file_path: Path) -> None:
        async with await FileWriteStream.from_path(file_path) as stream:
            await stream.send(b"Hel")
            await stream.send(b"lo")

        assert file_path.read_text() == "Hello"

    async def test_append_file(self, file_path: Path) -> None:
        file_path.write_text("Hello")
        async with await FileWriteStream.from_path(file_path, True) as stream:
            await stream.send(b", World!")

        assert file_path.read_text() == "Hello, World!"

    async def test_write_after_close(self, file_path: Path) -> None:
        async with await FileWriteStream.from_path(file_path, True) as stream:
            pass

        with pytest.raises(ClosedResourceError):
            await stream.send(b"foo")

    async def test_extra_attributes(self, file_path: Path) -> None:
        async with await FileWriteStream.from_path(file_path) as stream:
            path = stream.extra(FileStreamAttribute.path)
            assert path == file_path

            fileno = stream.extra(FileStreamAttribute.fileno)
            assert fileno > 2

            file = stream.extra(FileStreamAttribute.file)
            assert file.fileno() == fileno
