import errno
import platform
import sys
from time import sleep

import pytest
import requests

from qbittorrentapi import APINames
from qbittorrentapi._version_support import v
from qbittorrentapi.exceptions import Conflict409Error
from qbittorrentapi.exceptions import Forbidden403Error
from qbittorrentapi.exceptions import InvalidRequest400Error
from qbittorrentapi.exceptions import TorrentFileError
from qbittorrentapi.exceptions import TorrentFileNotFoundError
from qbittorrentapi.exceptions import TorrentFilePermissionError
from qbittorrentapi.torrents import TagList
from qbittorrentapi.torrents import TorrentCategoriesDictionary
from qbittorrentapi.torrents import TorrentFilesList
from qbittorrentapi.torrents import TorrentInfoList
from qbittorrentapi.torrents import TorrentLimitsDictionary
from qbittorrentapi.torrents import TorrentPieceInfoList
from qbittorrentapi.torrents import TorrentPropertiesDictionary
from qbittorrentapi.torrents import TorrentsAddPeersDictionary
from qbittorrentapi.torrents import TrackersList
from qbittorrentapi.torrents import WebSeedsList
from tests.conftest import ROOT_FOLDER_TORRENT_FILE
from tests.conftest import ROOT_FOLDER_TORRENT_HASH
from tests.conftest import TORRENT1_FILENAME
from tests.conftest import TORRENT1_HASH
from tests.conftest import TORRENT1_URL
from tests.conftest import TORRENT2_FILENAME
from tests.conftest import TORRENT2_HASH
from tests.conftest import TORRENT2_URL
from tests.conftest import new_torrent_standalone
from tests.utils import check
from tests.utils import mkpath
from tests.utils import retry


def disable_queueing(client):
    if client.app.preferences.queueing_enabled:
        client.app.set_preferences(dict(queueing_enabled=False))


def enable_queueing(client):
    if not client.app.preferences.queueing_enabled:
        client.app.set_preferences(dict(queueing_enabled=True))


@pytest.mark.skipif(sys.version_info < (3, 9), reason="removeprefix not in 3.8")
def test_methods(client):
    all_dotted_methods = {
        meth
        for namespace in [APINames.Torrents, "torrent_tags", "torrent_categories"]
        for meth in dir(getattr(client, namespace))
    }

    for meth in [meth for meth in dir(client) if meth.startswith("torrents_")]:
        assert meth.removeprefix("torrents_") in all_dotted_methods


# something was wrong with torrents_add on v2.0.0 (the initial version)
@pytest.mark.skipif_before_api_version("2.0.1")
@pytest.mark.parametrize(
    "add_func, delete_func",
    [("torrents_add", "torrents_delete"), ("torrents.add", "torrents.delete")],
)
def test_add_delete(client, add_func, delete_func, tmp_path):
    def download_file(url, filename=None, return_bytes=False):
        max_attempts = 3
        for attempt in range(max_attempts):
            try:
                with requests.get(url, timeout=30) as r:
                    r.raise_for_status()
                    if return_bytes:
                        return r.content
                    with open(mkpath(tmp_path, filename), "wb") as f:
                        for chunk in r.iter_content(chunk_size=1024):
                            f.write(chunk)
            except Exception if attempt < (max_attempts - 1) else ZeroDivisionError:
                pass  # throw away errors until we hit the retry limit
            else:
                return
        raise Exception("Download failed: %s" % url)

    def delete():
        client.func(delete_func)(delete_files=True, torrent_hashes=TORRENT1_HASH)
        client.func(delete_func)(delete_files=True, torrent_hashes=TORRENT2_HASH)
        check(
            lambda: [t.hash for t in client.torrents_info()],
            TORRENT2_HASH,
            reverse=True,
            negate=True,
        )

    def check_torrents_added(f):
        def inner(**kwargs):
            try:
                f(**kwargs)
                check(
                    lambda: [t.hash for t in client.torrents_info()],
                    TORRENT1_HASH,
                    reverse=True,
                )
                if kwargs.get("single", False) is False:
                    check(
                        lambda: [t.hash for t in client.torrents_info()],
                        TORRENT2_HASH,
                        reverse=True,
                    )
            finally:
                delete()

        return inner

    @retry()
    @check_torrents_added
    def add_by_filename(single):
        download_file(url=TORRENT1_URL, filename=TORRENT1_FILENAME)
        download_file(url=TORRENT2_URL, filename=TORRENT2_FILENAME)
        files = (
            mkpath(tmp_path, TORRENT1_FILENAME),
            mkpath(tmp_path, TORRENT2_FILENAME),
        )

        if single:
            assert client.func(add_func)(torrent_files=files[0]) == "Ok."
        else:
            assert client.func(add_func)(torrent_files=files) == "Ok."

    @retry()
    @check_torrents_added
    def add_by_filename_dict(single):
        download_file(url=TORRENT1_URL, filename=TORRENT1_FILENAME)
        download_file(url=TORRENT2_URL, filename=TORRENT2_FILENAME)

        if single:
            assert (
                client.func(add_func)(
                    torrent_files={
                        TORRENT1_FILENAME: mkpath(tmp_path, TORRENT1_FILENAME)
                    }
                )
                == "Ok."
            )
        else:
            files = {
                TORRENT1_FILENAME: mkpath(tmp_path, TORRENT1_FILENAME),
                TORRENT2_FILENAME: mkpath(tmp_path, TORRENT2_FILENAME),
            }
            assert client.func(add_func)(torrent_files=files) == "Ok."

    @retry()
    @check_torrents_added
    def add_by_filehandles(single):
        download_file(url=TORRENT1_URL, filename=TORRENT1_FILENAME)
        download_file(url=TORRENT2_URL, filename=TORRENT2_FILENAME)
        files = (
            open(mkpath(tmp_path, TORRENT1_FILENAME), "rb"),
            open(mkpath(tmp_path, TORRENT2_FILENAME), "rb"),
        )

        if single:
            assert client.func(add_func)(torrent_files=files[0]) == "Ok."
        else:
            assert client.func(add_func)(torrent_files=files) == "Ok."

        for file in files:
            file.close()

    @retry()
    @check_torrents_added
    def add_by_bytes(single):
        files = (
            download_file(TORRENT1_URL, return_bytes=True),
            download_file(TORRENT2_URL, return_bytes=True),
        )

        if single:
            assert client.func(add_func)(torrent_files=files[0]) == "Ok."
        else:
            assert client.func(add_func)(torrent_files=files) == "Ok."

    @retry()
    @check_torrents_added
    def add_by_url(single):
        urls = (TORRENT1_URL, TORRENT2_URL)

        if single:
            client.func(add_func)(urls=urls[0])
        else:
            client.func(add_func)(urls=urls)

    add_by_filename(single=False)
    add_by_filename(single=True)
    add_by_filename_dict(single=False)
    add_by_filename_dict(single=True)
    add_by_url(single=False)
    add_by_url(single=True)
    add_by_filehandles(single=False)
    add_by_filehandles(single=True)
    add_by_bytes(single=False)
    add_by_bytes(single=True)


