#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Project      : MeUtils.
# @File         : file
# @Time         : 2022/7/5 下午3:31
# @Author       : yuanjie
# @WeChat       : meutils
# @Software     : PyCharm
# @Description  :

import mimetypes

import shortuuid

from meutils.pipe import *
from meutils.decorators.retry import retrying
from meutils.caches import rcache, cache
from meutils.oss.minio_oss import Minio

# from fastapi import UploadFile 有点区别
from starlette.datastructures import UploadFile
from contextlib import asynccontextmanager
from httpx import AsyncClient
from PIL import Image


def guess_mime_type(file, default: str = "application/octet-stream"):
    mime_type = None
    if isinstance(file, str):
        file = file.split("?", maxsplit=1)[0].strip()
        mime_type, _ = mimetypes.guess_type(file)
    return mime_type or default


def file_append_firstline(line):
    with open('untitled.txt', "r+") as f:
        old = f.read()

        f.seek(0)
        f.write(line)
        f.write(old)


def base64_to_bytes(base64_image_string):
    """
    # 将字节数据写入图片文件
    image_data = base64_to_bytes(...)
    with open(filename, 'wb') as file:
        file.write(image_data)
    """
    return base64.b64decode(base64_image_string.split(",", 1)[-1])


async def to_bytes(
        file: Union[UploadFile, str, bytes],
        headers: Optional[dict] = None
):
    """

    :param file: 文件对象、路径、base64、url
    :param headers: httpx

    :return: todo: bytes、filepath、io.BytesIO
    """
    # assert file

    if isinstance(file, list):
        tasks = [to_bytes(_, headers) for _ in file]
        file_bytes_list = await asyncio.gather(*tasks)
        return file_bytes_list

    if isinstance(file, bytes):
        logger.debug(f"FileType: BYTES")

        return file

    elif isinstance(file, str):
        if file.startswith('http'):
            logger.debug(f"FileType: HTTP")

            async with AsyncClient(
                    headers=headers or {},
                    timeout=120,
                    verify=False
            ) as cilent:  # todo: 缓存 根据大小 判断有效链接  todo 无效链接 下载超时 等等
                resp = await cilent.get(file)
                file_bytes = resp.content

        elif file.startswith('data:') and ";base64," in file or len(file) > 1024:
            logger.debug(f"FileType: BASE64")

            file_bytes = base64_to_bytes(file)

        elif Path(file).is_file():  # file
            logger.debug(f"FileType: PATH")

            file_bytes = Path(file).read_bytes()

        else:
            raise Exception(f"FileType: MAY BASE64 or NOT EXIST")

        return file_bytes

    elif isinstance(file, UploadFile):
        file_bytes = await file.read()
        return file_bytes


@asynccontextmanager
async def to_tempfile(file: Union[UploadFile, str]):
    """

    :param file: 文件对象、路径、base64、url
    :return: todo: bytes、filepath、io.BytesIO
    """
    file_bytes = await to_bytes(file)

    with tempfile.NamedTemporaryFile(mode='wb+') as temp:
        temp.write(file_bytes)
        temp.seek(0)

        logger.debug(temp.name)

        yield temp.name


@retrying()
async def to_url_fal(
        file: Union[str, bytes, List],
        filename: Optional[str] = None,
        headers: Optional[dict] = None,
        content_type: str = "application/octet-stream",
):
    """对象存储 todo: minio"""
    if isinstance(file, list):
        tasks = [to_url_fal(_, filename, headers, content_type) for _ in file]
        urls = await asyncio.gather(*tasks)
        return urls

    if not file: return

    if isinstance(file, str) and file.startswith("http"):  # 转存： todo: base64
        content_type = mimetypes.guess_type(file)[0] or content_type

    file = await to_bytes(file, headers=headers)
    content_type = (
            mimetypes.guess_type(filename or '')[0]
            or mimetypes.guess_type(f"x.{content_type}")[0]  # format: image/png
            or content_type
    )

    import fal_client

    url = await fal_client.upload_async(data=file, content_type=content_type, file_name=filename)
    return url


async def to_url(
        file: Union[str, bytes, List],
        filename: Optional[str] = None,
        headers: Optional[dict] = None,

        content_type: Optional[str] = None,
        mime_type: Optional[str] = None,

):  # 传入 url 是否 转存
    if not file: return

    content_type = content_type or mime_type

    if isinstance(file, list):
        tasks = [to_url(_, f"{shortuuid.random()}_{filename}", headers, content_type=content_type) for _ in file]
        urls = await asyncio.gather(*tasks)
        return urls

    file = await to_bytes(file, headers=headers)
    file_url = await Minio().upload(file, filename, content_type=content_type)
    return file_url


