"""自定义常用 URL 方法"""

from colorama import Fore, Style, init
from tqdm.asyncio import tqdm
import aiohttp
import asyncio
import os
import re
import shutil
import socket
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import Union, List, Tuple
import pyspeedtest
import requests

from .color_print import Print
from .get_tool_delta_version import get_tool_delta_version

# 使用方法 mirror_github[value: int].format(url: str)
mirror_github = [
    "https://tdload.tblstudio.cn/{}",
    "https://hub.gitmirror.com/{}",
    "https://gh.con.sh/{}",
    "https://mirror.ghproxy.com/{}",
]

# Initialize colorama
init(autoreset=True)


async def download_file_urls(download_url2dst: List[Tuple[str, str]]) -> None:
    """
    从给定的URL并发下载文件到指定的目标路径。

    Args:
        download_url2dst (List[Tuple[str, str]]): 包含多个元组的列表，每个元组包含：
            - url (str): 要下载的文件的URL。
            - dst (str): 下载的文件将保存的目标路径。

    Returns:
        None
    """

    async def download_file(
        session: aiohttp.ClientSession,
        url: str,
        i: int,
        sem: asyncio.Semaphore,
        sem2: asyncio.Semaphore,
        file_path: str,
    ) -> tqdm:
        """
        从给定的URL下载单个文件到指定的目标路径。

        Args:
            session (aiohttp.ClientSession): 用于发送HTTP请求的aiohttp客户端会话。
            url (str): 要下载的文件的URL。
            i (int): 下载任务的索引，用于进度条定位。
            sem (asyncio.Semaphore): 用于限制同时进行的下载任务数量的信号量。
            sem2 (asyncio.Semaphore): 用于限制显示的进度条数量的信号量。
            file_path (str): 下载的文件将保存的目标路径。

        Returns:
            tqdm: 下载任务的进度条。
        """
        async with sem2:
            progress_bar = tqdm(
                desc=f"• Downloading {Fore.CYAN}{url.split('/')[-1]}{Style.RESET_ALL}: {Fore.YELLOW}Pending...{Style.RESET_ALL}",
                total=0,
                unit="MB",
                unit_scale=True,
                bar_format="{desc} {n:.2f}MB/{total:.2f}MB",
                position=i,
            )
            async with sem:
                async with session.get(url) as response:
                    if response.status == 200:
                        filename = url.split("/")[-1]
                        total_size = int(response.headers.get("content-length", 0))
                        total_size_mb = total_size / (1024 * 1024)  # 转换为 MB
                        progress_bar.reset(total=total_size_mb)

                        progress_bar.set_description_str(
                            f"• Downloading {Fore.CYAN}{filename}{Style.RESET_ALL}: {Fore.YELLOW}In Progress...{Style.RESET_ALL}"
                        )
                        downloaded = 0

                        with open(file_path, "wb") as f:
                            async for chunk in response.content.iter_chunked(1024):
                                f.write(chunk)
                                downloaded += len(chunk)
                                progress_bar.update(
                                    len(chunk) / (1024 * 1024)
                                )  # 更新进度为 MB

                        progress_bar.set_description_str(
                            f"• Downloading {Fore.CYAN}{filename}{Style.RESET_ALL}: {Fore.GREEN}Succeed{Style.RESET_ALL}"
                        )
                        progress_bar.bar_format = "{desc}"  # 只显示描述
                        progress_bar.refresh()
                    else:
                        progress_bar.set_description_str(
                            f"• Downloading {Fore.CYAN}{url.split('/')[-1]}{Style.RESET_ALL}: {Fore.RED}Failed (HTTP {response.status}){Style.RESET_ALL}"
                        )
                        progress_bar.bar_format = "{desc}"  # 只显示描述
                        progress_bar.refresh()
            return progress_bar

    sem: asyncio.Semaphore = asyncio.Semaphore(4)  # 限制同时进行的下载任务数量为4
    sem2: asyncio.Semaphore = asyncio.Semaphore(10)  # 只显示 10 个下载任务

    async with aiohttp.ClientSession() as session:
        tasks: List[asyncio.Task] = []
        progress_bars: List[tqdm] = []

        for i, (url, dst) in enumerate(download_url2dst):
            os.makedirs(os.path.dirname(dst), exist_ok=True)
            task: asyncio.Task = asyncio.create_task(
                download_file(session, url, i, sem, sem2, dst)
            )
            tasks.append(task)

        progress_bars = await asyncio.gather(*tasks)
        for progress_bar in progress_bars:
            progress_bar.close()


def format_mirror_url(url: str) -> list:
    """填充 url 到镜像 url 列表

    Args:
        url (str): 原始 URL

    Returns:
        list: 填充原始 url 后的镜像列表
    """
    mir_url: list = []
    for mirror in mirror_github:
        mir_url.append(mirror.format(url))
    return mir_url


