# -*- coding: utf-8 -*-
"""Cloud Drive Management - list, add, remove cloud storage providers."""

import sys
import os
import urllib.parse
import urllib.request

import xbmc
import xbmcplugin
import xbmcgui

from .cd2_client import get_client
from .cd2_constants import (
    MODE_CLOUD_ADD_QR, MODE_CLOUD_REMOVE, MODE_CLOUD_SPACE,
    MODE_CLOUD_ADD_ALIST, MODE_CLOUD_ADD_SMB, MODE_SMB_SHARES, MODE_SMB_LOGIN,
    MODE_NOOP, MODE_PLAY_FILE, QR_LOGIN_PROVIDERS,
    MODE_FILE_BROWSE, MODE_DOWNLOAD_FILE, CD2_DEFAULT_HOST, CD2_DEFAULT_PORT,
)
from .cd2_l10n import _, __
from . import cd2_utils

log = cd2_utils.log


def show_cloud_list(handle):
    """Display list of all configured cloud drives."""
    client = get_client()
    xbmcplugin.setContent(handle, "files")

    try:
        resp = client.get_all_cloud_apis(timeout=10)
    except Exception as e:
        log(f"Failed to get cloud APIs: {e}")
        xbmcgui.Dialog().ok(_(32030), _(32034, e))
        xbmcplugin.endOfDirectory(handle)
        return

    cloud_apis = []
    if hasattr(resp, 'apis') and resp.apis:
        cloud_apis = resp.apis

    if cloud_apis:
        for api in cloud_apis:
            name = api.name if hasattr(api, 'name') else str(api)
            user_name = api.userName if hasattr(api, 'userName') else ""
            is_locked = api.isLocked if hasattr(api, 'isLocked') else False

            label = f"[B]{name}[/B]"
            if is_locked:
                label += f"  [COLOR red]{_(32150)}[/COLOR]"

            li = xbmcgui.ListItem(label)
            li.setProperty("IsPlayable", "false")
            cmds = [
                (_(32060), f"RunPlugin({_build_url(MODE_CLOUD_SPACE, cloud_name=name)})"),
                (_(32061), f"RunPlugin({_build_url(MODE_CLOUD_REMOVE, cloud_name=name, user_name=user_name)})"),
            ]
            li.addContextMenuItems(cmds)
            noop_url = _build_url(MODE_NOOP)
            xbmcplugin.addDirectoryItem(handle, noop_url, li, isFolder=False)

    else:
        li = xbmcgui.ListItem(_(32062))
        li.setProperty("IsPlayable", "false")
        noop_url = _build_url(MODE_NOOP)
        xbmcplugin.addDirectoryItem(handle, noop_url, li, isFolder=False)

    xbmcplugin.endOfDirectory(handle)


def show_cloud_add_menu(handle):
    """Show menu of cloud providers that can be added."""
    xbmcplugin.setContent(handle, "files")

    # ALIST (fixed-param WebDAV) — action, not a directory
    url = _build_url(MODE_CLOUD_ADD_ALIST)
    li = xbmcgui.ListItem(_(32069))
    li.setProperty("IsPlayable", "false")
    xbmcplugin.addDirectoryItem(handle, url, li, isFolder=False)

    # SMB — navigates to server list directory
    url = _build_url(MODE_CLOUD_ADD_SMB)
    li = xbmcgui.ListItem(_(32021))
    li.setProperty("IsPlayable", "false")
    xbmcplugin.addDirectoryItem(handle, url, li, isFolder=True)

    # QR code login providers — open custom window, not a directory
    for key, provider in QR_LOGIN_PROVIDERS.items():
        url = _build_url(MODE_CLOUD_ADD_QR, provider=key)
        li = xbmcgui.ListItem(_(32068, provider['name'], provider['desc']))
        li.setProperty("IsPlayable", "false")
        xbmcplugin.addDirectoryItem(handle, url, li, isFolder=False)

    xbmcplugin.endOfDirectory(handle)


def add_alist_api():
    """Add local ALIST WebDAV storage with fixed parameters."""
    if not _check_can_add():
        return
    log("add_alist_api called")
    client = get_client()
    try:
        client.add_webdav(
            server_url="http://127.0.0.1:5244/dav",
            user_name="admin",
            password="123456",
            do_not_sync=True,
            timeout=10,
        )
        xbmcgui.Dialog().notification("CloudDrive2", _(32012), time=3000)
    except Exception as e:
        log(f"add_alist_api failed: {e}", xbmc.LOGERROR)
        xbmcgui.Dialog().ok(_(32030), _(32055, str(e)))