def test_add_torrent_file_fail(client, monkeypatch):
    with pytest.raises(TorrentFileNotFoundError):
        client.torrents_add(torrent_files="/tmp/asdfasdfasdfasdf")

    with pytest.raises(TorrentFilePermissionError):
        client.torrents_add(torrent_files="/etc/shadow")

    if platform.python_implementation() == "CPython":
        with pytest.raises(TorrentFileError):

            def fake_open(*arg, **kwargs):
                raise OSError(errno.ENODEV)

            with monkeypatch.context() as m:
                m.setitem(__builtins__, "open", fake_open)
                client.torrents_add(torrent_files="/etc/hosts")


@pytest.mark.parametrize("keep_root_folder", [True, False, None])
@pytest.mark.parametrize(
    "content_layout", [None, "Original", "Subfolder", "NoSubfolder"]
)
def test_add_options(client, api_version, keep_root_folder, content_layout, tmp_path):
    if v(api_version) >= v("2.3.0"):
        client.torrents_create_tags("option-tag")
    new_torrent = new_torrent_standalone(
        client=client,
        torrent_files=ROOT_FOLDER_TORRENT_FILE,
        torrent_hash=ROOT_FOLDER_TORRENT_HASH,
        save_path=mkpath(tmp_path, "test_download"),
        category="test_category",
        is_paused=True,
        upload_limit=1024,
        download_limit=2048,
        is_sequential_download=True,
        is_first_last_piece_priority=True,
        is_root_folder=keep_root_folder,
        rename="this is a new name for the torrent",
        use_auto_torrent_management=False,
        tags="option-tag",
        content_layout=content_layout,
        ratio_limit=2,
        seeding_time_limit=120,
    )

    with new_torrent as torrent:
        check(lambda: torrent.info.category, "test_category")
        check(
            lambda: torrent.info.state,
            ("pausedDL", "checkingResumeData"),
            reverse=True,
            any=True,
        )
        check(lambda: mkpath(torrent.info.save_path), mkpath(tmp_path, "test_download"))
        check(lambda: torrent.info.up_limit, 1024)
        check(lambda: torrent.info.dl_limit, 2048)
        check(lambda: torrent.info.seq_dl, True)
        if v(api_version) >= v("2.0.1"):
            check(lambda: torrent.info.f_l_piece_prio, True)
        if content_layout is None:
            check(
                lambda: torrent.files[0]["name"].startswith("root_folder"),
                keep_root_folder in {True, None},
            )
        check(lambda: torrent.info.name, "this is a new name for the torrent")
        check(lambda: torrent.info.auto_tmm, False)
        if v(api_version) >= v("2.6.2"):
            check(lambda: torrent.info.tags, "option-tag")

        if v(api_version) >= v("2.7"):
            # after web api v2.7...root dir is driven by content_layout
            if content_layout is None:
                should_root_dir_exists = keep_root_folder in {None, True}
            else:
                should_root_dir_exists = content_layout in {"Original", "Subfolder"}
        else:
            # before web api v2.7...it is driven by is_root_folder
            if content_layout is not None and keep_root_folder is None:
                should_root_dir_exists = content_layout in {"Original", "Subfolder"}
            else:
                should_root_dir_exists = keep_root_folder in {None, True}
        check(
            lambda: any(f["name"].startswith("root_folder") for f in torrent.files),
            should_root_dir_exists,
        )

        if v(api_version) >= v("2.8.1"):
            check(lambda: torrent.info.ratio_limit, 2)
            check(lambda: torrent.info.seeding_time_limit, 120)


@pytest.mark.skipif_before_api_version("2.8.4")
@pytest.mark.parametrize("use_download_path", [None, True, False])
def test_torrents_add_download_path(client, use_download_path, tmp_path):
    client.torrents_delete(torrent_hashes=ROOT_FOLDER_TORRENT_HASH, delete_files=True)
    save_path = mkpath(tmp_path, "down_path_save_path_test")
    download_path = mkpath(tmp_path, "down_path_test")
    new_torrent = new_torrent_standalone(
        client=client,
        torrent_hash=ROOT_FOLDER_TORRENT_HASH,
        torrent_files=ROOT_FOLDER_TORRENT_FILE,
        download_path=download_path,
        use_download_path=use_download_path,
        test_download_limit=1024,
        save_path=save_path,
    )

    with new_torrent as torrent:
        if use_download_path is False:
            check(
                lambda: mkpath(torrent.info.download_path), download_path, negate=True
            )
        else:
            check(lambda: mkpath(torrent.info.download_path), download_path)


@pytest.mark.skipif_before_api_version("2.9.3")
@pytest.mark.parametrize("count_func", ["torrents_count", "torrents.count"])
def test_count(client, count_func):
    assert client.func(count_func)() == 1


@pytest.mark.skipif_after_api_version("2.9.3")
@pytest.mark.parametrize("count_func", ["torrents_count", "torrents.count"])
def test_count_not_implemented(client, count_func):
    with pytest.raises(NotImplementedError):
        assert client.func(count_func)() == 1


@pytest.mark.parametrize(
    "properties_func", ["torrents_properties", "torrents.properties"]
)
def test_properties(client, orig_torrent, properties_func):
    props = client.func(properties_func)(torrent_hash=orig_torrent.hash)
    assert isinstance(props, TorrentPropertiesDictionary)


@pytest.mark.parametrize("trackers_func", ["torrents_trackers", "torrents.trackers"])
def test_trackers(client, orig_torrent, trackers_func):
    trackers = client.func(trackers_func)(torrent_hash=orig_torrent.hash)
    assert isinstance(trackers, TrackersList)


@pytest.mark.parametrize("trackers_func", ["torrents_trackers", "torrents.trackers"])
def test_trackers_slice(client, orig_torrent, trackers_func):
    trackers = client.func(trackers_func)(torrent_hash=orig_torrent.hash)
    assert isinstance(trackers[1:2], TrackersList)


@pytest.mark.parametrize("webseeds_func", ["torrents_webseeds", "torrents.webseeds"])
def test_webseeds(client, orig_torrent, webseeds_func):
    web_seeds = client.func(webseeds_func)(torrent_hash=orig_torrent.hash)
    assert isinstance(web_seeds, WebSeedsList)


@pytest.mark.parametrize("webseeds_func", ["torrents_webseeds", "torrents.webseeds"])
def test_webseeds_slice(client, orig_torrent, webseeds_func):
    web_seeds = client.func(webseeds_func)(torrent_hash=orig_torrent.hash)
    assert isinstance(web_seeds[1:2], WebSeedsList)


@pytest.mark.parametrize("files_func", ["torrents_files", "torrents.files"])
def test_files(client, orig_torrent, files_func):
    files = client.func(files_func)(torrent_hash=orig_torrent.hash)
    assert isinstance(files, TorrentFilesList)
    assert "availability" in files[0]
    assert all(file["id"] == file["index"] for file in files)