def githubdownloadurl_to_rawurl(url: str) -> str:
    """将 GitHub 下载链接转换为原始链接

    Args:
        url (str): 原始链接

    Returns:
        str: 原始链接
    """
    try:
        if url.startswith("https://github.com/"):
            return requests.head(url, allow_redirects=True).url
        return url
    except Exception:
        return url


def progress_bar(
    current: float | int,
    total: float | int,
    length: int | float = 20,
    color1: str = "§f",
    color2: str = "§b",
) -> str:
    """执行进度条

    Args:
        current (float | int): 当前进度值
        total (float | int): 总进度值
        length (int): 进度条长度.
        color1 (str): 进度条颜色 1.
        color2 (str): 进度条颜色 2.

    Returns:
        str: 格式化后的进度条字符串
    """
    pc = round(current / total * length)
    return Print.colormode_replace(
        color1 + " " * pc + color2 + " " * (20 - pc) + "§r ", 7
    )


def download_progress_bar(
    current_bytes: int, total_bytes: int, speed: float = 0
) -> None:
    """构建下载进度条

    Args:
        current_bytes (int): 当前已下载的字节数
        total_bytes (int): 文件总字节数
        speed ( float): 下载速度.
    """
    progressBar = progress_bar(current_bytes, total_bytes)
    b = f"{progressBar} {pretty_kb(current_bytes)}B / {pretty_kb(total_bytes)}B"
    if speed != 0:
        b += f" ({pretty_kb(speed)}B/s)    "
    with Print.lock:
        Print.print_with_info(b, "§a 下载 ", need_log=False, end="\r")


def pretty_kb(n: float) -> str:
    """将字节数转换为可读性更好的字符串表示形式

    Args:
        n (float): 字节数

    Returns:
        str: 可读性更好的字符串表示形式
    """
    if n >= 1048576:
        return f"{round(n / 1048576, 2)}M"
    if n >= 1024:
        return f"{round(n / 1024, 2)}K"
    return f"{round(n, 1)}"


def is_common_text_file(url_path: str) -> bool:
    """判断是否为常见的文本文件.

    Args:
        url_path (str): 文件路径

    Returns:
        bool: 是否为常见的文本文件
    """
    return any(
        url_path.endswith(i)
        for i in [
            ".txt",
            ".yml",
            ".md",
            ".xml",
            ".html",
            ".py",
            ".h",
            ".c",
            ".pyi",
            ".js",
            ".json",
        ]
    )


def get_file_size(url: str) -> Union[int, None]:
    """获取文件大小

    Args:
        url (str): 网址

    Returns:
        Union[int, None]: 文件大小（单位：字节）
    """
    response = requests.head(url, timeout=10)
    if "Content-Length" in response.headers:
        file_size = int(response.headers["Content-Length"])
        return file_size
    return None


def download_file_chunk(url: str, start_byte: int, end_byte: int, save_dir: str) -> int:
    """下载文件的代码块

    Args:
        url (str): 文件的 URL 地址
        start_byte (int): 下载的起始字节位置
        end_byte (int): 下载的结束字节位置
        save_dir (str): 文件保存的目录

    Returns:
        int: 已下载的字节数
    """
    headers = {"Range": f"bytes={start_byte}-{end_byte}"}
    response = requests.get(url, headers=headers, stream=True, timeout=10)
    response.raise_for_status()
    with open(save_dir + ".tmp", "rb+") as dwnf:
        dwnf.seek(start_byte)
        downloaded_bytes = 0
        for chunk in response.iter_content(chunk_size=8192):
            dwnf.write(chunk)
            downloaded_bytes += len(chunk)
        return downloaded_bytes


def download_file_singlethreaded(url: str, save_dir: str) -> None:
    """下载单个文件

    Args:
        url (str): 文件的 URL 地址
        save_dir (str): 文件保存的目录
        ignore_warnings (bool, optional): 是否忽略警告
    """
    with requests.get(url, stream=True, timeout=10) as res:
        res.raise_for_status()
        filesize = get_file_size(url)
        chunk_size = 8192
        useSpeed: float = 0
        # nowsize: 当前已下载的字节数
        nowsize: int = 0
        lastime: float = 1
        with open(save_dir + ".tmp", "wb") as dwnf:
            for chk in res.iter_content(chunk_size):
                nowtime = time.time()
                if nowtime != lastime:
                    useSpeed = chunk_size / (nowtime - lastime)
                    lastime = nowtime
                nowsize += len(chk)
                dwnf.write(chk)
                if nowsize % 81920 == 0 and filesize:  # 每下载 10 个数据块更新一次进度
                    download_progress_bar(nowsize, filesize, useSpeed)
        shutil.move(save_dir + ".tmp", save_dir)


