import datetime
import mimetypes
import os.path
import time
import zipfile
from enum import Enum
from typing import TYPE_CHECKING, Optional, Literal
from urllib.parse import unquote

from loguru import logger
import requests
from .config import OssConfig
from .lazy import LazyLoader
from .utils import get_md5
from tqdm import tqdm

if TYPE_CHECKING:
    import oss2
else:
    oss2 = LazyLoader("oss2")


class UploadFile(object):
    """
    文件上传
    """

    def __init__(self, prefix: str, access_key: str = "", access_secret: str = "", endpoint: str = "",
                 bucket_name: str = "", retry_num: int = 5):
        auth = oss2.Auth(access_key, access_secret)
        self.prefix = prefix
        self.bucket = oss2.Bucket(auth, endpoint, bucket_name)
        self.oss_path = prefix + '/' + datetime.datetime.now().strftime("%Y%m/%d")
        self.retry_num = retry_num
        logger.debug(f'当前oss根路径:{self.oss_path}')

    @staticmethod
    def simple(config: OssConfig, prefix: str, bucket_name: str = 'bainiudata1') -> 'UploadFile':
        """
        通过配置文件创建对象
        :param config:
        :param prefix:
        :param bucket_name:
        :return:
        """
        return UploadFile(
            prefix=prefix,
            access_key=config.access_key,
            access_secret=config.access_secret,
            endpoint=config.endpoint,
            bucket_name=bucket_name  # 存储桶
        )

    def is_exist(self, osspath):
        return self.bucket.object_exists(osspath)

    def upload(self, filepath: str, filename: str = None) -> str:
        self.oss_path = self.prefix + '/' + datetime.datetime.now().strftime("%Y%m/%d")
        if not filepath or not os.path.exists(filepath):
            logger.warning(f"本地文件不存在: {filepath}")
            return ''
        if filename:
            oss_abs_path = self.oss_path + '/' + filename
        else:
            oss_abs_path = self.oss_path + '/' + filepath.split('/')[-1]

        exist = self.bucket.object_exists(oss_abs_path)
        if exist:
            logger.warning(f"oss 存在相同名的文件 {oss_abs_path}")
            return oss_abs_path
        else:
            num = 0
            while True:
                try:
                    result = self.bucket.put_object_from_file(oss_abs_path, filepath)
                    break
                except Exception as e:
                    num += 1
                    logger.error(f'{e.__class__}')
                    if num > self.retry_num:
                        logger.error(f"oss upload faild {oss_abs_path},error:{e}")
                        return ''

            if int(result.status) == 200:
                logger.success(f"{oss_abs_path} 上传成功")
            else:
                logger.error(f"oss upload faild {oss_abs_path},status:{result.status}")
            return oss_abs_path