def show_smb_servers(handle):
    """Discover and list SMB servers on the LAN."""
    client = get_client()
    xbmcplugin.setContent(handle, "files")

    try:
        resp = client.discover_smb_servers(timeout=15)
    except Exception as e:
        log(f"DiscoverSmbServers failed: {e}")
        xbmcgui.Dialog().ok(_(32030), _(32053, str(e)))
        xbmcplugin.endOfDirectory(handle)
        return

    servers = resp.servers if hasattr(resp, 'servers') and resp.servers else []

    if servers:
        for srv in servers:
            name = srv.name if hasattr(srv, 'name') else ""
            addr = srv.address if hasattr(srv, 'address') else ""
            label = _(32024, f"{name or addr} ({addr})") if name else addr
            url = _build_url(MODE_SMB_SHARES, server=addr or name)
            li = xbmcgui.ListItem(label)
            li.setProperty("IsPlayable", "false")
            xbmcplugin.addDirectoryItem(handle, url, li, isFolder=True)
    else:
        li = xbmcgui.ListItem(f"[{_(32023)}]")
        li.setProperty("IsPlayable", "false")
        xbmcplugin.addDirectoryItem(handle, _build_url(MODE_NOOP), li, isFolder=False)

    xbmcplugin.endOfDirectory(handle)


def show_smb_shares(handle, server):
    """Prompt for SMB credentials, then list shares on a server."""
    dialog = xbmcgui.Dialog()
    user = dialog.input(_(32026, server), "")
    if not user:
        xbmcplugin.endOfDirectory(handle)
        return
    pw = dialog.input(_(32027, server), "", xbmcgui.INPUT_ALPHANUM, xbmcgui.ALPHANUM_HIDE_INPUT)
    if pw is None:
        pw = ""
    workgroup = dialog.input(_(32029, server), "")
    if workgroup is None:
        workgroup = ""

    client = get_client()
    xbmcplugin.setContent(handle, "files")

    try:
        resp = client.discover_smb_shares(server, user, pw, workgroup=workgroup, timeout=15)
    except Exception as e:
        log(f"DiscoverSmbShares failed: {e}")
        xbmcgui.Dialog().ok(_(32030), _(32053, str(e)))
        xbmcplugin.endOfDirectory(handle)
        return

    shares = resp.shareNames if hasattr(resp, 'shareNames') and resp.shareNames else []

    if shares:
        for share in shares:
            url = _build_url(MODE_SMB_LOGIN,
                            server=server, share=share,
                            user=user, password=pw, workgroup=workgroup)
            li = xbmcgui.ListItem(share)
            li.setProperty("IsPlayable", "false")
            xbmcplugin.addDirectoryItem(handle, url, li, isFolder=True)
    else:
        li = xbmcgui.ListItem(f"[{_(32054)}]")
        li.setProperty("IsPlayable", "false")
        xbmcplugin.addDirectoryItem(handle, _build_url(MODE_NOOP), li, isFolder=False)

    xbmcplugin.endOfDirectory(handle)


def do_login_smb(server, share, user, password, workgroup=""):
    """Add an SMB share as a cloud drive."""
    if not _check_can_add():
        return
    log(f"do_login_smb: server={server}, share={share}, user={user}")
    client = get_client()
    try:
        client.add_smb(server=server, share=share,
                       user_name=user, password=password,
                       workgroup=workgroup, do_not_sync=True, timeout=10)
        xbmcgui.Dialog().notification("CloudDrive2", _(32012), time=3000)
    except Exception as e:
        log(f"LoginSmb failed: {e}", xbmc.LOGERROR)
        xbmcgui.Dialog().ok(_(32030), _(32028, str(e)))