def download_unknown_file(url: str, save_dir: str) -> None:
    """下载未知文件

    Args:
        url (str): 文件的 URL 地址
        save_dir (str): 文件保存的目录
    """
    # 鉴于 Content-Length 不一定表示文件原始大小，二进制文件与文本文件需要分开下载
    # 否则显示的下载条会异常
    resp = requests.get(url, timeout=10)
    resp.raise_for_status()
    if is_common_text_file(save_dir):
        # 文本文件，体积可能不大
        with open(save_dir, "wb") as f:
            f.write(resp.content)
    download_file_singlethreaded(url, save_dir)


def test_site_latency(Da: dict) -> list:
    """测试网站延迟

    Args:
        Da (dict): 包含 URL 和镜像 URL 的字典

    Returns:
        list: 按延迟排序的 URL 和延迟时间的元组列表
    """
    tmp_speed = {}
    urls = [Da["url"]] + Da["mirror_url"]

    with ThreadPoolExecutor() as executor:
        futures = [executor.submit(measure_latencyt, url) for url in urls]

        for future, url in zip(as_completed(futures), urls):
            try:
                latency = future.result(timeout=5)
                if latency != -1:
                    tmp_speed[url] = latency
            except Exception as e:
                Print.print_war(f"Error measuring latency for {url}: {e}")

    return sorted(tmp_speed.items(), key=lambda x: x[1], reverse=True)


def measure_latencyt(url: str) -> float:
    """测量延迟

    Args:
        url (str): 网址

    Raises:
        ValueError: 无效的网址

    Returns:
        float: 延迟时间
    """
    try:
        # 提取域名
        domain = re.search(r"(?<=http[s]://)[.\w-]*(:\d{1,8})?((?=/)|(?!/))", url)
        if isinstance(domain, type(None)):  # 如果没有匹配到域名
            raise ValueError("Invalid URL")
        st = pyspeedtest.SpeedTest(domain.group())  # 传入域名
        download_speed = st.download()
        return download_speed
    except Exception as e:
        Print.print_war(f"Error measuring latency for {url}: {e}")
    return -1.0  # 返回 -1 表示测速失败


def get_free_port(start: int = 2000, end: int = 65535) -> int:
    """获取空闲端口号

    Args:
        start (int, optional): 起始端口号.
        end (int, optional): 结束端口号.

    Raises:
        Exception: 未找到空闲端口

    Returns:
        int: 空闲端口号
    """
    for port in range(start, end):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            try:
                s.bind(("localhost", port))
                return port
            except OSError:
                Print.print_war(f"端口 {port} 正被占用，跳过")
    raise ValueError(f"未找到空闲端口 ({start}~{end})")


def check_update() -> None:
    """检查更新"""
    try:
        latest_version: str = requests.get(
            "https://tdload.tblstudio.cn/https://api.github.com/repos/ToolDelta/ToolDelta/releases/latest",
            timeout=5,
        ).json()["tag_name"]
        current_version = ".".join(map(str, get_tool_delta_version()[:3]))

        if not latest_version.replace(".", "") <= current_version.replace(".", ""):
            # Print.print_suc(f"当前为最新版本 -> v{current_version}，无需更新")
            Print.print_load(
                f"检测到最新版本 {current_version} -> {latest_version}，请及时更新！"
            )
    except KeyError:
        Print.print_war("获取最新版本失败，请检查网络连接")
    except Exception as err:
        Print.print_war(f"无法获取最新版本: {str(err)[:30]}.., 已忽略")


def if_token() -> None:
    """检查路径下是否有 fbtoken，没有就提示输入

    Raises:
        SystemExit: 未输入 fbtoken
    """
    if not os.path.isfile("fbtoken"):
        Print.print_inf(
            "请到对应的验证服务器官网下载 FBToken，并放在本目录中，或者在下面输入 fbtoken"
        )
        fbtoken = input(Print.fmt_info("请输入 fbtoken: ", "§b 输入 "))
        if fbtoken:
            with open("fbtoken", "w", encoding="utf-8") as f:
                f.write(fbtoken)
        else:
            Print.print_err("未输入 fbtoken, 无法继续")
            raise SystemExit


def fbtokenFix():
    """修复 fbtoken 里的换行符"""
    with open("fbtoken", "r", encoding="utf-8") as f:
        token = f.read()
        if "\n" in token:
            Print.print_war("fbtoken 里有换行符，会造成 fb 登陆失败，已自动修复")
            with open("fbtoken", "w", encoding="utf-8") as f:
                f.write(token.replace("\n", ""))