@pytest.mark.parametrize("files_func", ["torrents_files", "torrents.files"])
def test_files_slice(client, orig_torrent, files_func):
    files = client.func(files_func)(torrent_hash=orig_torrent.hash)
    assert isinstance(files[1:2], TorrentFilesList)


@pytest.mark.parametrize(
    "piece_state_func",
    [
        "torrents_piece_states",
        "torrents_pieceStates",
        "torrents.piece_states",
        "torrents.pieceStates",
    ],
)
def test_piece_states(client, orig_torrent, piece_state_func):
    piece_states = client.func(piece_state_func)(torrent_hash=orig_torrent.hash)
    assert isinstance(piece_states, TorrentPieceInfoList)


@pytest.mark.parametrize(
    "piece_state_func",
    [
        "torrents_piece_states",
        "torrents_pieceStates",
        "torrents.piece_states",
        "torrents.pieceStates",
    ],
)
def test_piece_states_slice(client, orig_torrent, piece_state_func):
    piece_states = client.func(piece_state_func)(torrent_hash=orig_torrent.hash)
    assert isinstance(piece_states[1:2], TorrentPieceInfoList)


@pytest.mark.parametrize(
    "piece_hashes_func",
    [
        "torrents_piece_hashes",
        "torrents_pieceHashes",
        "torrents.piece_hashes",
        "torrents.pieceHashes",
    ],
)
def test_piece_hashes(client, orig_torrent, piece_hashes_func):
    piece_hashes = client.func(piece_hashes_func)(torrent_hash=orig_torrent.hash)
    assert isinstance(piece_hashes, TorrentPieceInfoList)


@pytest.mark.parametrize(
    "piece_hashes_func",
    [
        "torrents_piece_hashes",
        "torrents_pieceHashes",
        "torrents.piece_hashes",
        "torrents.pieceHashes",
    ],
)
def test_piece_hashes_slice(client, orig_torrent, piece_hashes_func):
    piece_hashes = client.func(piece_hashes_func)(torrent_hash=orig_torrent.hash)
    assert isinstance(piece_hashes[1:2], TorrentPieceInfoList)


@pytest.mark.parametrize("trackers", ["127.0.0.1", ["127.0.0.2", "127.0.0.3"]])
@pytest.mark.parametrize(
    "add_trackers_func",
    [
        "torrents_add_trackers",
        "torrents_addTrackers",
        "torrents.add_trackers",
        "torrents.addTrackers",
    ],
)
def test_add_trackers(client, trackers, new_torrent, add_trackers_func):
    client.func(add_trackers_func)(torrent_hash=new_torrent.hash, urls=trackers)
    check(lambda: (t.url for t in new_torrent.trackers), trackers, reverse=True)


@pytest.mark.skipif_before_api_version("2.2.0")
@pytest.mark.parametrize(
    "edit_trackers_func",
    [
        "torrents_edit_tracker",
        "torrents_editTracker",
        "torrents.edit_tracker",
        "torrents.editTracker",
    ],
)
def test_edit_tracker(client, orig_torrent, edit_trackers_func):
    orig_torrent.add_trackers("127.1.0.1")
    client.func(edit_trackers_func)(
        torrent_hash=orig_torrent.hash,
        original_url="127.1.0.1",
        new_url="127.1.0.2",
    )
    check(lambda: (t.url for t in orig_torrent.trackers), "127.1.0.2", reverse=True)
    client.torrents_remove_trackers(torrent_hash=orig_torrent.hash, urls="127.1.0.2")


@pytest.mark.skipif_after_api_version("2.2.0")
@pytest.mark.parametrize(
    "edit_trackers_func",
    [
        "torrents_edit_tracker",
        "torrents_editTracker",
        "torrents.edit_tracker",
        "torrents.editTracker",
    ],
)
def test_edit_tracker_not_implemented(client, orig_torrent, edit_trackers_func):
    with pytest.raises(NotImplementedError):
        client.func(edit_trackers_func)()


@pytest.mark.skipif_before_api_version("2.2.0")
@pytest.mark.parametrize(
    "trackers",
    [
        ["127.2.0.1"],
        ["127.2.0.2", "127.2.0.3"],
    ],
)
@pytest.mark.parametrize(
    "remove_trackers_func",
    [
        "torrents_remove_trackers",
        "torrents_removeTrackers",
        "torrents.remove_trackers",
        "torrents.removeTrackers",
    ],
)
def test_remove_trackers(client, trackers, orig_torrent, remove_trackers_func):
    orig_torrent.add_trackers(trackers)
    client.func(remove_trackers_func)(torrent_hash=orig_torrent.hash, urls=trackers)
    check(
        lambda: (t.url for t in orig_torrent.trackers),
        trackers,
        reverse=True,
        negate=True,
    )


@pytest.mark.skipif_after_api_version("2.2.0")
@pytest.mark.parametrize(
    "remove_trackers_func",
    [
        "torrents_remove_trackers",
        "torrents_removeTrackers",
        "torrents.remove_trackers",
        "torrents.removeTrackers",
    ],
)
def test_remove_trackers_not_implemented(client, orig_torrent, remove_trackers_func):
    with pytest.raises(NotImplementedError):
        client.func(remove_trackers_func)()


@pytest.mark.parametrize(
    "file_prio_func",
    [
        "torrents_file_priority",
        "torrents_filePrio",
        "torrents.file_priority",
        "torrents.filePrio",
    ],
)
def test_file_priority(client, orig_torrent, file_prio_func):
    client.func(file_prio_func)(torrent_hash=orig_torrent.hash, file_ids=0, priority=6)
    check(lambda: orig_torrent.files[0].priority, 6)
    client.func(file_prio_func)(torrent_hash=orig_torrent.hash, file_ids=0, priority=7)
    check(lambda: orig_torrent.files[0].priority, 7)


@pytest.mark.parametrize("new_name", ["new name 2", "new_name_2"])
@pytest.mark.parametrize("rename_func", ["torrents_rename", "torrents.rename"])
def test_rename(client, new_torrent, new_name, rename_func):
    client.func(rename_func)(torrent_hash=new_torrent.hash, new_torrent_name=new_name)
    check(lambda: new_torrent.info.name.replace("+", " "), new_name)


