import sys
from contextlib import AbstractAsyncContextManager, AbstractContextManager

from ._attributes import Attributes
from ._bytes import Bytes
from ._store import ObjectStore

if sys.version_info >= (3, 11):
    from typing import Self
else:
    from typing_extensions import Self

if sys.version_info >= (3, 12):
    from collections.abc import Buffer
else:
    from typing_extensions import Buffer

def open_reader(
    store: ObjectStore,
    path: str,
    *,
    buffer_size: int = 1024 * 1024,
    size: int | None = None,
) -> ReadableFile:
    """Open a readable file object from the specified location.

    Args:
        store: The ObjectStore instance to use.
        path: The path within ObjectStore to retrieve.

    Keyword Args:
        buffer_size: The minimum number of bytes to read in a single request. Up to `buffer_size` bytes will be buffered in memory.
        size: Optional byte size of the object. When provided, skips the HEAD request used to fetch the file size. Useful for callers that already know the size from external metadata.

            The caller is responsible for accuracy: a value larger than the actual file surfaces as a read-time range error, a value smaller causes silent truncation. Defaults to `None`.

    Returns:
        ReadableFile

    """

async def open_reader_async(
    store: ObjectStore,
    path: str,
    *,
    buffer_size: int = 1024 * 1024,
    size: int | None = None,
) -> AsyncReadableFile:
    """Call `open_reader` asynchronously, returning a readable file object with asynchronous operations.

    Refer to the documentation for [open_reader][obstore.open_reader].
    """

class ReadableFile:
    """A synchronous-buffered reader that implements a similar interface as a Python
    [`BufferedReader`][io.BufferedReader].

    Internally this maintains a buffer of the requested size, and uses
    [`get_range`][obstore.get_range] to populate its internal buffer once depleted. This
    buffer is cleared on seek.

    Whilst simple, this interface will typically be outperformed by the native `obstore`
    methods that better map to the network APIs. This is because most object stores have
    very [high first-byte latencies], on the order of 100-200ms, and so avoiding
    unnecessary round-trips is critical to throughput.

    Systems looking to sequentially scan a file should instead consider using
    [`get`][obstore.get], or [`get_range`][obstore.get_range] to read a particular
    range.

    Systems looking to read multiple ranges of a file should instead consider using
    [`get_ranges`][obstore.get_ranges], which will optimise the vectored IO.

    [high first-byte latencies]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimizing-performance.html

    !!! warning "Not importable at runtime"

        To use this type hint in your code, import it within a `TYPE_CHECKING` block:

        ```py
        from __future__ import annotations
        from typing import TYPE_CHECKING
        if TYPE_CHECKING:
            from obstore import ReadableFile
        ```
    """  # noqa: D205

    def close(self) -> None:
        """Close the current file.

        This is currently a no-op.
        """

    def read(self, size: int | None = None, /) -> Bytes:
        """Read up to `size` bytes from the object and return them.

        As a convenience, if size is unspecified or `None`, all bytes until EOF are
        returned.
        """

    def readall(self) -> Bytes:
        """Read and return all the bytes from the stream until EOF."""

    def readline(self) -> Bytes:
        """Read a single line of the file, up until the next newline character."""

    def readlines(self, hint: int = -1, /) -> list[Bytes]:
        """Read all remaining lines into a list of buffers."""

    def seek(self, offset: int, whence: int = ..., /) -> int:
        """Change the stream position.

        Change the stream position to the given byte `offset`, interpreted relative to
        the position indicated by `whence`, and return the new absolute position. Values
        for `whence` are:

        - [`os.SEEK_SET`][] or 0: start of the stream (the default); `offset` should be zero or positive
        - [`os.SEEK_CUR`][] or 1: current stream position; `offset` may be negative
        - [`os.SEEK_END`][] or 2: end of the stream; `offset` is usually negative
        """

    def seekable(self) -> bool:
        """Return True if the stream supports random access."""

    @property
    def size(self) -> int:
        """The size in bytes of the object."""

    def tell(self) -> int:
        """Return the current stream position."""