def show_search(handle):
    """Search files within a selected cloud drive."""
    client = get_client()
    dialog = xbmcgui.Dialog()

    # Step 1: Choose cloud drive
    cloud_names = []
    try:
        resp = client.get_all_cloud_apis(timeout=10)
        if hasattr(resp, 'apis') and resp.apis:
            cloud_names = [api.name for api in resp.apis if hasattr(api, 'name') and api.name]
    except Exception:
        pass

    if not cloud_names:
        dialog.ok(_(32030), _(32062))
        xbmcplugin.endOfDirectory(handle)
        return

    idx = dialog.select(_(32008), cloud_names)
    if idx < 0:
        xbmcplugin.endOfDirectory(handle)
        return
    target = cloud_names[idx]
    search_path = "/" + target

    # Step 2: Enter keyword
    keyword = dialog.input(_(32009), "")
    if not keyword:
        xbmcplugin.endOfDirectory(handle)
        return

    # Step 3: Search
    xbmcplugin.setContent(handle, "files")
    try:
        results = client.search_files(keyword, path=search_path, timeout=30)
    except Exception as e:
        log(f"Search failed: {e}")
        dialog.ok(_(32030), _(32085, str(e)))
        xbmcplugin.endOfDirectory(handle)
        return

    files = []
    if hasattr(results, '_list'):
        files = results._list
    elif hasattr(results, '__iter__'):
        files = list(results)

    if files:
        for f in files:
            file_name = (f.get("name") or f.get("fileName", "?")) if hasattr(f, 'get') else getattr(f, 'name', '?')
            file_size = f.get("size") or f.get("size", 0) if hasattr(f, 'get') else getattr(f, 'size', 0)
            try:
                file_size = int(file_size)
            except (TypeError, ValueError):
                file_size = 0
            full_path = f.get("fullPathName") or f.get("path", "") if hasattr(f, 'get') else getattr(f, 'fullPathName', '')
            is_dir = f.get("isDirectory") or f.get("isDirectory", False) if hasattr(f, 'get') else getattr(f, 'isDirectory', False)

            size_str = cd2_utils.format_size(file_size) if file_size else ""
            label = f"[B]{file_name}[/B]"
            if size_str:
                label += f"  {size_str}"

            if is_dir:
                # Directories not playable
                li = xbmcgui.ListItem(label, full_path)
                li.setProperty("IsPlayable", "false")
                xbmcplugin.addDirectoryItem(handle, _build_url(MODE_NOOP), li, isFolder=False)
            else:
                # Files: click to play
                url = _build_url(MODE_PLAY_FILE, path=full_path, name=file_name)
                li = xbmcgui.ListItem(label, full_path)
                li.setProperty("IsPlayable", "true")
                xbmcplugin.addDirectoryItem(handle, url, li, isFolder=False)
    else:
        li = xbmcgui.ListItem(f"[{_(32052)}]")
        li.setProperty("IsPlayable", "false")
        xbmcplugin.addDirectoryItem(handle, _build_url(MODE_NOOP), li, isFolder=False)

    xbmcplugin.endOfDirectory(handle)


def remove_cloud_api(cloud_name, user_name=""):
    """Remove a cloud API by name and username."""
    log(f"remove_cloud_api called: cloud_name={cloud_name}, user_name={user_name}")
    client = get_client()
    try:
        resp = client.remove_cloud_api(cloud_name, user_name, timeout=10)
        log(f"remove_cloud_api success: {resp}")
        xbmcgui.Dialog().notification("CloudDrive2", _(32067, cloud_name), time=3000)
    except Exception as e:
        log(f"remove_cloud_api failed: {e}", xbmc.LOGERROR)
        xbmcgui.Dialog().notification("CloudDrive2", _(32050, e))


def show_cloud_space(cloud_name):
    """Show space info for a cloud drive."""
    client = get_client()
    try:
        info = client.get_space_info(cloud_name, timeout=10)
        total = cd2_utils.format_size(info.totalSpace if hasattr(info, 'totalSpace') else 0)
        used = cd2_utils.format_size(info.usedSpace if hasattr(info, 'usedSpace') else 0)
        free = cd2_utils.format_size(info.freeSpace if hasattr(info, 'freeSpace') else 0)
        xbmcgui.Dialog().ok(
            _(32063, cloud_name),
            f"{_(32064, total)}\n{_(32065, used)}\n{_(32066, free)}"
        )
    except Exception as e:
        xbmcgui.Dialog().ok(_(32030), _(32038, e))


def _cloud_type_label(cloud_type_str):
    """Return a short label for cloud type."""
    labels = {
        "115open": "115",
        "aliyundriveopen": "Aliyun",
        "aliyundrive": "Aliyun",
        "baidupan": "Baidu",
        "onedrive": "OneDrive",
        "googledrive": "Google",
        "webdav": "WebDAV",
        "s3": "S3",
        "189": "189",
        "xunlei": "Xunlei",
        "xunleiopen": "Xunlei",
        "123pan": "123",
        "clouddrive": "CD2",
        "ftp": "FTP",
        "sftp": "SFTP",
        "smb": "SMB",
    }
    for k, v in labels.items():
        if k in cloud_type_str.lower().replace(" ", "").replace("_", ""):
            return v
    return cloud_type_str or "CLOUD"