async def to_base64(file: Union[UploadFile, str, bytes, list], content_type: Optional[str] = None):
    if isinstance(file, list):
        tasks = [to_base64(_, content_type) for _ in file]
        return await asyncio.gather(*tasks)

    if not file: return

    # logger.debug(file)await to_base64(request.input_reference)
    _ = base64.b64encode(await to_bytes(file)).decode('utf-8')

    # if content_type := content_type or mimetypes.guess_type(file)[0]:  # "image/png"
    if content_type:  # "image/png"
        _ = f"data:{content_type};base64,{_}"

    return _


def base64_to_file(base64_image_string, filename):
    image_data = base64_to_bytes(base64_image_string)
    with open(filename, 'wb') as file:
        file.write(image_data)


def file_to_base64(file):
    _ = base64.b64encode(Path(file).read_bytes()).decode('utf-8')
    content_type = mimetypes.guess_type(file)[0] or "data:image/jpeg"

    return f"data:{content_type},{_}"


async def to_file(file: Union[UploadFile, str, bytes], filename: Optional[str] = None):
    file = await to_bytes(file)
    if not filename:  # 临时文件
        with tempfile.NamedTemporaryFile(mode='wb+', delete=False) as temp:
            temp.write(file)
            temp.seek(0)
            return temp.name
    else:
        Path(filename).write_bytes(file)
        return Path(filename).resolve()


async def markdown_base64_to_url(text, pattern=r'!\[.*?\]\((.*?)\)'):
    """
    :param text:
    :param pattern:
        pattern=r'!\[.*?\]\((data:image/.*?)\)'
    :return:
    """
    base64_strings = re.findall(pattern, text)

    # logger.debug(text)
    # logger.debug(base64_strings)

    tasks = [to_url(base64_string, filename=f"{shortuuid.random()}.png") for base64_string in base64_strings]
    urls = await asyncio.gather(*tasks)

    for base64_string, url in zip(base64_strings, urls):
        text = text.replace(base64_string, url)
    return text


async def get_file_duration(filename: str = ".mp4", url: Optional[str] = None, content: Optional[bytes] = None,
                            headers: Optional[dict] = None):
    # Path(url.split('?')[0]).name
    headers = {
        "Range": "bytes=0-8191"
    }
    async with httpx.AsyncClient(timeout=200, follow_redirects=True) as client:
        for i in range(2):
            if url:
                response = await client.get(url=url, headers=headers)
                response.raise_for_status()

                content = response.content
            elif content is None:
                raise ValueError("url or content is required")

            from tinytag import TinyTag

            tag = TinyTag.get(filename=filename, file_obj=io.BytesIO(content), ignore_errors=False)

            logger.debug(f"duration: {tag.duration}")
            if tag.duration and tag.duration > 0.5:
                break
            else:
                headers = None

        return int(np.ceil(tag.duration or 10))


async def to_png(image: Union[bytes, List[bytes], str, List[str]], response_format: str = 'bytes'):
    """
    将 WebP 二进制数据无损转换为 PNG 二进制数据
    """
    if isinstance(image, list):
        tasks = [to_png(_, response_format) for _ in image]
        return await asyncio.gather(*tasks)

    if isinstance(image, str):
        image = await to_bytes(image)
        if not image:
            raise Exception(f"invalid image: {image}")

    with Image.open(io.BytesIO(image)) as im:
        out = io.BytesIO()
        im.save(out, format="PNG")  # 无损

        if response_format != "bytes":
            return await to_base64(out.getvalue(), content_type="image/png")

        return out.getvalue()


