#!/usr/bin/env python3
# encoding: utf-8

__author__ = "ChenyangGao <https://chenyanggao.github.io>"
__all__ = [
    "upload_url", "upload_endpoint_url", "oss_upload_token", "oss_upload_sign", 
    "oss_upload_request", "oss_multipart_upload_url", "oss_multipart_part_iter", 
    "oss_multipart_upload_init", "oss_multipart_upload_complete", 
    "oss_multipart_upload_cancel", "oss_multipart_upload_part", 
    "oss_multipart_upload_part_iter", "oss_upload", "oss_multipart_upload", 
]

from asyncio import Lock as AsyncLock
from base64 import b64encode
from collections import UserString
from collections.abc import (
    AsyncIterable, AsyncIterator, Awaitable, Buffer, Callable, 
    Coroutine, Iterable, Iterator, Mapping, Sequence, 
)
from datetime import datetime, timedelta
from email.utils import formatdate
from hmac import digest as hmac_digest
from inspect import iscoroutinefunction, signature
from itertools import count
from threading import Lock
from typing import cast, overload, Any, Final, Literal
from urllib.parse import urlsplit, urlunsplit
from xml.etree.ElementTree import fromstring

from dicttools import iter_items, dict_update
from filewrap import (
    SupportsRead, buffer_length, 
    bio_chunk_iter, bio_chunk_async_iter, 
    bytes_iter_to_async_reader, bytes_iter_to_reader, 
)
from http_request import complete_url
from http_response import get_status_code
from integer_tool import try_parse_int
from iterutils import (
    foreach, collect, run_gen_step, 
    run_gen_step_iter, wrap_iter, Yield, 
)


_UPLOAD_TOKEN: Final[dict[str, str]] = {}
_UPLOAD_TOKEN_LOCK: Final = Lock()
_UPLOAD_TOKEN_ASYNC_LOCK: Final = AsyncLock()


@overload
def _upload_token(
    refresh: bool = False, 
    *, 
    async_: Literal[False] = False, 
    **request_kwargs, 
) -> dict[str, str]:
    ...
@overload
def _upload_token(
    refresh: bool = False, 
    *, 
    async_: Literal[True], 
    **request_kwargs, 
) -> Coroutine[Any, Any, dict[str, str]]:
    ...
def _upload_token(
    refresh: bool = False, 
    *, 
    async_: Literal[False, True] = False, 
    **request_kwargs, 
) -> dict[str, str] | Coroutine[Any, Any, dict[str, str]]:
    request_kwargs["parse"] = parse_json
    def gen_step():
        if async_:
            lock: AsyncLock | Lock = _UPLOAD_TOKEN_ASYNC_LOCK
        else:
            lock = _UPLOAD_TOKEN_LOCK
        try:
            yield lock.acquire()
            expiration = _UPLOAD_TOKEN.get("Expiration") or ""
            if refresh:
                deadline = datetime.now() - timedelta(hours=7, minutes=10)
            else:
                deadline = datetime.now() - timedelta(hours=7, minutes=59)
            if expiration < deadline.strftime("%FT%XZ"):
                while True:
                    resp = yield oss_upload_token(async_=async_, **request_kwargs)
                    if resp.get("StatusCode") == "200":
                        break
                _UPLOAD_TOKEN.update(resp)
        finally:
            lock.release()
        return _UPLOAD_TOKEN
    return run_gen_step(gen_step, async_)


def to_base64(s: Buffer | str | UserString, /) -> str:
    if isinstance(s, (str, UserString)):
        s = s.encode("utf-8")
    return str(b64encode(s), "ascii")


def parse_json(_, content: bytes, /):
    from orjson import loads
    return loads(memoryview(content))


def parse_upload_id(_, content: bytes, /) -> str:
    return getattr(fromstring(content).find("UploadId"), "text")