def play_file(handle, path, name=""):
    """Resolve a file path to a playable URL for Kodi's native player."""
    from .cd2_client import get_client as _get_client
    from .cd2_constants import CD2_DEFAULT_HOST, CD2_DEFAULT_PORT
    import xbmcplugin as _xbmcplugin

    client = _get_client()
    try:
        info = client.get_download_url(path, timeout=10)
    except Exception as e:
        log(f"GetDownloadUrl failed: {e}", xbmc.LOGERROR)
        xbmcgui.Dialog().ok(_(32030), str(e))
        return

    # Prefer directUrl, fall back to downloadUrlPath combined with server host
    url = ""
    if hasattr(info, 'directUrl') and info.directUrl:
        url = info.directUrl
    elif hasattr(info, 'downloadUrlPath') and info.downloadUrlPath:
        url = f"http://{CD2_DEFAULT_HOST}:{CD2_DEFAULT_PORT}{info.downloadUrlPath}"

    if not url:
        xbmcgui.Dialog().ok(_(32030), "No playable URL returned")
        return

    li = xbmcgui.ListItem(name or path, path=url)
    _set_mimetype(li, name or path)
    _xbmcplugin.setResolvedUrl(handle, True, li)


def _set_mimetype(li, filename):
    """Set ListItem mime-type and info labels based on file extension."""
    ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else ""
    video_exts = {"mp4", "mkv", "avi", "mov", "wmv", "flv", "webm", "ts",
                  "m2ts", "mpg", "mpeg", "3gp", "ogv", "divx", "xvid",
                  "vob", "asf", "m4v", "rm", "rmvb", "iso", "bdmv",
                  "mts", "m2t", "evo", "mpls"}
    audio_exts = {"mp3", "flac", "aac", "ogg", "wav", "wma", "m4a",
                  "opus", "ape", "wv", "aiff", "dsf", "dff"}
    image_exts = {"jpg", "jpeg", "png", "gif", "bmp", "webp", "tiff",
                  "tif", "ico", "svg"}

    if ext in video_exts:
        li.setMimeType("video/" + ("mpeg" if ext in ("vob", "mpg", "mpeg") else "mp4"))
        li.setInfo("video", {"title": filename})
    elif ext in audio_exts:
        li.setMimeType("audio/" + ("mpeg" if ext == "mp3" else ext))
        li.setInfo("music", {"title": filename})
    elif ext in image_exts:
        li.setMimeType("image/" + ("jpeg" if ext == "jpg" else ext))
    else:
        li.setInfo("video", {"title": filename})
        li.setMimeType("application/octet-stream")


def _check_can_add():
    """Check if user can add more cloud APIs. Returns False and shows dialog if not."""
    client = get_client()
    try:
        result = client.can_add_more_cloud_apis(timeout=5)
    except Exception as e:
        xbmcgui.Dialog().ok(_(32030), _(32055, str(e)))
        return False
    if not result.success:
        xbmcgui.Dialog().ok(_(32030), result.errorMessage or _(32056))
        return False
    return True


def _build_url(mode, **params):
    """Build a plugin:// URL for routing."""
    parts = [f"mode={mode}"]
    for k, v in params.items():
        parts.append(f"{k}={urllib.parse.quote(str(v))}")
    return sys.argv[0] + "?" + "&".join(parts)


_DOWNLOAD_LOCK = "/tmp/cd2_download.lock"


