"""
阿里云OSS工具类模块

提供对阿里云对象存储服务(OSS)的便捷操作封装。
"""
from typing import Optional, Iterator, Any
from pathlib import Path
from io import BytesIO
import alibabacloud_oss_v2 as oss_sdk


class OssHelper:
    """
    阿里云OSS操作辅助类
    
    提供文件上传、下载、删除等常用OSS操作的简化接口。
    支持普通文件操作、范围下载、分页列表等功能。
    
    Examples:
        >>> oss_config = {
        ...     'auth': {
        ...         'access_key_id': 'your_access_key',
        ...         'access_key_secret': 'your_secret'
        ...     },
        ...     'bucket': {
        ...         'name': 'your-bucket',
        ...         'endpoint': 'https://oss-cn-shanghai.aliyuncs.com',
        ...         'region': 'cn-shanghai'
        ...     }
        ... }
        >>> oss = OssHelper(**oss_config)
        >>> oss.upload_file('path/to/file.txt', Path('local_file.txt'))
    """
    def __init__(self, **kwargs: Any) -> None:
        """
        初始化OSS客户端
        
        :param kwargs: 配置参数字典，包含以下键：
            - auth (dict): 认证信息，包含 access_key_id 和 access_key_secret
            - bucket (dict): bucket配置，包含 name、endpoint 和 region
        :raises KeyError: 当必需的配置参数缺失时
        """
        cfg = oss_sdk.config.load_default()
        cfg.credentials_provider = oss_sdk.credentials.StaticCredentialsProvider(**kwargs.get('auth'))
        bucket = kwargs.get('bucket')
        cfg.region = bucket.get("region")
        cfg.endpoint = bucket.get("endpoint")

        self.__client = oss_sdk.Client(cfg)
        self.__bucket = bucket.get("name")

    def upload_file(self, key: str, filename: Path, **kwargs: Any) -> Any:
        """
        上传本地文件到OSS
        
        :param key: OSS对象键（远程路径）
        :param filename: 本地文件路径
        :param kwargs: 其他可选参数，传递给底层SDK
        :return: 上传响应对象
        :raises FileNotFoundError: 当本地文件不存在时
        :raises ValueError: 当提供的路径不是文件时
        """
        if not filename.exists():
            raise FileNotFoundError(f"File not found: {filename}")
        if not filename.is_file():
            raise ValueError(f"Path must be a file, not a directory: {filename}")
        return self.__client.put_object_from_file(oss_sdk.PutObjectRequest(self.__bucket, key), str(filename), **kwargs)

    def put_object(self, key: str, string: str, **kwargs: Any) -> Any:
        """
        将字符串内容作为对象上传到OSS
        
        :param key: OSS对象键（远程路径）
        :param string: 要上传的字符串内容
        :param kwargs: 其他可选参数，传递给底层SDK
        :return: 上传响应对象
        :raises ValueError: 当字符串为空时
        """
        if not string:
            raise ValueError('String content must not be empty')

        return self.__client.put_object(oss_sdk.PutObjectRequest(self.__bucket, key, body=string.encode('utf-8')),
                                        **kwargs)

    def download(self, key: str, **kwargs: Any) -> bytes:
        """
        下载对象内容到内存
        
        :param key: OSS对象键（远程路径）
        :param kwargs: 其他可选参数，传递给底层SDK
        :return: 下载的字节数据
        """
        buf = BytesIO()
        self.__client.downloader().download_to(oss_sdk.GetObjectRequest(self.__bucket, key), buf, **kwargs)
        return buf.getvalue()

    def download_file(self, key: str, filename: Path, overwrite: bool = False, **kwargs: Any) -> Any:
        """
        下载对象到本地文件
        
        :param key: OSS对象键（远程路径）
        :param filename: 本地文件路径
        :param overwrite: 是否覆盖已存在的文件，默认False
        :param kwargs: 其他可选参数，传递给底层SDK
        :return: 下载响应对象
        :raises FileExistsError: 当文件已存在且overwrite为False时
        """
        if filename.exists() and not overwrite:
            raise FileExistsError(f"File already exists: {filename}")

        filename.parent.mkdir(parents=True, exist_ok=True)
        return self.__client.downloader(part_size=1024 * 1024).download_file(
            oss_sdk.GetObjectRequest(self.__bucket, key), str(filename), **kwargs)

    def download_range(self, key: str, start_byte: int, **kwargs: Any) -> bytes:
        """
        下载对象的指定字节范围（Range GET）
        
        用于AppendObject模式下的增量下载，仅获取新追加的数据。
        
        :param key: OSS对象键（远程路径）
        :param start_byte: 起始字节偏移量（0-based index）
        :param kwargs: 其他可选参数，传递给底层SDK
        :return: 指定范围的字节数据
        """
        range_header = f"bytes={start_byte}-"
        request = oss_sdk.GetObjectRequest(self.__bucket, key, range_header=range_header)
        result = self.__client.get_object(request, **kwargs)
        with result.body as stream:
            return stream.read()

    def is_object_exist(self, key: str, **kwargs: Any) -> bool:
        """
        判断对象是否存在
        
        :param key: OSS对象键（远程路径）
        :param kwargs: 其他可选参数，传递给底层SDK
        :return: 对象存在返回True，否则返回False
        """
        return self.__client.is_object_exist(self.__bucket, key, **kwargs)

    def get_object_size(self, key: str, **kwargs: Any) -> Optional[int]:
        """
        获取对象大小（字节数）
        
        通过HEAD请求获取元数据，不下载对象内容。
        
        :param key: OSS对象键（远程路径）
        :param kwargs: 其他可选参数，传递给底层SDK
        :return: 对象大小（字节），对象不存在时返回None
        """
        try:
            result = self.__client.head_object(oss_sdk.HeadObjectRequest(self.__bucket, key), **kwargs)
            return result.content_length
        except oss_sdk.exceptions.ServiceError as e:
            # 对象不存在或其他服务错误
            return None

    def list_objects_paginator(self, **kwargs: Any) -> Iterator[Any]:
        """
        分页列举对象
        
        :param kwargs: 列举ListObjectsV2Request参数，例如：
            - prefix: 对象键前缀
            - max_keys: 每页返回的最大对象数
            - delimiter: 分隔符，用于模拟目录结构
        :return: 分页迭代器，每页包含多个对象
        """
        paginator = self.__client.list_objects_v2_paginator()
        return paginator.iter_page(oss_sdk.ListObjectsV2Request(self.__bucket, **kwargs))

    def delete(self, *keys: str, **kwargs: Any) -> Optional[Any]:
        """
        删除一个或多个OSS对象
        
        :param keys: 要删除的对象键，可以传入多个
        :param kwargs: 其他可选参数，传递给底层SDK
        :return: 删除响应对象，未提供对象键时返回None
        """
        cnt = len(keys)
        if cnt <= 0:
            return None

        if cnt == 1:
            return self.__client.delete_object(oss_sdk.models.DeleteObjectRequest(self.__bucket, keys[0]), **kwargs)

        objects = list(map(lambda k: oss_sdk.DeleteObject(k), keys))
        return self.__client.delete_multiple_objects(
            oss_sdk.models.DeleteMultipleObjectsRequest(self.__bucket, objects=objects), **kwargs)