@pytest.mark.skipif_before_api_version("2.4.0")
@pytest.mark.parametrize("new_name", ["new name file 2", "new_name_file_2"])
@pytest.mark.parametrize(
    "rename_file_func",
    [
        "torrents_rename_file",
        "torrents_renameFile",
        "torrents.rename_file",
        "torrents.renameFile",
    ],
)
def test_rename_file(
    client,
    new_torrent,
    new_name,
    rename_file_func,
):
    # pre-v4.3.3 rename_file signature
    client.func(rename_file_func)(
        torrent_hash=new_torrent.hash, file_id=0, new_file_name=new_name
    )
    check(lambda: new_torrent.files[0].name.replace("+", " "), new_name)
    # test invalid file ID is rejected
    with pytest.raises(Conflict409Error):
        client.func(rename_file_func)(
            torrent_hash=new_torrent.hash, file_id=10, new_file_name=new_name
        )
    # post-v4.3.3 rename_file signature
    new_new_name = new_name + "NEW"
    client.func(rename_file_func)(
        torrent_hash=new_torrent.hash,
        old_path=new_torrent.files[0].name,
        new_path=new_new_name,
    )
    check(lambda: new_torrent.files[0].name.replace("+", " "), new_new_name)
    # test invalid old_path is rejected
    with pytest.raises(Conflict409Error):
        client.func(rename_file_func)(
            torrent_hash=new_torrent.hash, old_path="asdf", new_path="xcvb"
        )


@pytest.mark.skipif_after_api_version("2.4.0")
@pytest.mark.parametrize(
    "rename_file_func",
    [
        "torrents_rename_file",
        "torrents_renameFile",
        "torrents.rename_file",
        "torrents.renameFile",
    ],
)
def test_rename_file_not_implemented(
    client,
    new_torrent,
    rename_file_func,
):
    with pytest.raises(NotImplementedError):
        client.func(rename_file_func)()


@pytest.mark.skipif_before_api_version("2.7")
@pytest.mark.parametrize("new_name", ["asdf zxcv", "asdf_zxcv"])
@pytest.mark.parametrize(
    "rename_folder_func",
    [
        "torrents_rename_folder",
        "torrents_renameFolder",
        "torrents.rename_folder",
        "torrents.renameFolder",
    ],
)
def test_rename_folder(client, app_version, new_torrent, new_name, rename_folder_func):
    if v(app_version) >= v("v4.3.3"):
        # move the file in to a new folder
        orig_file_path = new_torrent.files[0].name
        new_folder = "qwer"
        client.torrents_rename_file(
            torrent_hash=new_torrent.hash,
            old_path=orig_file_path,
            new_path=new_folder + "/" + orig_file_path,
        )

        # wait for the folder to be renamed
        check(
            lambda: [f.name.split("/")[0] for f in new_torrent.files],
            new_folder,
            reverse=True,
        )

        # test rename that new folder
        client.func(rename_folder_func)(
            torrent_hash=new_torrent.hash,
            old_path=new_folder,
            new_path=new_name,
        )
        check(
            lambda: new_torrent.files[0].name.replace("+", " "),
            new_name + "/" + orig_file_path,
        )
    elif v(app_version) >= v("v4.3.2"):
        with pytest.raises(NotImplementedError):
            client.func(rename_folder_func)()


@pytest.mark.skipif_after_api_version("2.7")
@pytest.mark.parametrize(
    "rename_folder_func",
    [
        "torrents_rename_folder",
        "torrents_renameFolder",
        "torrents.rename_folder",
        "torrents.renameFolder",
    ],
)
def test_rename_folder_not_implemented(client, rename_folder_func):
    with pytest.raises(NotImplementedError):
        client.func(rename_folder_func)()


@pytest.mark.skipif_before_api_version("2.8.14")
@pytest.mark.parametrize("export_func", ["torrents_export", "torrents.export"])
def test_export(client, orig_torrent, export_func):
    assert isinstance(client.func(export_func)(torrent_hash=orig_torrent.hash), bytes)


@pytest.mark.skipif_after_api_version("2.8.14")
@pytest.mark.parametrize("export_func", ["torrents_export", "torrents.export"])
def test_export_not_implemented(client, export_func):
    with pytest.raises(NotImplementedError):
        client.func(export_func)()


@pytest.mark.parametrize("info_func", ["torrents_info", "torrents.info"])
def test_torrents_info(client, info_func):
    assert isinstance(client.func(info_func)(), TorrentInfoList)
    if "." in info_func:
        assert isinstance(client.func(info_func).all(), TorrentInfoList)
        assert isinstance(client.func(info_func).downloading(), TorrentInfoList)
        assert isinstance(client.func(info_func).seeding(), TorrentInfoList)
        assert isinstance(client.func(info_func).completed(), TorrentInfoList)
        assert isinstance(client.func(info_func).paused(), TorrentInfoList)
        assert isinstance(client.func(info_func).active(), TorrentInfoList)
        assert isinstance(client.func(info_func).inactive(), TorrentInfoList)
        assert isinstance(client.func(info_func).resumed(), TorrentInfoList)
        assert isinstance(client.func(info_func).stalled(), TorrentInfoList)
        assert isinstance(client.func(info_func).stalled_uploading(), TorrentInfoList)
        assert isinstance(client.func(info_func).stalled_downloading(), TorrentInfoList)
        assert isinstance(client.func(info_func).checking(), TorrentInfoList)
        assert isinstance(client.func(info_func).moving(), TorrentInfoList)
        assert isinstance(client.func(info_func).errored(), TorrentInfoList)


def test_torrents_info_slice(client):
    assert isinstance(client.torrents_info()[1:2], TorrentInfoList)


@pytest.mark.skipif_before_api_version("2.8.3")
@pytest.mark.parametrize("info_func", ["torrents_info", "torrents.info"])
def test_torrents_info_tag(client, new_torrent, info_func):
    tag_name = "tag_filter_name"
    client.torrents_add_tags(tags=tag_name, torrent_hashes=new_torrent.hash)
    torrents = client.func(info_func)(torrent_hashes=new_torrent.hash, tag=tag_name)
    assert new_torrent.hash in {t.hash for t in torrents}


# test fails on 4.1.0 release
@pytest.mark.skipif_before_api_version("2.0.1")
@pytest.mark.parametrize(
    "pause_func, resume_func",
    [("torrents_pause", "torrents_resume"), ("torrents.pause", "torrents.resume")],
)
def test_pause_resume(client, new_torrent, pause_func, resume_func):
    client.func(pause_func)(torrent_hashes=new_torrent.hash)
    check(
        lambda: client.torrents_info(torrent_hashes=new_torrent.hash)[
            0
        ].state_enum.is_paused,
        True,
    )

    client.func(resume_func)(torrent_hashes=new_torrent.hash)
    check(
        lambda: client.torrents_info(torrent_hashes=new_torrent.hash)[
            0
        ].state_enum.is_paused,
        False,
    )


def test_action_for_all_torrents(client):
    client.torrents.resume.all()
    for torrent in client.torrents.info():
        check(
            lambda: client.torrents_info(torrent_hashes=torrent.hash)[0].state,
            {"pausedDL"},
            negate=True,
        )
    client.torrents.pause.all()
    for torrent in client.torrents.info():
        check(
            lambda: client.torrents_info(torrent_hashes=torrent.hash)[0].state,
            {"stalledDL", "pausedDL"},
            any=True,
        )


@pytest.mark.parametrize("recheck_func", ["torrents_recheck", "torrents.recheck"])
def test_recheck(client, orig_torrent, recheck_func):
    client.func(recheck_func)(torrent_hashes=orig_torrent.hash)