def show_download_browser(handle, path=None):
    """Browse cloud drives and files for download."""
    client = get_client()
    xbmcplugin.setContent(handle, "files")

    if path is None or path == "/":
        # Root: list cloud drives
        try:
            resp = client.get_all_cloud_apis(timeout=10)
        except Exception as e:
            xbmcgui.Dialog().ok(_(32030), str(e))
            xbmcplugin.endOfDirectory(handle)
            return

        cloud_apis = []
        if hasattr(resp, 'apis') and resp.apis:
            cloud_apis = resp.apis

        if cloud_apis:
            for api in cloud_apis:
                name = api.name if hasattr(api, 'name') else str(api)
                api_path = api.path if hasattr(api, 'path') else "/"
                url = _build_url(MODE_FILE_BROWSE, path=api_path)
                li = xbmcgui.ListItem(f"[B]{name}[/B]")
                li.setProperty("IsPlayable", "false")
                xbmcplugin.addDirectoryItem(handle, url, li, isFolder=True)
        else:
            li = xbmcgui.ListItem(f"[{_(32177)}]")
            li.setProperty("IsPlayable", "false")
            xbmcplugin.addDirectoryItem(handle, _build_url(MODE_NOOP), li, isFolder=False)

        xbmcplugin.endOfDirectory(handle)
        return

    # Browse files at given path
    try:
        resp = client.get_sub_files(path, timeout=30)
    except Exception as e:
        xbmcgui.Dialog().ok(_(32030), str(e))
        xbmcplugin.endOfDirectory(handle)
        return

    files = []
    if hasattr(resp, '_list'):
        files = resp._list
    elif hasattr(resp, '__iter__'):
        files = list(resp)

    # Parent directory
    parent = "/".join(path.rstrip("/").split("/")[:-1]) or "/"
    if parent != path:
        url = _build_url(MODE_FILE_BROWSE, path=parent)
        li = xbmcgui.ListItem(f"[B].. {_(32178)}[/B]")
        li.setProperty("IsPlayable", "false")
        xbmcplugin.addDirectoryItem(handle, url, li, isFolder=True)

    if files:
        for f in files:
            file_name = (f.get("name") or f.get("fileName", "?")) if hasattr(f, 'get') else getattr(f, 'name', '?')
            full_path = f.get("fullPathName") or f.get("path", "") if hasattr(f, 'get') else getattr(f, 'fullPathName', '')
            is_dir = f.get("isDirectory") or f.get("isDirectory", False) if hasattr(f, 'get') else getattr(f, 'isDirectory', False)
            file_size = f.get("size") or f.get("size", 0) if hasattr(f, 'get') else getattr(f, 'size', 0)
            try:
                file_size = int(file_size)
            except (TypeError, ValueError):
                file_size = 0

            size_str = cd2_utils.format_size(file_size) if file_size else ""
            label = f"[B]{file_name}[/B]"
            if size_str:
                label += f"  {size_str}"

            if is_dir and full_path:
                url = _build_url(MODE_FILE_BROWSE, path=full_path)
                li = xbmcgui.ListItem(label)
                li.setProperty("IsPlayable", "false")
                xbmcplugin.addDirectoryItem(handle, url, li, isFolder=True)
            elif full_path and not is_dir:
                # File: context menu for download
                url = _build_url(MODE_DOWNLOAD_FILE, path=full_path, name=file_name)
                li = xbmcgui.ListItem(label)
                li.setProperty("IsPlayable", "false")
                xbmcplugin.addDirectoryItem(handle, url, li, isFolder=False)

    xbmcplugin.endOfDirectory(handle)



