from typing import Any

import aiofiles
import aiohttp
import httpx
import orjson
from httpx import AsyncClient

from beni.bdefine import MB

from . import bpath
from .btype import Null, XPath


async def get_bytes(
    url: str,
    *,
    headers: dict[str, Any] = Null,
    timeout: int = 10,
):
    async with AsyncClient() as client:
        client.headers.update(headers)
        client.timeout = timeout
        response = await client.get(url)
        return response.content


async def get_str(
    url: str,
    *,
    encoding: str = 'UTF8',
    headers: dict[str, Any] = Null,
    timeout: int = 10,
):
    data = await get_bytes(url, headers=headers, timeout=timeout)
    return data.decode(encoding)


async def get_json(
    url: str,
    *,
    headers: dict[str, Any] = Null,
    timeout: int = 10,
):
    data = await get_bytes(url, headers=headers, timeout=timeout)
    return orjson.loads(data)


async def post_bytes(
    url: str,
    *,
    data: bytes | dict[str, Any] = Null,
    headers: dict[str, Any] = Null,
    timeout: int = 10,
):
    async with AsyncClient() as client:
        client.headers.update(headers)
        client.timeout = timeout
        response = await client.post(url, json=data)
        return response.content


async def post_str(
    url: str,
    *,
    encoding: str = 'UTF8',
    data: bytes | dict[str, Any] = Null,
    headers: dict[str, Any] = Null,
    timeout: int = 10,
):
    data = await post_bytes(url, data=data, headers=headers, timeout=timeout)
    return data.decode(encoding)


async def post_json(
    url: str,
    *,
    data: bytes | dict[str, Any] = Null,
    headers: dict[str, Any] = Null,
    timeout: int = 10,
):
    data = await post_bytes(url, data=data, headers=headers, timeout=timeout)
    return orjson.loads(data)


async def download(url: str, file: XPath, timeout: int = 300, headers: dict[str, Any] = Null):
    file = bpath.get(file)
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers, timeout=aiohttp.ClientTimeout(total=timeout)) as response:
            bpath.make(file.parent)
            size_loaded = 0
            async with aiofiles.open(file, 'wb') as f:
                while True:
                    data = await response.content.read(1 * MB)
                    if data:
                        await f.write(data)
                        size_loaded += len(data)
                    else:
                        if 'Content-Encoding' not in response.headers:
                            assert response.content_length, '下载内容为空'
                            assert response.content_length == size_loaded, '下载为文件不完整'
                        break


def download_sync(url: str, file: XPath, timeout: int = 300, headers: dict[str, Any] = Null):
    """
    使用 httpx 同步流式下载大文件。
    """
    file = bpath.get(file)
    try:
        with httpx.Client(timeout=timeout) as client:
            with client.stream('GET', url, headers=headers) as response:
                response.raise_for_status()
                bpath.make(file.parent)
                size_loaded = 0
                with open(file, 'wb') as f:
                    for chunk in response.iter_bytes(chunk_size=MB):
                        if chunk:
                            f.write(chunk)
                            size_loaded += len(chunk)
                # 检查完整性（未压缩时）
                if 'Content-Encoding' not in response.headers:
                    content_length = response.headers.get('Content-Length')
                    if content_length:
                        assert int(content_length) == size_loaded, '下载为文件不完整'
                    else:
                        assert size_loaded > 0, '下载内容为空'
    except Exception:
        bpath.remove(file)
        raise