@pytest.mark.skipif_before_api_version("2.0.2")
@pytest.mark.parametrize(
    "reannounce_func", ["torrents_reannounce", "torrents.reannounce"]
)
def test_reannounce(client, orig_torrent, reannounce_func):
    client.func(reannounce_func)(torrent_hashes=orig_torrent.hash)


@pytest.mark.skipif_after_api_version("2.0.2")
@pytest.mark.parametrize(
    "reannounce_func", ["torrents_reannounce", "torrents.reannounce"]
)
def test_reannounce_not_implemented(client, reannounce_func):
    with pytest.raises(NotImplementedError):
        client.func(reannounce_func)()


# priority doesn't seem to work on v4.1.0
@pytest.mark.skipif_before_api_version("2.0.1")
@pytest.mark.parametrize(
    "inc_prio_func, dec_prio_func, top_prio_func, bottom_prio_func",
    [
        (
            "torrents_increase_priority",
            "torrents_decrease_priority",
            "torrents_top_priority",
            "torrents_bottom_priority",
        ),
        (
            "torrents_increasePrio",
            "torrents_decreasePrio",
            "torrents_topPrio",
            "torrents_bottomPrio",
        ),
    ],
)
def test_priority(
    client, new_torrent, inc_prio_func, dec_prio_func, top_prio_func, bottom_prio_func
):
    disable_queueing(client)

    with pytest.raises(Conflict409Error):
        client.func(inc_prio_func)(torrent_hashes=new_torrent.hash)
    with pytest.raises(Conflict409Error):
        client.func(dec_prio_func)(torrent_hashes=new_torrent.hash)
    with pytest.raises(Conflict409Error):
        client.func(top_prio_func)(torrent_hashes=new_torrent.hash)
    with pytest.raises(Conflict409Error):
        client.func(bottom_prio_func)(torrent_hashes=new_torrent.hash)

    enable_queueing(client)

    @retry()
    def test1(current_priority):
        client.func(inc_prio_func)(torrent_hashes=new_torrent.hash)
        check(lambda: new_torrent.info.priority < current_priority, True)

    @retry()
    def test2(current_priority):
        client.func(dec_prio_func)(torrent_hashes=new_torrent.hash)
        check(lambda: new_torrent.info.priority > current_priority, True)

    @retry()
    def test3(current_priority):
        client.func(top_prio_func)(torrent_hashes=new_torrent.hash)
        check(lambda: new_torrent.info.priority < current_priority, True)

    @retry()
    def test4(current_priority):
        client.func(bottom_prio_func)(torrent_hashes=new_torrent.hash)
        check(lambda: new_torrent.info.priority > current_priority, True)

    test1(current_priority=new_torrent.info.priority)
    test2(current_priority=new_torrent.info.priority)
    test3(current_priority=new_torrent.info.priority)
    test4(current_priority=new_torrent.info.priority)


@pytest.mark.parametrize(
    "set_down_limit_func, down_limit_func",
    [
        ("torrents_set_download_limit", "torrents_download_limit"),
        ("torrents_setDownloadLimit", "torrents_downloadLimit"),
        ("torrents.set_download_limit", "torrents.download_limit"),
        ("torrents.setDownloadLimit", "torrents.downloadLimit"),
    ],
)
def test_download_limit(client, orig_torrent, set_down_limit_func, down_limit_func):
    orig_download_limit = client.func(down_limit_func)(
        torrent_hashes=orig_torrent.hash
    )[orig_torrent.hash]

    client.func(set_down_limit_func)(torrent_hashes=orig_torrent.hash, limit=100)
    assert isinstance(
        client.func(down_limit_func)(torrent_hashes=orig_torrent.hash),
        TorrentLimitsDictionary,
    )
    check(
        lambda: client.func(down_limit_func)(torrent_hashes=orig_torrent.hash)[
            orig_torrent.hash
        ],
        100,
    )

    # reset download limit
    client.func(set_down_limit_func)(
        torrent_hashes=orig_torrent.hash, limit=orig_download_limit
    )
    check(
        lambda: client.func(down_limit_func)(torrent_hashes=orig_torrent.hash)[
            orig_torrent.hash
        ],
        orig_download_limit,
    )


@pytest.mark.parametrize(
    "set_up_limit_func, up_limit_func",
    [
        ("torrents_set_upload_limit", "torrents_upload_limit"),
        ("torrents_setUploadLimit", "torrents_uploadLimit"),
        ("torrents.set_upload_limit", "torrents.upload_limit"),
        ("torrents.setUploadLimit", "torrents.uploadLimit"),
    ],
)
def test_upload_limit(client, orig_torrent, set_up_limit_func, up_limit_func):
    orig_upload_limit = client.func(up_limit_func)(torrent_hashes=orig_torrent.hash)[
        orig_torrent.hash
    ]

    client.func(set_up_limit_func)(torrent_hashes=orig_torrent.hash, limit=100)
    assert isinstance(
        client.func(up_limit_func)(torrent_hashes=orig_torrent.hash),
        TorrentLimitsDictionary,
    )
    check(
        lambda: client.func(up_limit_func)(torrent_hashes=orig_torrent.hash)[
            orig_torrent.hash
        ],
        100,
    )

    # reset upload limit
    client.func(set_up_limit_func)(
        torrent_hashes=orig_torrent.hash, limit=orig_upload_limit
    )
    check(
        lambda: client.func(up_limit_func)(torrent_hashes=orig_torrent.hash)[
            orig_torrent.hash
        ],
        orig_upload_limit,
    )


@pytest.mark.skipif_before_api_version("2.0.1")
@pytest.mark.parametrize(
    "set_share_limits_func",
    [
        "torrents_set_share_limits",
        "torrents_setShareLimits",
        "torrents.set_share_limits",
        "torrents.setShareLimits",
    ],
)
def test_set_share_limits(client, orig_torrent, set_share_limits_func):
    client.func(set_share_limits_func)(
        ratio_limit=2,
        seeding_time_limit=5,
        inactive_seeding_time_limit=8,
        torrent_hashes=orig_torrent.hash,
    )
    check(lambda: orig_torrent.info.max_ratio, 2)
    check(lambda: orig_torrent.info.max_seeding_time, 5)
    if "max_inactive_seeding_time" in orig_torrent.info:
        check(lambda: orig_torrent.info.max_inactive_seeding_time, 8)

    client.func(set_share_limits_func)(
        ratio_limit=3,
        seeding_time_limit=6,
        inactive_seeding_time_limit=9,
        torrent_hashes=orig_torrent.hash,
    )
    check(lambda: orig_torrent.info.max_ratio, 3)
    check(lambda: orig_torrent.info.max_seeding_time, 6)
    if "max_inactive_seeding_time" in orig_torrent.info:
        check(lambda: orig_torrent.info.max_inactive_seeding_time, 9)