def start_download(file_path, file_name):
    """Download a file from CD2 to local storage with background progress."""
    import threading
    import time as _time

    if os.path.exists(_DOWNLOAD_LOCK):
        try:
            with open(_DOWNLOAD_LOCK) as f:
                old_pid = f.read().strip()
            # Check if PID is still running
            if old_pid and os.path.exists(f"/proc/{old_pid}"):
                xbmcgui.Dialog().ok("CloudDrive2", _(32181))
                return
        except Exception:
            pass

    # Create lock file with current PID
    try:
        with open(_DOWNLOAD_LOCK, "w") as f:
            f.write(str(os.getpid()))
    except Exception:
        pass

    log(f"start_download: path={file_path}, name={file_name}")
    client = get_client()

    # Get download URL
    try:
        info = client.get_download_url(file_path, timeout=10)
        log(f"get_download_url: directUrl={getattr(info,'directUrl','N/A')}, downloadUrlPath={getattr(info,'downloadUrlPath','N/A')[:80]}")
    except Exception as e:
        log(f"get_download_url FAILED: {e}", xbmc.LOGERROR)
        os.remove(_DOWNLOAD_LOCK) if os.path.exists(_DOWNLOAD_LOCK) else None
        xbmcgui.Dialog().ok(_(32030), _(32176, str(e)))
        return

    url = ""
    if hasattr(info, 'directUrl') and info.directUrl:
        url = info.directUrl
    elif hasattr(info, 'downloadUrlPath') and info.downloadUrlPath:
        raw = info.downloadUrlPath
        raw = raw.replace("{SCHEME}", "http")
        raw = raw.replace("{HOST}", f"{CD2_DEFAULT_HOST}:{CD2_DEFAULT_PORT}")
        raw = raw.replace("{PREVIEW}", "false")
        url = f"http://{CD2_DEFAULT_HOST}:{CD2_DEFAULT_PORT}{raw}"

    if not url:
        log("start_download: no URL returned", xbmc.LOGERROR)
        os.remove(_DOWNLOAD_LOCK) if os.path.exists(_DOWNLOAD_LOCK) else None
        xbmcgui.Dialog().ok(_(32030), "No download URL returned")
        return

    # Choose save directory
    dialog = xbmcgui.Dialog()
    default_dir = "/var/media/"
    save_dir = dialog.browse(0, _(32173), "files", "", False, False, default_dir)
    if not save_dir:
        os.remove(_DOWNLOAD_LOCK) if os.path.exists(_DOWNLOAD_LOCK) else None
        return

    # Confirm before download
    if not dialog.yesno("CloudDrive2", _(32173) + ": " + save_dir + "\n" + file_name):
        os.remove(_DOWNLOAD_LOCK) if os.path.exists(_DOWNLOAD_LOCK) else None
        return

    dest = os.path.join(save_dir, file_name)
    log(f"start_download: dest={dest}")

    # Handle existing file
    if os.path.exists(dest):
        choice = dialog.select(
            f"{_(32182, file_name)}",
            [_(32183), _(32184), _(32185)]
        )
        if choice < 0:
            os.remove(_DOWNLOAD_LOCK) if os.path.exists(_DOWNLOAD_LOCK) else None
            return
        elif choice == 0:  # Overwrite
            pass
        elif choice == 1:  # Rename: append (1), (2), etc.
            base, ext = os.path.splitext(file_name)
            n = 1
            while os.path.exists(os.path.join(save_dir, f"{base} ({n}){ext}")):
                n += 1
            dest = os.path.join(save_dir, f"{base} ({n}){ext}")
        else:  # Skip
            os.remove(_DOWNLOAD_LOCK) if os.path.exists(_DOWNLOAD_LOCK) else None
            return

    # Shared state for the download thread
    state = {"downloaded": 0, "total": 0, "speed": 0, "done": False, "error": None}

    def _download_worker():
        try:
            proxy_handler = urllib.request.ProxyHandler({})
            opener = urllib.request.build_opener(proxy_handler)
            resp = opener.open(url, timeout=600)
            state["total"] = int(resp.headers.get("Content-Length", 0))
            last_bytes = 0
            last_time = _time.time()
            with open(dest, "wb") as f:
                while True:
                    chunk = resp.read(4194304)  # 4MB blocks
                    if not chunk:
                        break
                    f.write(chunk)
                    state["downloaded"] += len(chunk)
                    now = _time.time()
                    if now - last_time >= 1.0:
                        state["speed"] = state["downloaded"] - last_bytes
                        last_bytes = state["downloaded"]
                        last_time = now
            state["done"] = True
        except Exception as e:
            state["error"] = str(e)

    t = threading.Thread(target=_download_worker, daemon=True)
    t.start()

    try:
        # Show non-blocking progress at top
        progress = xbmcgui.DialogProgressBG()
        progress.create("CloudDrive2", _(32174, file_name))

        while t.is_alive():
            total = state["total"]
            downloaded = state["downloaded"]
            speed = state["speed"]
            if total > 0:
                pct = min(int(downloaded * 100 / total), 99)
            else:
                pct = 0
            size_str = cd2_utils.format_size(downloaded)
            total_str = cd2_utils.format_size(total) if total > 0 else "?"
            speed_str = cd2_utils.format_size(speed) + "/s" if speed else ""
            msg = f"{size_str} / {total_str}"
            if speed_str:
                msg += f"  [{speed_str}]"
            progress.update(pct, _(32174, file_name), msg)
            xbmc.sleep(100)

        t.join()

        # Final update: show 100% with real total
        total = state["total"]
        downloaded = state["downloaded"]
        size_str = cd2_utils.format_size(downloaded)
        total_str = cd2_utils.format_size(total) if total > 0 else size_str
        progress.update(100, _(32174, file_name), f"{size_str} / {total_str}")
        xbmc.sleep(1500)
    finally:
        progress.close()
        os.remove(_DOWNLOAD_LOCK) if os.path.exists(_DOWNLOAD_LOCK) else None

    if state["error"]:
        log(f"start_download: HTTP download error: {state['error']}", xbmc.LOGERROR)
        try:
            os.remove(dest)
        except Exception:
            pass
        xbmcgui.Dialog().ok(_(32030), _(32176, state["error"]))
    else:
        xbmcgui.Dialog().ok("CloudDrive2", _(32175, dest))