class AsyncReadableFile:
    """An async-buffered reader that implements a similar interface as a Python
    [`BufferedReader`][io.BufferedReader].

    Internally this maintains a buffer of the requested size, and uses
    [`get_range`][obstore.get_range] to populate its internal buffer once depleted. This
    buffer is cleared on seek.

    Whilst simple, this interface will typically be outperformed by the native `obstore`
    methods that better map to the network APIs. This is because most object stores have
    very [high first-byte latencies], on the order of 100-200ms, and so avoiding
    unnecessary round-trips is critical to throughput.

    Systems looking to sequentially scan a file should instead consider using
    [`get`][obstore.get], or [`get_range`][obstore.get_range] to read a particular
    range.

    Systems looking to read multiple ranges of a file should instead consider using
    [`get_ranges`][obstore.get_ranges], which will optimise the vectored IO.

    [high first-byte latencies]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimizing-performance.html

    !!! warning "Not importable at runtime"

        To use this type hint in your code, import it within a `TYPE_CHECKING` block:

        ```py
        from __future__ import annotations
        from typing import TYPE_CHECKING
        if TYPE_CHECKING:
            from obstore import AsyncReadableFile
        ```
    """  # noqa: D205

    def close(self) -> None:
        """Close the current file.

        This is currently a no-op.
        """

    async def read(self, size: int | None = None, /) -> Bytes:
        """Read up to `size` bytes from the object and return them.

        As a convenience, if size is unspecified or `None`, all bytes until EOF are
        returned.
        """

    async def readall(self) -> Bytes:
        """Read and return all the bytes from the stream until EOF."""

    async def readline(self) -> Bytes:
        """Read a single line of the file, up until the next newline character."""

    async def readlines(self, hint: int = -1, /) -> list[Bytes]:
        """Read all remaining lines into a list of buffers."""

    async def seek(self, offset: int, whence: int = ..., /) -> int:
        """Change the stream position.

        Change the stream position to the given byte `offset`, interpreted relative to
        the position indicated by `whence`, and return the new absolute position. Values
        for `whence` are:

        - [`os.SEEK_SET`][] or 0: start of the stream (the default); `offset` should be zero or positive
        - [`os.SEEK_CUR`][] or 1: current stream position; `offset` may be negative
        - [`os.SEEK_END`][] or 2: end of the stream; `offset` is usually negative
        """

    def seekable(self) -> bool:
        """Return True if the stream supports random access."""

    @property
    def size(self) -> int:
        """The size in bytes of the object."""

    async def tell(self) -> int:
        """Return the current stream position."""

def open_writer(
    store: ObjectStore,
    path: str,
    *,
    attributes: Attributes | None = None,
    buffer_size: int = 10 * 1024 * 1024,
    tags: dict[str, str] | None = None,
    max_concurrency: int = 12,
) -> WritableFile:
    """Open a writable file object at the specified location.

    Args:
        store: The ObjectStore instance to use.
        path: The path within ObjectStore to retrieve.

    Keyword Args:
        attributes: Provide a set of `Attributes`. Defaults to `None`.
        buffer_size: The underlying buffer size to use. Up to `buffer_size` bytes will be buffered in memory. If `buffer_size` is exceeded, data will be uploaded as a multipart upload in chunks of `buffer_size`.
        tags: Provide tags for this object. Defaults to `None`.
        max_concurrency: The maximum number of chunks to upload concurrently. Defaults to 12.

    Returns:
        ReadableFile

    """

def open_writer_async(
    store: ObjectStore,
    path: str,
    *,
    attributes: Attributes | None = None,
    buffer_size: int = 10 * 1024 * 1024,
    tags: dict[str, str] | None = None,
    max_concurrency: int = 12,
) -> AsyncWritableFile:
    """Open an **asynchronous** writable file object at the specified location.

    Refer to the documentation for [open_writer][obstore.open_writer].
    """

class WritableFile(AbstractContextManager):
    """A buffered writable file object with synchronous operations.

    This implements a similar interface as a Python
    [`BufferedWriter`][io.BufferedWriter].

    !!! warning "Not importable at runtime"

        To use this type hint in your code, import it within a `TYPE_CHECKING` block:

        ```py
        from __future__ import annotations
        from typing import TYPE_CHECKING
        if TYPE_CHECKING:
            from obstore import WritableFile
        ```
    """

    def __enter__(self) -> Self: ...
    def __exit__(self, exc_type, exc_value, traceback) -> None: ...  # noqa: ANN001
    def close(self) -> None:
        """Close the current file."""

    def closed(self) -> bool:
        """Check whether this file has been closed.

        Note that this is a method, not an attribute.
        """

    def flush(self) -> None:
        """Flushes this output stream, ensuring that all intermediately buffered contents reach their destination."""

    def write(self, buffer: bytes | Buffer, /) -> int:
        """Write the [bytes-like object](https://docs.python.org/3/glossary.html#term-bytes-like-object), `buffer`, and return the number of bytes written."""

class AsyncWritableFile(AbstractAsyncContextManager):
    """A buffered writable file object with **asynchronous** operations.

    !!! warning "Not importable at runtime"

        To use this type hint in your code, import it within a `TYPE_CHECKING` block:

        ```py
        from __future__ import annotations
        from typing import TYPE_CHECKING
        if TYPE_CHECKING:
            from obstore import AsyncWritableFile
        ```
    """

    async def __aenter__(self) -> Self: ...
    async def __aexit__(self, exc_type, exc_value, traceback) -> None: ...  # noqa: ANN001
    async def close(self) -> None:
        """Close the current file."""

    async def closed(self) -> bool:
        """Check whether this file has been closed.

        Note that this is an async method, not an attribute.
        """

    async def flush(self) -> None:
        """Flushes this output stream, ensuring that all intermediately buffered contents reach their destination."""

    async def write(self, buffer: bytes | Buffer, /) -> int:
        """Write the [bytes-like object](https://docs.python.org/3/glossary.html#term-bytes-like-object), `buffer`, and return the number of bytes written."""