@pytest.mark.skipif_after_api_version("2.0.1")
@pytest.mark.parametrize(
    "set_share_limits_func",
    [
        "torrents_set_share_limits",
        "torrents_setShareLimits",
        "torrents.set_share_limits",
        "torrents.setShareLimits",
    ],
)
def test_set_share_limits_not_implemented(client, set_share_limits_func):
    with pytest.raises(NotImplementedError):
        client.func(set_share_limits_func)()


@pytest.mark.skipif_before_api_version("2.0.2")
@pytest.mark.parametrize(
    "set_loc_func",
    [
        "torrents_set_location",
        "torrents_setLocation",
        "torrents.set_location",
        "torrents.setLocation",
    ],
)
def test_set_location(client, app_version, new_torrent, set_loc_func, tmp_path):
    # stopped erroring when the write check was removed for API
    if v(app_version) < v("v4.5.2"):
        with pytest.raises(Forbidden403Error):
            client.func(set_loc_func)(location="/etc/", torrent_hashes=new_torrent.hash)

    sleep(0.5)
    loc = mkpath(tmp_path, "1")
    client.func(set_loc_func)(location=loc, torrent_hashes=new_torrent.hash)
    # qBittorrent may return trailing separators depending on version....
    check(lambda: mkpath(new_torrent.info.save_path), loc, any=True)


@pytest.mark.skipif_before_api_version("2.8.4")
@pytest.mark.parametrize(
    "set_save_path_func",
    [
        "torrents_set_save_path",
        "torrents_setSavePath",
        "torrents.set_save_path",
        "torrents.setSavePath",
    ],
)
def test_set_save_path(client, new_torrent, set_save_path_func, tmp_path):
    with pytest.raises(Forbidden403Error):
        client.func(set_save_path_func)(
            save_path="/etc/", torrent_hashes=new_torrent.hash
        )
    with pytest.raises(Conflict409Error):
        client.func(set_save_path_func)(
            save_path="/etc/asdf", torrent_hashes=new_torrent.hash
        )

    loc = mkpath(tmp_path, "savepath1")
    client.func(set_save_path_func)(save_path=loc, torrent_hashes=new_torrent.hash)
    # qBittorrent may return trailing separators depending on version....
    check(lambda: mkpath(new_torrent.info.save_path), loc, any=True)


@pytest.mark.skipif_after_api_version("2.8.4")
@pytest.mark.parametrize(
    "set_save_path_func",
    [
        "torrents_set_save_path",
        "torrents_setSavePath",
        "torrents.set_save_path",
        "torrents.setSavePath",
    ],
)
def test_set_save_path_not_implemented(client, set_save_path_func):
    with pytest.raises(NotImplementedError):
        client.func(set_save_path_func)()


@pytest.mark.skipif_before_api_version("2.8.4")
@pytest.mark.parametrize(
    "set_down_path_func",
    [
        "torrents_set_download_path",
        "torrents_setDownloadPath",
        "torrents.set_download_path",
        "torrents.setDownloadPath",
    ],
)
def test_set_download_path(client, new_torrent, set_down_path_func, tmp_path):
    with pytest.raises(Forbidden403Error):
        client.func(set_down_path_func)(
            download_path="/etc/", torrent_hashes=new_torrent.hash
        )
    with pytest.raises(Conflict409Error):
        client.func(set_down_path_func)(
            download_path="/etc/asdf", torrent_hashes=new_torrent.hash
        )

    loc = mkpath(tmp_path, "savepath1")
    client.func(set_down_path_func)(download_path=loc, torrent_hashes=new_torrent.hash)
    # qBittorrent may return trailing separators depending on version....
    check(lambda: mkpath(new_torrent.info.download_path), loc, any=True)


@pytest.mark.skipif_after_api_version("2.8.4")
@pytest.mark.parametrize(
    "set_down_path_func",
    [
        "torrents_set_download_path",
        "torrents_setDownloadPath",
        "torrents.set_download_path",
        "torrents.setDownloadPath",
    ],
)
def test_set_download_path_not_implemented(client, new_torrent, set_down_path_func):
    with pytest.raises(NotImplementedError):
        client.func(set_down_path_func)()


@pytest.mark.parametrize(
    "set_cat_func",
    [
        "torrents_set_category",
        "torrents_setCategory",
        "torrents.set_category",
        "torrents.setCategory",
    ],
)
@pytest.mark.parametrize("name", ["awesome cat", "awesome_cat"])
def test_set_category(client, orig_torrent, set_cat_func, name):
    with pytest.raises(Conflict409Error):
        client.func(set_cat_func)(
            category="/!@#$%^&*(", torrent_hashes=orig_torrent.hash
        )

    client.torrents_create_category(name=name)
    try:
        client.func(set_cat_func)(category=name, torrent_hashes=orig_torrent.hash)
        check(lambda: orig_torrent.info.category.replace("+", " "), name)
    finally:
        client.torrents_remove_categories(categories=name)


@pytest.mark.parametrize(
    "set_auto_mgmt_func",
    [
        "torrents_set_auto_management",
        "torrents_setAutoManagement",
        "torrents.set_auto_management",
        "torrents.setAutoManagement",
    ],
)
def test_torrents_set_auto_management(client, orig_torrent, set_auto_mgmt_func):
    current_setting = orig_torrent.info.auto_tmm
    client.func(set_auto_mgmt_func)(
        enable=(not current_setting), torrent_hashes=orig_torrent.hash
    )
    check(lambda: orig_torrent.info.auto_tmm, (not current_setting))
    client.func(set_auto_mgmt_func)(
        enable=False, torrent_hashes=orig_torrent.hash
    )  # leave on False


@pytest.mark.parametrize(
    "toggle_seq_down_func",
    [
        "torrents_toggle_sequential_download",
        "torrents_toggleSequentialDownload",
        "torrents.toggle_sequential_download",
        "torrents.toggleSequentialDownload",
    ],
)
def test_toggle_sequential_download(client, orig_torrent, toggle_seq_down_func):
    current_setting = orig_torrent.info.seq_dl
    client.func(toggle_seq_down_func)(torrent_hashes=orig_torrent.hash)
    check(lambda: orig_torrent.info.seq_dl, not current_setting)


@pytest.mark.skipif_before_api_version("2.0.1")
@pytest.mark.parametrize(
    "toggle_piece_prio_func",
    [
        "torrents_toggle_first_last_piece_priority",
        "torrents_toggleFirstLastPiecePrio",
        "torrents.toggle_first_last_piece_priority",
        "torrents.toggleFirstLastPiecePrio",
    ],
)
def test_toggle_first_last_piece_priority(client, new_torrent, toggle_piece_prio_func):
    current_setting = new_torrent.info.f_l_piece_prio
    client.func(toggle_piece_prio_func)(torrent_hashes=new_torrent.hash)
    check(lambda: new_torrent.info.f_l_piece_prio, not current_setting)