def get_request(
    request_kwargs: dict, 
    async_: Literal[False, True] = False, 
):
    request = request_kwargs.pop("request", None)
    request_kwargs.setdefault("parse", parse_json)
    if request is None:
        from httpcore_request import request
        request_kwargs["async_"] = async_
    else:
        def has_keyword_async(request: Callable, /) -> bool:
            try:
                sig = signature(request)
            except (ValueError, TypeError):
                return False
            params = sig.parameters
            param = params.get("async_")
            return bool(param and param.kind in (param.POSITIONAL_OR_KEYWORD, param.KEYWORD_ONLY))
        if iscoroutinefunction(request):
            async_ = True
        if async_ is not None and has_keyword_async(request):
            request_kwargs["async_"] = async_
    return request


@overload
def upload_url(
    async_: Literal[False] = False, 
    **request_kwargs, 
) -> dict:
    ...
@overload
def upload_url(
    async_: Literal[True], 
    **request_kwargs, 
) -> Coroutine[Any, Any, dict]:
    ...
def upload_url(
    async_: Literal[False, True] = False, 
    **request_kwargs, 
) -> dict | Coroutine[Any, Any, dict]:
    """获取上传目的网址和获取 ``token`` 的网址

    :param async_: 是否异步
    :param request_kwargs: 其它请求参数

    :return: 网址的字典，"endpoint" 是上传目的网址，"gettokenurl" 是获取 ``token`` 的网址
    """
    api = "https://uplb.115.com/3.0/getuploadinfo.php"
    return get_request(request_kwargs, async_=async_)(url=api, **request_kwargs)


def upload_endpoint_url(
    object: str, 
    bucket: str = "fhnfile", 
    endpoint: str = "http://oss-cn-shenzhen.aliyuncs.com", 
) -> str:
    """构造上传时的 url

    :param bucket: 存储桶
    :param object: 存储对象 id
    :param endpoint: 上传目的网址

    :return: 上传时所用的 url
    """
    urlp = urlsplit(endpoint)
    return f"{urlp.scheme}://{bucket}.{urlp.netloc}/{object}"


@overload
def oss_upload_token(
    async_: Literal[False] = False, 
    **request_kwargs, 
) -> dict:
    ...
@overload
def oss_upload_token(
    async_: Literal[True], 
    **request_kwargs, 
) -> Coroutine[Any, Any, dict]:
    ...
def oss_upload_token(
    async_: Literal[False, True] = False, 
    **request_kwargs, 
) -> dict | Coroutine[Any, Any, dict]:
    """获取上传用到的令牌信息（字典）

    :param async_: 是否异步
    :param request_kwargs: 其它请求参数

    :return: 令牌信息（字典）
    """
    api = "https://uplb.115.com/3.0/gettoken.php"
    return get_request(request_kwargs, async_=async_)(url=api, **request_kwargs)


@overload
def oss_upload_sign(
    url: str, 
    method: str = "POST", 
    headers: None | Mapping[str, str] | Iterable[tuple[str, str]] = None, 
    token: None | dict = None, 
    *, 
    async_: Literal[False] = False, 
    **request_kwargs, 
) -> dict:
    ...
@overload
def oss_upload_sign(
    url: str, 
    method: str = "POST", 
    headers: None | Mapping[str, str] | Iterable[tuple[str, str]] = None, 
    token: None | dict = None, 
    *, 
    async_: Literal[True], 
    **request_kwargs, 
) -> dict:
    ...
