import os
from unittest.mock import Mock

import pytest

from loki.scrapers.pan_115 import Pan115, PathExpect


def test_walk_path():
    pan = Pan115.__new__(Pan115)
    pan.path_to_id = Mock(return_value="0")

    def _list_files(dir_id: str, limit: str = "20"):
        if dir_id == "0":
            return [
                {"name": "dirA", "id": "1", "size": 0, "class": "", "is_dir": True},
                {"name": "file1.txt", "id": "f1", "size": 123, "class": "1", "is_dir": False},
            ]
        if dir_id == "1":
            return [
                {"name": "file2.mp4", "id": "f2", "size": 456, "class": "2", "is_dir": False},
                {"name": "dirB", "id": "2", "size": 0, "class": "", "is_dir": True},
            ]
        if dir_id == "2":
            return []
        raise AssertionError(f"unexpected dir_id: {dir_id}")

    pan.list_files = Mock(side_effect=_list_files)

    res = pan.walk_path("/base", limit="20", sleep_seconds=0)
    assert {x["path"] for x in res} == {
        "/base/dirA",
        "/base/file1.txt",
        "/base/dirA/file2.mp4",
        "/base/dirA/dirB",
    }
    by_path = {x["path"]: x for x in res}
    assert by_path["/base/file1.txt"]["id"] == "f1"
    assert by_path["/base/dirA"]["id"] == "1"


def test_list_files_dir_with_extension():
    pan = Pan115.__new__(Pan115)

    resp = Mock()
    resp.json = Mock(return_value={
        "state": True,
        "cid": "0",
        "data": [
            # Folder whose name looks like a file; API may include "class" but has no "fid".
            {"n": "abc.mp4", "cid": "10", "class": "0"},
            {"n": "real.mp4", "fid": "f1", "cid": "0", "s": "123", "class": "1"},
        ],
    })
    pan.request = Mock(return_value=resp)

    files = pan.list_files("0", limit="20")
    dir_item = next(x for x in files if x["name"] == "abc.mp4" and x["is_dir"] is True)
    file_item = next(x for x in files if x["name"] == "real.mp4" and x["is_dir"] is False)
    assert dir_item["id"] == "10"
    assert dir_item["class"] == ""
    assert file_item["id"] == "f1"


def test_path_to_id_expect_type():
    pan = Pan115.__new__(Pan115)

    def _list_files(dir_id: str, limit: str = "20"):
        if dir_id == "0":
            return [
                {"name": "abc.mp4", "id": "10", "is_dir": True},
                {"name": "real.mp4", "id": "f1", "is_dir": False},
                {"name": "dir", "id": "1", "is_dir": True},
            ]
        if dir_id == "1":
            return [{"name": "child", "id": "f2", "is_dir": False}]
        return []

    pan.list_files = Mock(side_effect=_list_files)

    assert pan.path_to_id("/", expect=PathExpect.DIR) == "0"
    assert pan.path_to_id("abc.mp4", expect=PathExpect.DIR) == "10"
    assert pan.path_to_id("abc.mp4", expect=PathExpect.FILE) == ""
    assert pan.path_to_id("real.mp4", expect=PathExpect.FILE) == "f1"
    assert pan.path_to_id("real.mp4", expect=PathExpect.DIR) == ""
    assert pan.path_to_id("dir/child", expect=PathExpect.FILE) == "f2"
    assert pan.path_to_id("real.mp4/child") == ""


def test_paths_to_ids_expect_type():
    pan = Pan115.__new__(Pan115)

    def _list_files(dir_id: str, limit: str = "20"):
        if dir_id == "0":
            return [
                {"name": "abc.mp4", "id": "10", "is_dir": True},
                {"name": "real.mp4", "id": "f1", "is_dir": False},
            ]
        return []

    pan.list_files = Mock(side_effect=_list_files)

    assert pan.paths_to_ids(["abc.mp4", "real.mp4", "missing"], expect=PathExpect.ANY) == ["10", "f1", "-1"]
    assert pan.paths_to_ids(["abc.mp4", "real.mp4"], expect=PathExpect.DIR) == ["10", "-1"]
    assert pan.paths_to_ids(["abc.mp4", "real.mp4"], expect=PathExpect.FILE) == ["-1", "f1"]


def test_delete_passes_expect_and_skips_missing():
    pan = Pan115.__new__(Pan115)
    pan.paths_to_ids = Mock(return_value=["1", "-1", "2"])

    resp = Mock()
    resp.json = Mock(return_value={"state": True})
    pan.request = Mock(return_value=resp)

    ok = pan.delete(["/a", "/missing", "/b"], expect=PathExpect.FILE)
    assert ok is True
    pan.paths_to_ids.assert_called_once()
    assert pan.paths_to_ids.call_args.kwargs["expect"] == PathExpect.FILE
    assert "fid[0]" in pan.request.call_args.kwargs["data"]