@pytest.mark.parametrize(
    "set_force_start_func",
    [
        "torrents_set_force_start",
        "torrents_setForceStart",
        "torrents.set_force_start",
        "torrents.setForceStart",
    ],
)
def test_set_force_start(client, orig_torrent, set_force_start_func):
    current_setting = orig_torrent.info.force_start
    client.func(set_force_start_func)(
        enable=(not current_setting), torrent_hashes=orig_torrent.hash
    )
    check(lambda: orig_torrent.info.force_start, not current_setting)


@pytest.mark.parametrize(
    "set_super_seeding_func",
    [
        "torrents_set_super_seeding",
        "torrents_setSuperSeeding",
        "torrents.set_super_seeding",
        "torrents.setSuperSeeding",
    ],
)
def test_set_super_seeding(client, orig_torrent, set_super_seeding_func):
    client.func(set_super_seeding_func)(enable=False, torrent_hashes=orig_torrent.hash)
    check(lambda: orig_torrent.info.force_start, False)


@pytest.mark.skipif_before_api_version("2.3.0")
@pytest.mark.parametrize(
    "add_peers_func",
    [
        "torrents_add_peers",
        "torrents_addPeers",
        "torrents.add_peers",
        "torrents.addPeers",
    ],
)
@pytest.mark.parametrize(
    "peers", ["127.0.0.1:5000", ["127.0.0.1:5000", "127.0.0.2:5000"], "127.0.0.1"]
)
def test_torrents_add_peers(client, orig_torrent, add_peers_func, peers):
    if all(":" not in p for p in peers):
        with pytest.raises(InvalidRequest400Error):
            client.func(add_peers_func)(peers=peers, torrent_hashes=orig_torrent.hash)
    else:
        p = client.func(add_peers_func)(peers=peers, torrent_hashes=orig_torrent.hash)
        assert isinstance(p, TorrentsAddPeersDictionary)


@pytest.mark.skipif_after_api_version("2.3.0")
@pytest.mark.parametrize(
    "add_peers_func",
    [
        "torrents_add_peers",
        "torrents_addPeers",
        "torrents.add_peers",
        "torrents.addPeers",
    ],
)
def test_torrents_add_peers_not_implemented(client, add_peers_func):
    with pytest.raises(NotImplementedError):
        client.func(add_peers_func)()


def _categories_save_path_key(api_version):
    """With qBittorrent 4.4.0 (Web API 2.8.4), the key in the category definition
    returned changed from savePath to save_path...."""
    if v(api_version) == v("2.8.4"):
        return "save_path"
    return "savePath"


@pytest.mark.skipif_before_api_version("2.1.1")
def test_categories1(client):
    assert isinstance(client.torrents_categories(), TorrentCategoriesDictionary)


@pytest.mark.skipif_after_api_version("2.1.1")
def test_categories1_not_implemented(client, api_version):
    with pytest.raises(NotImplementedError):
        client.torrents_categories()


@pytest.mark.skipif_before_api_version("2.1.1")
def test_categories2(client, api_version, tmp_path):
    save_path_key = _categories_save_path_key(api_version)
    name = "new_category"
    client.torrent_categories.categories = {"name": name, save_path_key: tmp_path}
    assert name in client.torrent_categories.categories
    client.torrent_categories.categories = {
        "name": name,
        save_path_key: mkpath(tmp_path, "new"),
    }
    assert mkpath(client.torrent_categories.categories[name][save_path_key]) == mkpath(
        tmp_path, "new"
    )
    client.torrents_remove_categories(categories=name)


@pytest.mark.skipif_after_api_version("2.1.1")
def test_categories2_not_implemented(client):
    with pytest.raises(NotImplementedError):
        _ = client.torrent_categories.categories


@pytest.mark.parametrize(
    "create_cat_func",
    [
        "torrents_create_category",
        "torrents_createCategory",
        "torrent_categories.create_category",
        "torrent_categories.createCategory",
    ],
)
@pytest.mark.parametrize("filepath", [None, "", "/tmp/"])
@pytest.mark.parametrize("name", ["name", "name 1"])
@pytest.mark.parametrize("enable_download_path", [None, True, False])
def test_create_categories(
    client,
    api_version,
    orig_torrent,
    create_cat_func,
    filepath,
    name,
    enable_download_path,
):
    save_path = download_path = filepath
    if filepath:
        save_path += "save"
        download_path += "download"

    try:
        client.func(create_cat_func)(
            name=name,
            save_path=save_path,
            download_path=download_path,
            enable_download_path=enable_download_path,
        )
        client.torrents_set_category(torrent_hashes=orig_torrent.hash, category=name)
        check(lambda: orig_torrent.info.category.replace("+", " "), name)
        if v(api_version) >= v("2.2"):
            check(
                lambda: [n.replace("+", " ") for n in client.torrents_categories()],
                name,
                reverse=True,
            )
            save_path_key = _categories_save_path_key(api_version)
            check(
                lambda: [
                    mkpath(cat[save_path_key])
                    for cat in client.torrents_categories().values()
                ],
                mkpath(save_path) or "",
                reverse=True,
            )
        if v(api_version) >= v("2.8.4") and enable_download_path is not False:
            check(
                lambda: [
                    mkpath(cat.get("download_path", ""))
                    for cat in client.torrents_categories().values()
                ],
                mkpath(download_path) or "",
                reverse=True,
            )
    finally:
        client.torrents_remove_categories(categories=name)


@pytest.mark.skipif_before_api_version("2.1.0")
@pytest.mark.parametrize(
    "edit_cat_func",
    [
        "torrents_edit_category",
        "torrents_editCategory",
        "torrent_categories.edit_category",
        "torrent_categories.editCategory",
    ],
)
@pytest.mark.parametrize("filepath", ["", "/tmp/"])
@pytest.mark.parametrize("name", ["editcategory"])
@pytest.mark.parametrize("enable_download_path", [None, True, False])
def test_edit_category(
    client, api_version, edit_cat_func, filepath, name, enable_download_path
):
    try:
        client.torrents_create_category(
            name=name, save_path="/tmp/savetmp", download_path="/tmp/savetmp"
        )
        save_path = mkpath(filepath + "save/")
        download_path = mkpath(filepath + "down/")
        client.func(edit_cat_func)(
            name=name,
            save_path=save_path,
            download_path=download_path,
            enable_download_path=enable_download_path,
        )
        check(
            lambda: [n.replace("+", " ") for n in client.torrents_categories()],
            name,
            reverse=True,
        )
        save_path_key = _categories_save_path_key(api_version)
        check(
            lambda: (
                mkpath(cat[save_path_key])
                for cat in client.torrents_categories().values()
            ),
            mkpath(save_path) or "",
            reverse=True,
        )
        if v(api_version) >= v("2.8.4") and enable_download_path is not False:
            check(
                lambda: [
                    mkpath(cat.get("download_path", ""))
                    for cat in client.torrents_categories().values()
                ],
                mkpath(download_path) or "",
                reverse=True,
            )
    finally:
        client.torrents_remove_categories(categories=name)


