"""
使用:
    from enconia_utils import Credentials
    creds = Credentials(access_key_id="xxx", access_key_secret="xxx")
    oss_client = creds.get_oss_provider()
    
    oss_client.upload_file("oss://bucket/path/file.txt", "/local/file.txt")
    oss_client.download_file("oss://bucket/path/file.txt", "/local/", "file.txt")
"""

import os
import re
from typing import Optional

import oss2
from .util_type_mapping import get_content_type_4_filename


class OssCloudObjectProvider:

    def __init__(
        self,
        access_key_id: str,
        access_key_secret: str,
        endpoint: str = 'https://oss-cn-shanghai.aliyuncs.com',
        region: str = 'cn-shanghai',
    ):
        self.access_key_id = access_key_id
        self.access_key_secret = access_key_secret
        self.oss_endpoint = endpoint
        self.region = region
        self.auth = oss2.Auth(self.access_key_id, self.access_key_secret)

    def oss_split_path(self, oss_path):
        if not oss_path.startswith('oss://'):
            raise ValueError(f"Invalid oss path format: {oss_path}")
        oss_path_re = re.compile(r"(oss://[a-zA-Z\-_0-9]+)/(.+)")
        match = oss_path_re.search(oss_path)
        if not match:
            raise ValueError(f"Invalid oss path format: {oss_path}")
        path_group = match.groups()
        bucket = path_group[0].replace('oss://', '')
        if not bucket:
            raise ValueError(f"Invalid oss path format: {oss_path}")
        key = path_group[1]
        if not key:
            raise ValueError(f"Invalid oss path format: {oss_path}")
        return bucket, key

    def copy_oss_obj(self, src_oss_path: str, dst_oss_path: str):
        src_bucket, src_key = self.oss_split_path(src_oss_path)
        dst_bucket, dst_key = self.oss_split_path(dst_oss_path)
        bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, dst_bucket, region=self.region)
        result = bucket_obj.copy_object(src_bucket, src_key, dst_key)
        if result.status != 200:
            raise RuntimeError(f"Copy failed with status code {result.status}")

    def check_file_exist(self, oss_path: str) -> bool:
        bucket, key = self.oss_split_path(oss_path)
        bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, bucket, region=self.region)
        return bucket_obj.object_exists(key)

    def upload_file(self, oss_path: str, local_path: str):
        if not os.path.exists(local_path):
            raise FileNotFoundError(f"Local file doesn't exist: {local_path}")
        bucket, key = self.oss_split_path(oss_path)
        bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, bucket, region=self.region)
        content_type = get_content_type_4_filename(local_path, text_with_utf8=True)
        result = bucket_obj.put_object_from_file(key, local_path, headers={"Content-Type": content_type})
        if result.status != 200:
            raise RuntimeError(f"Upload failed with status code {result.status}")

    def download_file(self, oss_path: str, local_path: str, filename: str):
        if not os.path.exists(local_path):
            os.makedirs(local_path)
        bucket, key = self.oss_split_path(oss_path)
        bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, bucket, region=self.region)
        bucket_obj.get_object_to_file(key, os.path.join(local_path, filename))

    def get_presign_url(self, oss_path: str, expire_hours: int = 1, cname_endpoint: Optional[str] = None) -> str:
        bucket, key = self.oss_split_path(oss_path)
        endpoint = cname_endpoint or self.oss_endpoint
        is_cname = cname_endpoint is not None
        bucket_obj = oss2.Bucket(self.auth, endpoint, bucket, region=self.region, is_cname=is_cname)
        expire_seconds = expire_hours * 60 * 60
        return bucket_obj.sign_url('GET', key, expire_seconds, slash_safe=True)

    def get_upload_url(self, oss_path: str, expire_hours: int = 1) -> str:
        bucket, key = self.oss_split_path(oss_path)
        bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, bucket, region=self.region)
        expire_seconds = expire_hours * 60 * 60
        return bucket_obj.sign_url('PUT', key, expire_seconds, slash_safe=True)

    def list_dir_iter(self, oss_path_pre: str):
        bucket_name, key_pre = self.oss_split_path(oss_path_pre)
        bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, bucket_name, region=self.region)
        for obj in oss2.ObjectIterator(bucket_obj, prefix=key_pre):
            yield obj.key

    def get_file_content(self, oss_path: str):
        bucket, key = self.oss_split_path(oss_path)
        bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, bucket, region=self.region)
        return bucket_obj.get_object(key).read()

    def delete_file_from_oss(self, oss_path: str):
        bucket, key = self.oss_split_path(oss_path)
        bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, bucket, region=self.region)
        result = bucket_obj.delete_object(key)
        if result.status != 204:
            raise RuntimeError(f"Delete failed with status code {result.status}")