def test_move_passes_expect_filters_ids():
    pan = Pan115.__new__(Pan115)
    pan.paths_to_ids = Mock(return_value=["1", "-1", "2"])
    pan.create_folder = Mock(return_value="dest")

    resp = Mock()
    resp.json = Mock(return_value={"state": True})
    pan.request = Mock(return_value=resp)

    ok = pan.move(["/a", "/missing", "/b"], dest_path="/dest", expect=PathExpect.ANY)
    assert ok is True
    data = pan.request.call_args.kwargs["data"]
    assert data["pid"] == "dest"
    assert data["fid[0]"] == "1"
    assert data["fid[1]"] == "2"
    assert "fid[2]" not in data


def test_rename_passes_expect_filters_ids():
    pan = Pan115.__new__(Pan115)
    pan.paths_to_ids = Mock(return_value=["-1", "2"])

    resp = Mock()
    resp.json = Mock(return_value={"state": True})
    pan.request = Mock(return_value=resp)

    ok = pan.rename({"/missing": "a", "/ok": "b"}, expect=PathExpect.FILE)
    assert ok is True
    data = pan.request.call_args.kwargs["data"]
    assert "files_new_name[-1]" not in data
    assert data["files_new_name[2]"] == "b"


def test_add_lixian_task_to_id():
    pan = Pan115.__new__(Pan115)
    pan.uid = "u1"
    pan._offline_space = Mock(return_value={"sign": "s", "time": "t"})

    resp = Mock()
    resp.json = Mock(return_value={"errno": 0, "info_hash": "h", "url": "x"})
    pan.request = Mock(return_value=resp)

    res = pan.add_lixian_task_id(["magnet:?xt=urn:btih:xx"], "123")
    assert res["info_hash"] == "h"
    assert pan.request.call_args.kwargs["data"]["wp_path_id"] == "123"


def test_move_ids_filters_missing():
    pan = Pan115.__new__(Pan115)

    resp = Mock()
    resp.json = Mock(return_value={"state": True})
    pan.request = Mock(return_value=resp)

    ok = pan.move_ids(["1", "-1", "2", ""], "dest")
    assert ok is True
    data = pan.request.call_args.kwargs["data"]
    assert data["pid"] == "dest"
    assert data["fid[0]"] == "1"
    assert data["fid[1]"] == "2"
    assert "fid[2]" not in data


def test_delete_ids_filters_missing():
    pan = Pan115.__new__(Pan115)

    resp = Mock()
    resp.json = Mock(return_value={"state": True})
    pan.request = Mock(return_value=resp)

    ok = pan.delete_ids(["-1", "1", "", "2"])
    assert ok is True
    data = pan.request.call_args.kwargs["data"]
    assert data["fid[0]"] == "1"
    assert data["fid[1]"] == "2"
    assert "fid[2]" not in data


def test_rename_ids_filters_missing():
    pan = Pan115.__new__(Pan115)

    resp = Mock()
    resp.json = Mock(return_value={"state": True})
    pan.request = Mock(return_value=resp)

    ok = pan.rename_ids({"-1": "a", "": "b", "2": "c"})
    assert ok is True
    data = pan.request.call_args.kwargs["data"]
    assert "files_new_name[-1]" not in data
    assert data["files_new_name[2]"] == "c"


def test_create_folder_to_id_creates_missing_segments():
    pan = Pan115.__new__(Pan115)
    pan.create_folder_in = Mock(side_effect=["11"])

    def _list_files(dir_id: str, limit: str = "1000"):
        if dir_id == "0":
            return [{"name": "a", "id": "10", "is_dir": True}]
        if dir_id == "10":
            return []
        raise AssertionError(f"unexpected dir_id: {dir_id}")

    pan.list_files = Mock(side_effect=_list_files)

    out = pan.create_folder_to_id("0", "/a/b", limit="1000")
    assert out == "11"
    pan.create_folder_in.assert_called_once_with("10", "b")


@pytest.mark.skipif(
    os.environ.get("LOKI_PAN115_INTEGRATION") != "1",
    reason="integration test (requires 115 cookies/config and network)",
)
def test_pan115_integration_smoke():
    pan_115 = Pan115(profile_name="acc1")
    res = pan_115.list_files("0")
    assert isinstance(res, list)