@pytest.mark.skipif_after_api_version("2.1.0")
@pytest.mark.parametrize(
    "edit_cat_func",
    (
        "torrents_edit_category",
        "torrents_editCategory",
        "torrent_categories.edit_category",
        "torrent_categories.editCategory",
    ),
)
def test_edit_category_not_implemented(client, edit_cat_func):
    with pytest.raises(NotImplementedError):
        client.func(edit_cat_func)()


@pytest.mark.parametrize(
    "remove_cat_func",
    [
        "torrents_remove_categories",
        "torrents_removeCategories",
        "torrent_categories.remove_categories",
        "torrent_categories.removeCategories",
    ],
)
@pytest.mark.parametrize("categories", [["category1"], ["category1", "category 2"]])
def test_remove_category(
    client, api_version, orig_torrent, remove_cat_func, categories
):
    for name in categories:
        client.torrents_create_category(name=name)
    orig_torrent.set_category(category=categories[0])
    client.func(remove_cat_func)(categories=categories)
    if v(api_version) >= v("2.2"):
        check(
            lambda: [n.replace("+", " ") for n in client.torrents_categories()],
            categories,
            reverse=True,
            negate=True,
        )
    check(lambda: orig_torrent.info.category, categories[0], negate=True)


@pytest.mark.skipif_before_api_version("2.3.0")
@pytest.mark.parametrize(
    "tags_func",
    [
        "torrents_tags",
        "torrent_tags.tags",
    ],
)
def test_tags(client, tags_func):
    try:
        assert isinstance(client.func(tags_func)(), TagList)
    except TypeError:
        assert isinstance(client.func(tags_func), TagList)


@pytest.mark.skipif_before_api_version("2.3.0")
def test_tags_slice(client):
    assert isinstance(client.torrents_tags()[1:2], TagList)


@pytest.mark.skipif_after_api_version("2.3.0")
@pytest.mark.parametrize(
    "tags_func",
    [
        "torrents_tags",
        "torrent_tags.tags",
    ],
)
def test_tags_not_implemented(client, tags_func):
    with pytest.raises(NotImplementedError):
        client.func(tags_func)()


@pytest.mark.skipif_before_api_version("2.3.0")
def test_add_tag_though_property(client):
    name = "newtag"
    client.torrent_tags.tags = name
    assert name in client.torrent_tags.tags
    client.torrent_tags.delete_tags(name)
    assert name not in client.torrent_tags.tags


@pytest.mark.skipif_after_api_version("2.3.0")
def test_add_tag_though_property_not_implemented(client):
    with pytest.raises(NotImplementedError):
        client.torrent_tags.tags = "asdf"


@pytest.mark.skipif_before_api_version("2.3.0")
@pytest.mark.parametrize(
    "add_tags_func",
    [
        "torrents_add_tags",
        "torrents_addTags",
        "torrent_tags.add_tags",
        "torrent_tags.addTags",
    ],
)
@pytest.mark.parametrize("tags", [["tag1"], ["tag1", "tag 2"]])
def test_add_tags(client, orig_torrent, add_tags_func, tags):
    try:
        client.func(add_tags_func)(tags=tags, torrent_hashes=orig_torrent.hash)
        check(lambda: orig_torrent.info.tags, tags, reverse=True)
    finally:
        client.torrents_delete_tags(tags=tags)


@pytest.mark.skipif_after_api_version("2.3.0")
@pytest.mark.parametrize(
    "add_tags_func",
    [
        "torrents_add_tags",
        "torrents_addTags",
        "torrent_tags.add_tags",
        "torrent_tags.addTags",
    ],
)
def test_add_tags_not_implemented(client, add_tags_func):
    with pytest.raises(NotImplementedError):
        client.func(add_tags_func)()


@pytest.mark.skipif_before_api_version("2.3.0")
@pytest.mark.parametrize(
    "remove_tags_func",
    [
        "torrents_remove_tags",
        "torrents_removeTags",
        "torrent_tags.remove_tags",
        "torrent_tags.removeTags",
    ],
)
@pytest.mark.parametrize("tags", [["tag1"], ["tag1", "tag 2"]])
def test_remove_tags(client, orig_torrent, remove_tags_func, tags):
    try:
        orig_torrent.add_tags(tags=tags)
        client.func(remove_tags_func)(tags=tags, torrent_hashes=orig_torrent.hash)
        check(lambda: orig_torrent.info.tags, tags, reverse=True, negate=True)
    finally:
        client.torrents_delete_tags(tags=tags)


@pytest.mark.skipif_after_api_version("2.3.0")
@pytest.mark.parametrize(
    "remove_tags_func",
    [
        "torrents_remove_tags",
        "torrents_removeTags",
        "torrent_tags.remove_tags",
        "torrent_tags.removeTags",
    ],
)
def test_remove_tags_not_implemented(client, remove_tags_func):
    with pytest.raises(NotImplementedError):
        client.func(remove_tags_func)()


@pytest.mark.skipif_before_api_version("2.3.0")
@pytest.mark.parametrize(
    "create_tags_func",
    [
        "torrents_create_tags",
        "torrents_createTags",
        "torrent_tags.create_tags",
        "torrent_tags.createTags",
    ],
)
@pytest.mark.parametrize("tags", [["tag1"], ["tag1", "tag 2"]])
def test_create_tags(client, create_tags_func, tags):
    try:
        client.func(create_tags_func)(tags=tags)
        check(lambda: client.torrents_tags(), tags, reverse=True)
    finally:
        client.torrents_delete_tags(tags=tags)


@pytest.mark.skipif_after_api_version("2.3.0")
@pytest.mark.parametrize(
    "create_tags_func",
    [
        "torrents_create_tags",
        "torrents_createTags",
        "torrent_tags.create_tags",
        "torrent_tags.createTags",
    ],
)
def test_create_tags_not_implemented(client, create_tags_func):
    with pytest.raises(NotImplementedError):
        client.func(create_tags_func)()


@pytest.mark.skipif_before_api_version("2.3.0")
@pytest.mark.parametrize(
    "delete_tags_func",
    [
        "torrents_delete_tags",
        "torrents_deleteTags",
        "torrent_tags.delete_tags",
        "torrent_tags.deleteTags",
    ],
)
@pytest.mark.parametrize("tags", [["tag1"], ["tag1", "tag 2"]])
def test_delete_tags(client, delete_tags_func, tags):
    client.torrents_create_tags(tags=tags)
    client.func(delete_tags_func)(tags=tags)
    check(lambda: client.torrents_tags(), tags, reverse=True, negate=True)


@pytest.mark.skipif_after_api_version("2.3.0")
@pytest.mark.parametrize(
    "delete_tags_func",
    [
        "torrents_delete_tags",
        "torrents_deleteTags",
        "torrent_tags.delete_tags",
        "torrent_tags.deleteTags",
    ],
)
def test_delete_tags_not_implemented(client, delete_tags_func):
    with pytest.raises(NotImplementedError):
        client.func(delete_tags_func)()