def oss_upload_sign(
    url: str, 
    method: str = "POST", 
    headers: None | Mapping[str, str] | Iterable[tuple[str, str]] = None, 
    token: None | dict = None, 
    *, 
    async_: Literal[False, True] = False, 
    **request_kwargs, 
) -> dict:
    """计算然后返回带认证信息的请求头

    :param token: 上传用到的令牌信息（字典）
    :param method: HTTP 请求方法
    :param url: HTTP 请求链接
    :param headers: 默认的请求头
    :param async_: 是否异步
    :param request_kwargs: 其它请求参数

    :return: 带认证信息的请求头
    """
    # subresource_keys = (
    #     "accessPoint", "accessPointPolicy", "acl", "append", "asyncFetch", "bucketArchiveDirectRead", 
    #     "bucketInfo", "callback", "callback-var", "cname", "comp", "continuation-token", "cors", 
    #     "delete", "encryption", "endTime", "group", "httpsConfig", "inventory", "inventoryId", 
    #     "lifecycle", "link", "live", "location", "logging", "metaQuery", "objectInfo", "objectMeta", 
    #     "partNumber", "policy", "position", "publicAccessBlock", "qos", "qosInfo", "qosRequester", 
    #     "redundancyTransition", "referer", "regionList", "replication", "replicationLocation", 
    #     "replicationProgress", "requestPayment", "requesterQosInfo", "resourceGroup", "resourcePool", 
    #     "resourcePoolBuckets", "resourcePoolInfo", "response-cache-control", "response-content-disposition", 
    #     "response-content-encoding", "response-content-language", "response-content-type", "response-expires", 
    #     "restore", "security-token", "sequential", "startTime", "stat", "status", "style", "styleName", 
    #     "symlink", "tagging", "transferAcceleration", "uploadId", "uploads", "versionId", "versioning", 
    #     "versions", "vod", "website", "worm", "wormExtend", "wormId", "x-oss-ac-forward-allow", 
    #     "x-oss-ac-source-ip", "x-oss-ac-subnet-mask", "x-oss-ac-vpc-id", "x-oss-access-point-name", 
    #     "x-oss-async-process", "x-oss-process", "x-oss-redundancy-transition-taskid", "x-oss-request-payer", 
    #     "x-oss-target-redundancy-type", "x-oss-traffic-limit", "x-oss-write-get-object-response", 
    # )
    def gen_step():
        nonlocal headers, token
        if not token:
            token = yield _upload_token(async_=async_, **request_kwargs)
        urlp = urlsplit(url)
        bucket = cast(str, urlp.hostname).partition(".")[0]
        headers = {k.lower(): v for k, v in iter_items(headers or ())}
        headers["x-oss-security-token"] = token["SecurityToken"]
        date = headers["date"] = headers.get("x-oss-date") or headers.get("date") or formatdate(usegmt=True)
        signature = to_base64(hmac_digest(
            bytes(token["AccessKeySecret"], "utf-8"), 
            f"""\
{method.upper()}
{headers.setdefault("content-md5", "")}
{headers.setdefault("content-type", "")}
{date}
{"\n".join(map(
    "%s:%s".__mod__, 
    sorted(e for e in headers.items() if e[0].startswith("x-oss-"))
))}
/{bucket}{urlunsplit(urlp._replace(scheme="", netloc=""))}""".encode("utf-8"), 
            "sha1", 
        ))
        headers["authorization"] = "OSS {0}:{1}".format(token["AccessKeyId"], signature)
        return headers
    return run_gen_step(gen_step, async_)


def oss_upload_request(
    url: str, 
    method: str = "POST", 
    params: None | str | Mapping | Sequence[tuple[Any, Any]] = None, 
    token: None | dict = None, 
    *, 
    async_: Literal[False, True] = False, 
    **request_kwargs, 
):
    """请求阿里云 OSS 的公用函数
    """
    url = complete_url(url, params=params)
    def gen_step():
        nonlocal token
        if not token:
            token = yield _upload_token(async_=async_, **request_kwargs)
        request_kwargs["headers"] = oss_upload_sign(
            url, 
            method=method, 
            headers=request_kwargs.get("headers"), 
            token=token, 
        )
        return get_request(request_kwargs, async_=async_)(
            url=url, 
            method=method, 
            **request_kwargs, 
        )
    return run_gen_step(gen_step, async_)


@overload
def oss_multipart_upload_url(
    url: str, 
    upload_id: int | str, 
    part_number: int = 1, 
    token: None | dict = None, 
    *, 
    async_: Literal[False] = False, 
    **request_kwargs, 
) -> tuple[str, dict]:
    ...