if __name__ == '__main__':
    oss_config = {
        'auth': {
            "access_key_id": "",
            "access_key_secret": ""
        },
        'bucket': {
            'name': 'xiaoyang-measurement',
            'endpoint': 'https://oss-cn-shanghai.aliyuncs.com',
            # 'endpoint': 'http://oss-cn-shanghai-internal.aliyuncs.com',
            'region': "cn-shanghai"
        }
    }

    _oss = OssHelper(**oss_config)
    # ps = {
    #     'key': 'dev/photos/0015e695-3681-4315-930b-4bb624d11eda.jpg',
    #     'filename': 'sample.jpg'
    # }
    # oss_sdk.upload(**ps)
    #  _oss.download_file(**ps)
    # _obj = _oss.download(ps.get("key"))
    # _obj = _oss.download("dev/videos/3a1a2d67-bfa5-177d-e5b2-1dab7191af83_1.web")

    pages = _oss.list_objects_paginator(prefix='dev/sdks', max_keys=2)
    for page in pages:
        for o in page.contents:
            print(f'Object: {o.key}, Size: {o.size}, Last_modified: {o.last_modified}')

    # print(oss_sdk.delete(
    #     'prod/videos/5b550b2f-367d-4104-b2ff-83fef756a0f9.mp4'))
    # print(_oss.delete('dev/chunks/3a1bac65-6b47-8a77-6145-1cc5af7c92ff_2',
    #                   'dev/chunks/3a1bac65-6b47-8a77-6145-1cc5af7c92ff_3'))
    pass