async def to_image(
        image: Union[bytes, List[bytes], str, List[str]],
        to_image_format: str = 'JPEG',  # PNG JPEG
        response_format: Literal["bytes", "base64", "url"] = "url",
        mini_aspect_ratio: Optional[str] = None
):
    """
    将 WebP 二进制数据无损转换为 PNG 二进制数据
    """
    logger.debug(image)

    if isinstance(image, list):
        tasks = [to_image(_, to_image_format, response_format) for _ in image]
        return await asyncio.gather(*tasks)

    if isinstance(image, str):
        # if guess_mime_type(image) == to_image_format.lower():  # 无需转换
        #     return image

        # logger.debug(image)

        image = await to_bytes(image)
        if not image:
            raise Exception(f"invalid image: {image}")

    with Image.open(io.BytesIO(image)) as img:
        out = io.BytesIO()
        # if img.mode == 'RGBA':
        #     logger.debug(img.mode)
        #     # 创建白色背景（或任何你想要的背景色）
        #     background = Image.new('RGB', img.size, (255, 255, 255))
        #     # 将 RGBA 图像粘贴到背景上，使用 Alpha 通道作为掩码
        #     background.paste(img, mask=img.split()[-1])
        #     img = background
        # elif img.mode != 'RGB':
        #     img = img.convert('RGB')

        img = img.convert('RGB')
        img.save(out, format=to_image_format.upper())

        image_bytes = out.getvalue()

        if mini_aspect_ratio:
            w, h = map(int, mini_aspect_ratio.split(':'))
            if not (min(w / h, h / w) < img.width / img.height < max(w / h, h / w)):
                logger.debug(f"resize: {mini_aspect_ratio}")
                target_ratio = min(w / h, h / w) + 0.01  # mini_aspect_ratio
                image_bytes = await image_resize(image_bytes, target_ratio=target_ratio, response_format="bytes")

        logger.debug(f"response_format: {response_format}")

        if response_format == "bytes":
            return image_bytes

        elif response_format == "url":
            return await to_url(image_bytes, filename=f"{shortuuid.random()}.{to_image_format.lower()}")

        else:
            return await to_base64(image_bytes, content_type=f"image/{to_image_format.lower()}")


async def do_file_data(
        file_or_str_list: Union[str, List[str], UploadFile, List[UploadFile]],
        input_reference_format: Optional[str] = None,
        content_type: Optional[str] = None,

        # todo 图片格式
):
    file = file_or_str_list

    if isinstance(file, list):
        tasks = [do_file_data(_, input_reference_format, content_type) for _ in file]
        return await asyncio.gather(*tasks)

    # 针对单文件
    if isinstance(file, str):  # url1;url2
        if ';' in file:
            return file.split(';')
        elif '|' in file:
            return file.split('|')

        return file

    elif isinstance(file, UploadFile):
        if not file.filename: return None

        content_type = content_type or file.content_type
        if input_reference_format in {"base64", "b64"}:
            file = await to_base64(await file.read(), content_type=content_type)

        elif input_reference_format == "oss":  # to url todo海外服务器
            file = await to_url(await file.read(), content_type=content_type)  # todo: 图片类型变换

        else:  # fal url
            file = await to_url_fal(await file.read(), content_type=content_type)

    return file  # 要返回 list?


async def image_resize(image: Union[bytes, str], target_ratio: Union[float, str], response_format,
                       fill_color=(0, 0, 0)):
    """
    将图片缩放到目标比例，保持完整内容，不足部分填充背景色

    output_path

    :param target_ratio: 目标宽高比 (width/height)，如 16/9
    """
    w = h = 1
    if isinstance(target_ratio, str):

        if 'x' in target_ratio:
            w, h = map(int, target_ratio.split('x'))
        elif ':' in target_ratio:
            w, h = map(int, target_ratio.split(':'))

        target_ratio = w / h

    _ = await to_bytes(image)
    img = Image.open(io.BytesIO(_))

    img_ratio = img.width / img.height

    # 计算缩放后的尺寸
    if img_ratio > target_ratio:
        # 原图更宽，以宽度为基准
        new_width = img.width
        new_height = int(img.width / target_ratio)
    else:
        # 原图更高或正好，以高度为基准
        new_height = img.height
        new_width = int(img.height * target_ratio)

    if w > 32:
        new_width, new_height = w, h

    # 创建目标尺寸画布并居中粘贴
    result = Image.new('RGB', (new_width, new_height), fill_color)

    offset_x = (new_width - img.width) // 2
    offset_y = (new_height - img.height) // 2
    result.paste(img, (offset_x, offset_y))

    if 'bytes' in response_format:
        format = "JPEG"
        # 创建一个字节流缓冲区
        buffered = io.BytesIO()
        # 将图像保存到缓冲区
        result.save(buffered, format)
        # 获取缓冲区的字节内容
        img_bytes = buffered.getvalue()

        return img_bytes

    elif 'base64' in response_format:
        format = "JPEG"
        # 创建一个字节流缓冲区
        buffered = io.BytesIO()
        # 将图像保存到缓冲区
        result.save(buffered, format)
        # 获取缓冲区的字节内容
        img_bytes = buffered.getvalue()
        # 将字节内容编码为Base64字符串
        img_base64 = base64.b64encode(img_bytes).decode("utf-8")
        return f"data:image/{format.lower()};base64,{img_base64}"

    elif response_format == 'url':
        format = "JPEG"
        # 创建一个字节流缓冲区
        buffered = io.BytesIO()
        # 将图像保存到缓冲区
        result.save(buffered, format)
        # 获取缓冲区的字节内容
        img_bytes = buffered.getvalue()

        return await to_url(img_bytes, f"{shortuuid.random()}.jpg")

    else:
        result.save(response_format)
        print(f"✅ {image} -> {response_format}")
        print(f"   原尺寸: {img.width}x{img.height} (比例: {img_ratio:.2f})")
        print(f"   新尺寸: {new_width}x{new_height} (比例: {target_ratio:.2f})")
        return result