@overload
def oss_multipart_upload_url(
    url: str, 
    upload_id: int | str, 
    part_number: int = 1, 
    token: None | dict = None, 
    *, 
    async_: Literal[True], 
    **request_kwargs, 
) -> Coroutine[Any, Any, tuple[str, dict]]:
    ...
def oss_multipart_upload_url(
    url: str, 
    upload_id: int | str, 
    part_number: int = 1, 
    token: None | dict = None, 
    *, 
    async_: Literal[False, True] = False, 
    **request_kwargs, 
) -> tuple[str, dict] | Coroutine[Any, Any, tuple[str, dict]]:
    """获取分块上传的链接和请求头

    :param url: HTTP 请求链接
    :param upload_id: 上传任务的 id
    :param part_number: 分块编号（从 1 开始）
    :param token: 上传用到的令牌信息（字典）
    :param async_: 是否异步
    :param request_kwargs: 其它请求参数   

    :return: 构造后的 上传链接 和 请求头 的 2 元组
    """
    url = complete_url(url, params={"partNumber": part_number, "uploadId": upload_id})
    def gen_step():
        nonlocal token
        if not token:
            token = yield _upload_token(refresh=True, async_=async_, **request_kwargs)
        headers = yield oss_upload_sign(
            url=url, 
            method="PUT", 
            headers=request_kwargs.get("headers"), 
            token=token, 
            async_=async_, 
            **request_kwargs, 
        )
        return url, headers
    return run_gen_step(gen_step, async_)


@overload
def oss_multipart_part_iter(
    url: str, 
    upload_id: str, 
    token: None | dict = None, 
    *, 
    async_: Literal[False] = False, 
    **request_kwargs, 
) -> Iterator[dict]:
    ...
@overload
def oss_multipart_part_iter(
    url: str, 
    upload_id: str, 
    token: None | dict = None, 
    *, 
    async_: Literal[True], 
    **request_kwargs, 
) -> AsyncIterator[dict]:
    ...
def oss_multipart_part_iter(
    url: str, 
    upload_id: str, 
    token: None | dict = None, 
    *, 
    async_: Literal[False, True] = False, 
    **request_kwargs, 
) -> Iterator[dict] | AsyncIterator[dict]:
    """罗列某个分块上传任务，已经上传的分块

    :param url: HTTP 请求链接
    :param upload_id: 上传任务的 id
    :param token: 上传用到的令牌信息（字典）
    :param async_: 是否异步
    :param request_kwargs: 其它请求参数    

    :return: 上传完成分块信息的迭代器
    """
    request_kwargs.update(
        method="GET", 
        params={"uploadId": upload_id}, 
        parse=False, 
    )
    def gen_step():
        params = request_kwargs["params"]
        while True:
            content = yield oss_upload_request(
                url=url, 
                token=token, 
                async_=async_, 
                **request_kwargs, 
            )
            etree = fromstring(content)
            for el in etree.iterfind("Part"):
                yield Yield({sel.tag: try_parse_int(sel.text) for sel in el})
            if getattr(etree.find("IsTruncated"), "text") == "false":
                break
            params["part-number-marker"] = getattr(etree.find("NextPartNumberMarker"), "text")
    return run_gen_step_iter(gen_step, async_)


@overload
def oss_multipart_upload_init(
    url: str, 
    token: None | dict = None, 
    *, 
    async_: Literal[False] = False, 
    **request_kwargs, 
) -> str:
    ...
@overload
def oss_multipart_upload_init(
    url: str, 
    token: None | dict = None, 
    *, 
    async_: Literal[True], 
    **request_kwargs, 
) -> Coroutine[Any, Any, str]:
    ...