class DownloadFile(object):
    """
    下载文件
    """

    class NameMode(Enum):
        URL = 1
        Content_Disposition = 2
        Custom = 3

    def __init__(self, url, method: Literal['get', 'post'] = 'get', data=None, json_data: dict = None,
                 params: dict = None,
                 headers: dict = None, cookies: dict = None, timeout: int = 20, verify: bool = True,
                 delay=1, save_path='file/', name_mode: NameMode = NameMode.URL, bef_down_func=None,proxy=None,proxy_url=None):
        self.url = url
        self.headers = headers
        self.cookies = cookies
        self.timeout = timeout
        self.method = method
        self.data = data
        self.data = data
        self.json_data = json_data
        self.params = params
        self.verify = verify
        self.delay = delay
        self.proxies = None
        self.bef_down_func = bef_down_func
        self.save_path = save_path  # 文件保存位置
        self.response: Optional[requests.Response] = None  # 响应体
        self.filename: Optional[str] = None  # 文件名
        self.filepath: Optional[str] = None  # 文件路径
        self.prefix: Optional[str] = None  # 文件前缀
        self.suffix: Optional[str] = None  # 文件后缀
        self.name_mode: DownloadFile.NameMode = name_mode  # 文件名模式

    def get_filename(self):
        """
        根据指定模式生成文件名并构建完整存储路径
        模式：
            1，通过url获取
            2. 通过content-type获取
            3. 指定文件后缀
        :return:
        """

        if self.name_mode == DownloadFile.NameMode.URL:
            try:
                path_part = self.url.split('?', 1)[0]
                filename_part = path_part.split('/')[-1]
                if '.' in filename_part:
                    self.suffix = '.' + filename_part.rsplit('.', 1)[-1]
                    if len(self.suffix) > 10:
                        self.suffix = ''
                else:
                    self.suffix = ''  # 无有效扩展名时留空
                self.prefix = get_md5(self.url)
            except Exception as e:
                logger.exception(e)
                self.suffix = ''
                self.prefix = get_md5(self.url)
        elif self.name_mode == DownloadFile.NameMode.Content_Disposition:
            # 从Content-Disposition头解析文件名
            content_disposition = self.response.headers.get('Content-Disposition', '')
            filename = self._parse_content_disposition(content_disposition)
            if filename and '.' in filename:
                self.suffix = '.' + filename.rsplit('.', 1)[-1]
            if not self.suffix:
                # 无法解析文件名时从Content-Type获取
                self.suffix = self._get_suffix_from_content_type()
            self.prefix = get_md5(self.url)
        elif self.name_mode == DownloadFile.NameMode.Custom:
            if not self.filename:
                # 生成基础文件名
                self.prefix = get_md5(self.url)
                if self.suffix is None:
                    logger.warning("未指定文件后缀，将使用空后缀")
                    self.suffix = ''
        else:
            raise ValueError(f"未知的文件命名模式：{self.name_mode}")

        # 构建最终文件名和路径
        self.filename = self.filename if self.filename else f"{self.prefix}{self.suffix}"
        self.filepath = os.path.join(self.save_path, self.filename)

    def _parse_content_disposition(self, content_disposition):
        """解析Content-Disposition头获取文件名"""
        if not content_disposition:
            return None

        filename = None
        parts = [p.strip() for p in content_disposition.split(';')]

        for part in parts:
            if part.lower().startswith('filename='):
                filename = part[len('filename='):].strip()
                # 去除包裹的引号
                if filename.startswith(('"', "'")):
                    filename = filename[1:-1]
                filename = unquote(filename)
                break
            elif part.lower().startswith('filename*='):
                filename_part = part[len('filename*='):]
                try:
                    encoding, _, filename_encoded = filename_part.split("'", 2)
                    filename = unquote(filename_encoded)
                    break
                except ValueError:
                    continue

        return filename

    def _get_suffix_from_content_type(self):
        content_type_header = self.response.headers.get('Content-Type')
        if not content_type_header:
            return ''
        # 分割主类型和子类型，并只保留第一部分
        content_type = content_type_header.split(';', 1)[0].strip().lower()
        extension = mimetypes.guess_extension(content_type)
        return extension or ''

    def bef_down(self):
        if self.bef_down_func:
            self.bef_down_func(self)

    def download(self, retry_num=0, stream=False, success_code: Optional[list[int]] = None,
                 cancel_code: Optional[list[int]] = None):
        if not cancel_code: cancel_code = [404]
        if not success_code: success_code = [200]
        logger.debug(f"下载文件：{self.url}")
        num = 0
        while True:
            if num > retry_num: break
            num = num + 1
            try:
                self.bef_down()
                kwargs = {
                    'params': self.params,
                    'headers': self.headers,
                    'cookies': self.cookies,
                    'timeout': self.timeout,
                    'verify': self.verify,
                    'proxies': self.proxies,
                    'stream': stream,
                }
                self.response = requests.request(self.method, self.url, **kwargs)
                if self.response.status_code in cancel_code:
                    logger.warning(f"下载文件出错code:{self.response.status_code},放弃下载")
                    break
                if self.response.status_code in success_code:
                    if not os.path.exists(self.save_path):
                        os.makedirs(self.save_path)
                    self.get_filename()
                    if not stream:
                        with open(self.filepath, "wb") as f:
                            f.write(self.response.content)
                        logger.success(f"下载文件成功：{self.filepath}")
                    else:
                        total_size = int(self.response.headers.get('content-length', 0))
                        progress_bar = tqdm(total=total_size, unit='B', unit_scale=True,
                                            desc=f"{self.filename}:正在下载")
                        try:
                            with open(self.filepath, 'wb') as file:
                                # 逐块读取响应内容
                                for chunk in self.response.iter_content(chunk_size=8192):
                                    if chunk:
                                        file.write(chunk)
                                        progress_bar.update(len(chunk))
                        finally:
                            progress_bar.close()
                        logger.success(f"下载文件成功：{self.filepath}")
                    return
                logger.warning(f"{num}:下载文件出错code:{self.response.status_code}")
                time.sleep(self.delay)
            except Exception as e:
                logger.error(f"{e}")
                logger.warning(f"{num}:下载文件出错：{self.url}")
                time.sleep(self.delay)

    def remove(self):
        if not self.filepath:
            return
        if os.path.exists(self.filepath):
            os.remove(self.filepath)


def unzip(filepath, target='file', buffer_size=1024 * 64):
    """
    解压zip文件
    :param filepath:
    :param target:
    :param buffer_size:
    :return:
    """
    file_list = []
    with zipfile.ZipFile(filepath, 'r') as zip_file:
        for file_info in zip_file.infolist():
            filename = file_info.filename.encode('cp437').decode('gbk')
            file_path = os.path.join(target, filename)
            if file_info.is_dir():
                os.makedirs(file_path, exist_ok=True)
            else:
                os.makedirs(os.path.dirname(file_path), exist_ok=True)
                with zip_file.open(file_info) as source_file:
                    with open(file_path, "wb") as target_file:
                        while True:
                            buffer = source_file.read(buffer_size)
                            if not buffer:
                                break
                            target_file.write(buffer)
                file_list.append(file_path)
    return file_list


def get_file_content(filepath: str) -> str:
    if not os.path.exists(filepath):
        return ''
    with open(filepath, 'r', encoding='UTF-8') as file:
        result = file.read()
    return result