if __name__ == '__main__':
    # import tempfile
    #
    # # 使用上下文管理器自动处理文件的关闭和删除
    # with tempfile.NamedTemporaryFile(mode='wb+') as temp:
    #     temp.write(b"This is a temporary file.")
    #     temp.seek(0)
    #     print(f"文件内容: {temp.read()}")
    #     print(f"临时文件名: {temp.name}")
    # 文件在这里自动关闭和删除

    # arun(to_bytes(''))

    # arun(to_url("x.png"))

    # url = "https://storage.googleapis.com/bsp-remini-image-in-web-us-central1-autodelete/54a2d77f-3070-4a95-8a1b-906ac1c74d44/64439c90-c628-47e0-9e1b-36dbbbd06664/ba502f58/input.jpg?X-Goog-Algorithm=GOOG4-HMAC-SHA256&X-Goog-Credential=GOOG1ETQDJI557KBP4YD5TQG6FMZHVKCP3S53FHI6XLBYYMT24W3PZAZNZZWQ%2F20241014%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20241014T060335Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=host&X-Goog-Signature=b7745e3639a1cb81465409bfa7ed20801c5b997945739810c32cb4af02304893"

    # arun(to_base64(url))[:100]

    url = "https://oss.ffire.cc/files/kling_watermark.png"

    # arun(to_file(url, filename='x.jpg'))

    file = Path("/Users/betterme/PycharmProjects/AI/MeUtils/meutils/io/x.py").read_bytes()

    # arun(to_url_fal([file] * 1))

    # print(mimetypes.guess_type("http://url"))
    # print(mimetypes.guess_type("http://url.pdf"))
    # print(mimetypes.guess_type("http://url.php"))
    #
    # print(mimetypes.guess_type("xx.txt"))
    # print(mimetypes.guess_type("xx.html"))
    # print(mimetypes.guess_type("xx.mp3"))
    # print(mimetypes.guess_type("xx.mp4"))

    # print(guess_mime_type("http://url")) # application # msword

    # arun(to_bytes(None))

    # print(mimetypes.guess_type("x.jpg"))
    # print(mimetypes.guess_type("x.png"))
    # print(mimetypes.guess_type("x.jpg"))

    # print(mimetypes.guess_extension("x.mp4", False))

    # arun(to_url(
    #     "https://cdn.hailuoai.video/moss/prod/2024-11-11-09/video/1731287464150180347-video_raw_8ba15c5c206f8d393a9248f4f9215ed8_312186282087260162.mp4",
    #     content_type=None))

    # arun(to_url_fal(url))
    # print(guess_mime_type(b"base64xxxxxxxxxxxxxxxxxx.mp4"))

    # arun(to_url([Path('img_1.png').read_bytes()], filename='x.png'))
    file = "/Users/betterme/PycharmProjects/AI/ppt.txt"
    arun(to_url(Path(file).read_bytes(), filename='ppt.txt'))

    # arun(markdown_base64_to_url("![image](data:imagexxxxx)", pattern=r'!\[image\]\((.+?)\)'))

    # arun(to_bytes("https://oss.ffire.cc/files/kling_watermark.png"))

    # file = "https://v3.fal.media/files/penguin/Rx-8V0MVgkVZM6PJ0RiPD_douyin.mp4"
    # arun(to_bytes(file))

    # print(guess_mime_type("http://admin.ilovechatgpt.top/file/ceshiwendangdocx_31118702.docx "))

    url = "https://juzhen-1318772386.cos.ap-guangzhou.myqcloud.com/mj/2025/06/07/7b347a36-8146-4d3d-a5dc-0b8dc365817d.png"
    # r = arun(to_bytes(url))

    print(mimetypes.guess_type(url)[0])

    # url = "https://lmdbk.com/5.mp4"
    # url = "https://v3.fal.media/files/kangaroo/y5-1YTGpun17eSeggZMzX_video-1733468228.mp4"
    # content = requests.get(url).content
    #
    # url = "https://fal.media/files/koala/8teUPbRRMtAUTORDvqy0l.mp4"
    #
    # with timer():
    #     # arun(get_file_duration(content=content))
    #     arun(get_file_duration(url=url))

    # r = arun(to_url([]))
    # text = "这是一个示例文本，包含一个图片：![image](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAQACAIAAADwf7zUAAAgAElEQ) 这张图片很棒。"
    #
    # arun(markdown_base64_to_url(
    #     text=text,
    #     # pattern=r'!\[.*?\]\((data:image/.*?)\)'
    #     # pattern=r'!\[.*?\]\((.*?)\)'
    #
    # )
    # )

    # webp_bytes = Path("/Users/betterme/PycharmProjects/AI/MeUtils/meutils/apis/images/image1.webp").read_bytes()
    # webp_bytes = Path("image_0.emf",).read_bytes()
    #
    # arun(to_png(webp_bytes, response_format='b64'))

    # url = "https://r2.syntx.ai/user_5317909153/uploaded/5a8b8eaabe4d56957053cb5dee790abf_photo_2025-09-17_17-03-07_1758152505.jpg"
    # url = "https://mjimg.zhanjuzhe.cn/raw/banana_media/202509/17/20250917110929784771.JPG".lower()
    # arun(to_png(url, response_format='b64'))

    # guess_mime_type('xx.jpg')

    # arun(to_base64('a'))
    url = "https://lmdbk.com/5.mp4"
    url = "https://v15-kling.klingai.com/bs2/upload-ylab-stunt-sgp/minimax_tts/05622a13394bc31ce681ada011462007/audio.mp3"
    # arun(get_file_duration(filename='.mp4', url=url))

    # arun(do_file_data([None]))

    image = ["https://v3.fal.media/files/penguin/XoW0qavfF-ahg-jX4BMyL_image.webp"] * 2

    image = "https://v3.fal.media/files/penguin/XoW0qavfF-ahg-jX4BMyL_image.webp"

    image = "https://kimi-web-img.moonshot.cn/img/www.vhv.rs/50fef4f80227d1d7d2ae212599fc4a3d2fa91c81.png"

    image = "https://cdnzjzai.m.nengshuohuihua.com/banana/refer/202602/02/20260202152544761374.jpeg?auth_key=1770017144-86e367a61b7a46a7894a1072dfe8a539-0-060089b876fb3c713bdafcfbb16ac844"
    # print(guess_mime_type(image))
    # arun(to_image(image))

    # arun(to_url(image, content_type="image/jpeg"))

    url = "https://ark-common-storage-prod-cn-beijing.tos-cn-beijing.volces.com/doubao-seedance-2-0/2104716667/0/20260212/02177089914238900000000000000000000ffffac183fc8d5d6cf.mp4%3FX-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTYWJkZTExNjA1ZDUyNDc3YzhjNTM5OGIyNjBhNDcyOTQ%252F20260212%252Fcn-beijing%252Ftos%252Frequest&X-Tos-Date=20260212T123107Z&X-Tos-Expires=86400&X-Tos-Signature=065ea666a698224b237f7c0be0e31cc01a9ff7bd99caf0f90b1123095cf23cd4&X-Tos-SignedHeaders=host?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTMjgxMzUwNzliYzdlNDE4MTllYjJjZGVlOWQ3N2M1ZDY%2F20260212%2Fcn-beijing%2Ftos%2Frequest&X-Tos-Date=20260212T135308Z&X-Tos-Expires=604800&X-Tos-Signature=b982ef2fb2b2acffe10192a0412781ee57b3fdaa26af6fccbbe8cb40022dd22f&X-Tos-SignedHeaders=host"

    arun(to_url(url, content_type="video/mp4"))