def oss_multipart_upload_init(
    url: str, 
    token: None | dict = None, 
    *, 
    async_: Literal[False, True] = False, 
    **request_kwargs, 
) -> str | Coroutine[Any, Any, str]:
    """初始化，以获取分块上传任务的 id

    :param url: HTTP 请求链接
    :param token: 上传用到的令牌信息（字典）
    :param async_: 是否异步
    :param request_kwargs: 其它请求参数

    :return: 分块上传任务的 id
    """
    request_kwargs.update(
        method="POST", 
        params={"sequential": "1", "uploads": "1"}, 
    )
    request_kwargs.setdefault("parse", parse_upload_id)
    return oss_upload_request(
        url=url, 
        token=token, 
        async_=async_, 
        **request_kwargs, 
    )


@overload
def oss_multipart_upload_complete(
    url: str, 
    callback: dict, 
    upload_id: str, 
    parts: None | list[dict] = None, 
    token: None | dict = None, 
    *, 
    async_: Literal[False] = False, 
    **request_kwargs, 
) -> dict:
    ...
@overload
def oss_multipart_upload_complete(
    url: str, 
    callback: dict, 
    upload_id: str, 
    parts: None | list[dict] = None, 
    token: None | dict = None, 
    *, 
    async_: Literal[True], 
    **request_kwargs, 
) -> Coroutine[Any, Any, dict]:
    ...
def oss_multipart_upload_complete(
    url: str, 
    callback: dict, 
    upload_id: str, 
    parts: None | list[dict] = None, 
    token: None | dict = None, 
    *, 
    async_: Literal[False, True] = False, 
    **request_kwargs, 
) -> dict | Coroutine[Any, Any, dict]:
    """完成分块上传任务

    :param url: HTTP 请求链接
    :param callback: 回调数据
    :param upload_id: 上传任务 id
    :pamra parts: 已完成的分块信息列表
    :param token: 上传用到的令牌信息（字典）
    :param async_: 是否异步
    :param request_kwargs: 其它请求参数

    :return: 接口响应
    """
    def gen_step():
        nonlocal parts
        if parts is None:
            parts = yield collect(oss_multipart_part_iter(
                url=url, 
                upload_id=upload_id, 
                token=token, 
                async_=async_, 
                **request_kwargs, 
            ))
        request_kwargs.update(
            method="POST", 
            params={"uploadId": upload_id}, 
            data=b"".join((
                b"<CompleteMultipartUpload>", 
                *map(
                    b"<Part><PartNumber>%d</PartNumber><ETag>%s</ETag></Part>".__mod__, 
                    ((part["PartNumber"], bytes(part["ETag"], "ascii")) for part in parts), 
                ), 
                b"</CompleteMultipartUpload>", 
            )), 
            headers=dict_update(
                dict(request_kwargs.get("headers") or ()), 
                {
                    "x-oss-callback": to_base64(callback["callback"]), 
                    "x-oss-callback-var": to_base64(callback["callback_var"]), 
                    "content-type": "text/xml", 
                }, 
            ), 
        )
        return oss_upload_request(
            url=url, 
            token=token, 
            async_=async_, 
            **request_kwargs, 
        )
    return run_gen_step(gen_step, async_)


@overload
def oss_multipart_upload_cancel(
    url: str, 
    upload_id: str, 
    token: None | dict = None, 
    *, 
    async_: Literal[False] = False, 
    **request_kwargs, 
) -> bool:
    ...
@overload
def oss_multipart_upload_cancel(
    url: str, 
    upload_id: str, 
    token: None | dict = None, 
    *, 
    async_: Literal[True], 
    **request_kwargs, 
) -> Coroutine[Any, Any, bool]:
    ...
