import os
import random
import sys
import tempfile
import time
from typing import Any
from typing import Callable
from typing import Dict
from typing import Iterator
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
import unittest

if TYPE_CHECKING:
    from libtorrent import settings_pack
else:
    settings_pack = Dict[str, Union[int, str, bool]]


def get_random_bytes(n: int) -> bytes:
    return bytes(random.getrandbits(8) for _ in range(n))


def get_isolated_settings() -> settings_pack:
    return {
        "enable_dht": False,
        "enable_lsd": False,
        "enable_natpmp": False,
        "enable_upnp": False,
        "listen_interfaces": "127.0.0.1:0",
        "dht_bootstrap_nodes": "",
    }


def loop_until_timeout(timeout: float, msg: str = "condition") -> Iterator[None]:
    deadline = time.monotonic() + timeout
    while time.monotonic() < deadline:
        yield
    raise AssertionError(f"{msg} timed out")


_FT = TypeVar("_FT", bound=Callable[..., Any])


def uses_non_unicode_paths() -> Callable[[_FT], _FT]:
    supported = True
    try:
        os.fsdecode(b"\xff")
    except ValueError:
        supported = False
    return unittest.skipIf(
        not supported, "platform doesn't support non-unicode filenames"
    )


# Note that this happens to work on supported platforms (posix and windows),
# but unpaired surrogates *mean different things* on these platforms. On windows
# they're passed through, on posix they're treated as an escape for non-utf-8
# bytes
def uses_surrogate_paths() -> Callable[[_FT], _FT]:
    supported = True
    try:
        os.fsencode("\udcff")
    except ValueError:
        supported = False
    return unittest.skipIf(
        not supported, "platform doesn't support unpaired unicode surrogate filenames"
    )


def unlink_all_files(path: str) -> None:
    for dirpath, _, filenames in os.walk(path):
        for filename in filenames:
            filepath = os.path.join(dirpath, filename)
            os.unlink(filepath)


# In test cases where libtorrent writes torrent data in a temporary directory,
# cleaning up the tempdir on Windows CI sometimes fails with a PermissionError
# having WinError 5 (Access Denied). I can't repro this WinError in any way;
# holding an open file handle results in a different WinError. Seems to be a
# race condition which only happens with very short-lived tests which write
# data. Work around by cleaning up the tempdir in a loop.


# TODO: why is this necessary?
def cleanup_with_windows_fix(
    tempdir: tempfile.TemporaryDirectory, *, timeout: float
) -> None:
    # Clean up just the files, so we don't have to bother with depth-first
    # traversal
    for _ in loop_until_timeout(timeout, msg="PermissionError clear"):
        try:
            unlink_all_files(tempdir.name)
        except PermissionError:
            if sys.platform == "win32":
                # current release of mypy doesn't know about winerror
                # if exc.winerror == 5:
                continue
            raise
        break
    # This removes directories in depth-first traversal.
    # It also marks the tempdir as explicitly cleaned so it doesn't trigger a
    # ResourceWarning.
    tempdir.cleanup()