def oss_multipart_upload_cancel(
    url: str, 
    upload_id: str, 
    token: None | dict = None, 
    *, 
    async_: Literal[False, True] = False, 
    **request_kwargs, 
) -> bool | Coroutine[Any, Any, bool]:
    """取消分块上传任务

    :param url: HTTP 请求链接
    :param upload_id: 上传任务 id
    :param token: 上传用到的令牌信息（字典）
    :param async_: 是否异步
    :param request_kwargs: 其它请求参数

    :return: 是否成功
    """
    request_kwargs.update(
        method="DELETE", 
        params={"uploadId": upload_id}, 
        raise_for_status=False, 
    )
    request_kwargs.setdefault(
        "parse", 
        lambda resp: (code := get_status_code(resp)) == 404 or 200 <= code < 300, 
    )
    return oss_upload_request(
        url=url, 
        token=token, 
        async_=async_, 
        **request_kwargs, 
    )


@overload
def oss_multipart_upload_part(
    url: str, 
    file: Buffer | SupportsRead[Buffer] | Iterable[Buffer], 
    upload_id: str, 
    part_number: int, 
    token: None | dict = None, 
    *, 
    async_: Literal[False] = False, 
    **request_kwargs, 
) -> dict:
    ...
@overload
def oss_multipart_upload_part(
    url: str, 
    file: Buffer | SupportsRead[Buffer] | SupportsRead[Awaitable[Buffer]] | Iterable[Buffer] | AsyncIterable[Buffer], 
    upload_id: str, 
    part_number: int, 
    token: None | dict = None, 
    *, 
    async_: Literal[True], 
    **request_kwargs, 
) -> Coroutine[Any, Any, dict]:
    ...
def oss_multipart_upload_part(
    url: str, 
    file: Buffer | SupportsRead[Buffer] | SupportsRead[Awaitable[Buffer]] | Iterable[Buffer] | AsyncIterable[Buffer], 
    upload_id: str, 
    part_number: int, 
    token: None | dict = None, 
    *, 
    async_: Literal[False, True] = False, 
    **request_kwargs, 
) -> dict | Coroutine[Any, Any, dict]:
    """上传一个分块

    :param url: HTTP 请求链接
    :param file: 文件数据
    :param upload_id: 上传任务 id
    :param part_number: 分块编号（从 1 开始）
    :param token: 上传用到的令牌信息（字典）
    :param async_: 是否异步
    :param request_kwargs: 其它请求参数

    :return: 上传完成的分块的信息字典，包含如下字段：

        .. code:: python

            {
                "PartNumber": int,    # 分块序号，从 1 开始计数
                "LastModified": str,  # 最近更新时间
                "ETag": str,          # ETag 值，判断资源是否发生变化
                "HashCrc64ecma": int, # 校验码
                "Size": int,          # 分块大小
            }
    """
    count_in_bytes = 0
    if isinstance(file, Buffer):
        count_in_bytes = buffer_length(file)
    else:
        if isinstance(file, SupportsRead):
            if async_:
                file = bio_chunk_async_iter(file)
            else:
                file = bio_chunk_iter(cast(SupportsRead[Buffer], file))
        def acc(chunk: Buffer, /):
            nonlocal count_in_bytes
            count_in_bytes += buffer_length(chunk)
        file = wrap_iter(file, callnext=acc)
    def parse_upload_part(resp, /) -> dict:
        headers = resp.headers
        return {
            "PartNumber": part_number, 
            "LastModified": datetime.strptime(headers["date"], "%a, %d %b %Y %H:%M:%S GMT").strftime("%FT%X.%f")[:-3] + "Z", 
            "ETag": headers["ETag"], 
            "HashCrc64ecma": int(headers["x-oss-hash-crc64ecma"]), 
            "Size": count_in_bytes, 
        }
    request_kwargs.update(
        method="PUT", 
        params={"partNumber": part_number, "uploadId": upload_id}, 
        data=file, 
    )
    request_kwargs.setdefault("parse", parse_upload_part)
    return oss_upload_request(
        url=url, 
        token=token, 
        async_=async_, 
        **request_kwargs, 
    )


@overload
def oss_multipart_upload_part_iter(
    url: str, 
    file: Buffer | SupportsRead[Buffer] | Iterable[Buffer], 
    upload_id: str, 
    part_size: int, 
    part_number_start: int = 1, 
    token: None | dict = None, 
    *, 
    async_: Literal[False] = False, 
    **request_kwargs, 
) -> Iterator[dict]:
    ...
@overload
def oss_multipart_upload_part_iter(
    url: str, 
    file: Buffer | SupportsRead[Buffer] | SupportsRead[Awaitable[Buffer]] | Iterable[Buffer] | AsyncIterable[Buffer], 
    upload_id: str, 
    part_size: int, 
    part_number_start: int = 1, 
    token: None | dict = None, 
    *, 
    async_: Literal[True], 
    **request_kwargs, 
) -> AsyncIterator[dict]:
    ...
def oss_multipart_upload_part_iter(
    url: str, 
    file: Buffer | SupportsRead[Buffer] | SupportsRead[Awaitable[Buffer]] | Iterable[Buffer] | AsyncIterable[Buffer], 
    upload_id: str, 
    part_size: int, 
    part_number_start: int = 1, 
    token: None | dict = None, 
    *, 
    async_: Literal[False, True] = False, 
    **request_kwargs, 
) -> Iterator[dict] | AsyncIterator[dict]:
    """迭代器，迭代一次会上传一个分块

    .. attention::
        如果需要跳过一定的数据，请提前处理好，这个不管数据是否被重复上传

    :param url: HTTP 请求链接
    :param file: 文件数据
    :param upload_id: 上传任务 id
    :param part_size: 分块大小
    :param part_number_start: 开始的分块编号（从 1 开始）
    :param token: 上传用到的令牌信息（字典）
    :param async_: 是否异步
    :param request_kwargs: 其它请求参数

    :return: 上传完成的分块信息的迭代器
    """
    if isinstance(file, (Buffer, SupportsRead)):
        pass
    elif async_:
        file = bytes_iter_to_async_reader(file)
    else:
        file = bytes_iter_to_reader(cast(Iterable, file))
    def gen_step():
        chunk: Buffer | Iterator[Buffer] | AsyncIterator[Buffer]
        for i, part_number in enumerate(count(part_number_start)):
            if isinstance(file, memoryview):
                chunk = file[i*part_size:(i+1)*part_size]
            elif isinstance(file, SupportsRead):
                if async_:
                    chunk = bio_chunk_async_iter(file, part_size)
                else:
                    chunk = bio_chunk_iter(cast(SupportsRead[Buffer], file), part_size)
            part = yield Yield(oss_multipart_upload_part(
                url=url, 
                file=chunk, # type: ignore
                upload_id=upload_id, 
                part_number=part_number, 
                token=token, 
                async_=async_, # type: ignore
                **request_kwargs, 
            ))
            if part["Size"] < part_size:
                break
    return run_gen_step_iter(gen_step, async_)


@overload
def oss_upload(
    url: str, 
    file: Buffer | SupportsRead[Buffer] | Iterable[Buffer], 
    callback: dict, 
    token: None | dict = None, 
    *, 
    async_: Literal[False] = False, 
    **request_kwargs, 
) -> dict:
    ...
@overload
def oss_upload(
    url: str, 
    file: Buffer | SupportsRead[Buffer] | SupportsRead[Awaitable[Buffer]] | Iterable[Buffer] | AsyncIterable[Buffer], 
    callback: dict, 
    token: None | dict = None, 
    *, 
    async_: Literal[True], 
    **request_kwargs, 
) -> Coroutine[Any, Any, dict]:
    ...
def oss_upload(
    url: str, 
    file: Buffer | SupportsRead[Buffer] | SupportsRead[Awaitable[Buffer]] | Iterable[Buffer] | AsyncIterable[Buffer], 
    callback: dict, 
    token: None | dict = None, 
    *, 
    async_: Literal[False, True] = False, 
    **request_kwargs, 
) -> dict | Coroutine[Any, Any, dict]:
    """一次性上传文件到阿里云 OSS

    :param url: HTTP 请求链接
    :param file: 文件数据
    :param callback: 回调数据
    :param token: 上传用到的令牌信息（字典）
    :param async_: 是否异步
    :param request_kwargs: 其它请求参数

    :return: 接口响应
    """
    request_kwargs.update(
        method="PUT", 
        data=file, 
        headers=dict_update(
        dict(request_kwargs.get("headers") or ()), 
            {
                "x-oss-callback": to_base64(callback["callback"]), 
                "x-oss-callback-var": to_base64(callback["callback_var"]), 
            }, 
        ), 
    )
    return oss_upload_request(
        url=url, 
        token=token, 
        async_=async_, 
        **request_kwargs, 
    )


@overload
def oss_multipart_upload(
    url: str, 
    file: Buffer | SupportsRead[Buffer] | Iterable[Buffer], 
    callback: dict, 
    upload_id: None | str = None, 
    part_size: int = 10485760, 
    parts: None | list[dict] = None, 
    token: None | dict = None, 
    *, 
    async_: Literal[False] = False, 
    **request_kwargs, 
) -> dict:
    ...
@overload
def oss_multipart_upload(
    url: str, 
    file: Buffer | SupportsRead[Buffer] | SupportsRead[Awaitable[Buffer]] | Iterable[Buffer] | AsyncIterable[Buffer], 
    callback: dict, 
    upload_id: None | str = None, 
    part_size: int = 10485760, 
    parts: None | list[dict] = None, 
    token: None | dict = None, 
    *, 
    async_: Literal[True], 
    **request_kwargs, 
) -> Coroutine[Any, Any, dict]:
    ...
def oss_multipart_upload(
    url: str, 
    file: Buffer | SupportsRead[Buffer] | SupportsRead[Awaitable[Buffer]] | Iterable[Buffer] | AsyncIterable[Buffer], 
    callback: dict, 
    upload_id: None | str = None, 
    part_size: int = 10485760, 
    parts: None | list[dict] = None, 
    token: None | dict = None, 
    *, 
    async_: Literal[False, True] = False, 
    **request_kwargs, 
) -> dict | Coroutine[Any, Any, dict]:
    """分块上传文件到阿里云 OSS

    .. attention::
        如果需要跳过一定的数据，请提前处理好，这个不管数据是否被重复上传    

    :param url: HTTP 请求链接
    :param file: 文件数据
    :param callback: 回调数据
    :param upload_id: 上传任务 id
    :param part_size: 分块大小
    :pamra parts: 已完成的分块信息列表
    :param token: 上传用到的令牌信息（字典）
    :param async_: 是否异步
    :param request_kwargs: 其它请求参数

    :return: 接口响应
    """
    def gen_step():
        nonlocal upload_id, parts
        if upload_id:
            if parts is None:
                parts = []
                yield foreach(
                    parts.append, 
                    oss_multipart_part_iter(
                        url=url, 
                        upload_id=upload_id, 
                        token=token, 
                        async_=async_, 
                        **request_kwargs, 
                    ), 
                )
        else:
            upload_id = yield oss_multipart_upload_init(
                url=url, 
                token=token, 
                async_=async_, 
                **request_kwargs, 
            )
            upload_id = cast(str, upload_id)
            if parts is None:
                parts = []
            else:
                assert not parts
        yield foreach(
            parts.append, 
            oss_multipart_upload_part_iter(
                url=url, 
                file=file, # type: ignore
                upload_id=upload_id, 
                part_size=part_size, 
                part_number_start=len(parts)+1, 
                token=token, 
                async_=async_, # type: ignore
                **request_kwargs, 
            ), 
        )
        return (yield oss_multipart_upload_complete(
            url=url, 
            callback=callback, 
            upload_id=upload_id, 
            parts=parts, 
            token=token, 
            async_=async_, 
            **request_kwargs, 
        ))
    return run_gen_step(gen_step, async_)

