import asyncio
import base64
import json

from app.chain.download import DownloadChain
from app.domain.context import TorrentInfo
from app.modules.indexer.parser.yema import YemaSiteUserInfo
from app.modules.indexer.spider.yema import YemaSpider
from app.schemas import MediaType


class _FakeResponse:
    """构造 YemaPT 开放 API 测试使用的最小响应对象。"""

    def __init__(self, payload: dict, status_code: int = 200):
        """保存响应数据和状态码。"""
        self._payload = payload
        self.status_code = status_code
        self.reason = "OK"
        self.text = json.dumps(payload)

    def __bool__(self) -> bool:
        """按 HTTP 成功状态模拟 requests.Response 的布尔值。"""
        return self.status_code < 400

    def json(self) -> dict:
        """返回预设 JSON 数据。"""
        return self._payload


def _build_indexer() -> dict:
    """构造 YemaPT 开放 API Spider 所需的最小站点配置。"""
    return {
        "id": "yemapt",
        "name": "YemaPT",
        "domain": "https://www.yemapt.org/",
        "apikey": "yema-auth-key",
        "ua": "MoviePilot-Test",
        "proxy": False,
    }


def _torrent_response() -> _FakeResponse:
    """构造 YemaPT 公开种子列表响应。"""
    return _FakeResponse({
        "success": True,
        "showType": 0,
        "data": [{
            "id": 100,
            "showName": "Movie.2026.2160p.WEB-DL.H.265-GROUP",
            "shortDesc": "电影中文副标题",
            "categoryId": 4,
            "fileSize": 21474836480,
            "seedNum": 15,
            "leechNum": 2,
            "completedNum": 30,
            "listingTime": "2026-08-02T14:00:00+08:00",
            "uploadPromotion": "double_upload",
            "downloadPromotion": "free",
            "downloadPromotionEndTime": "2026-08-04T14:00:00+08:00",
            "tagList": ["6", "9"],
            "hrPunishEnable": True,
        }],
    })


def test_yemapt_search_uses_open_api_auth_and_maps_fields(monkeypatch):
    """YemaPT 搜索应使用 AuthKey、开放接口及标准字段映射。"""
    captured = {}

    def fake_post_res(request, url: str, json: dict = None, **_kwargs):
        """记录开放 API 搜索请求并回放种子数据。"""
        captured.update({
            "url": url,
            "json": json,
            "headers": request._headers,
            "cookies": request._cookies,
        })
        return _torrent_response()

    monkeypatch.setattr(
        "app.modules.indexer.spider.yema.RequestUtils.post_res",
        fake_post_res,
    )

    error, torrents = YemaSpider(_build_indexer()).search(
        keyword="Movie",
        mtype=MediaType.MOVIE,
        page=2,
    )

    assert not error
    assert captured == {
        "url": "https://www.yemapt.org/openApi/torrent/fetchOpenTorrentList.json",
        "json": {
            "categoryId": 4,
            "keyword": "Movie",
            "pageParam": {"current": 3, "pageSize": 40},
            "sorter": {},
        },
        "headers": {
            "Authorization": "yema-auth-key",
            "Content-Type": "application/json",
            "Accept": "application/json, text/plain, */*",
            "User-Agent": "MoviePilot-Test",
        },
        "cookies": None,
    }
    assert torrents[0] == {
        "title": "Movie.2026.2160p.WEB-DL.H.265-GROUP",
        "description": "电影中文副标题",
        "enclosure": torrents[0]["enclosure"],
        "pubdate": "2026-08-02 14:00:00",
        "size": 21474836480,
        "seeders": 15,
        "peers": 2,
        "grabs": 30,
        "downloadvolumefactor": 0,
        "uploadvolumefactor": 2,
        "freedate": "2026-08-04 14:00:00",
        "page_url": "https://www.yemapt.org/#/torrent/detail/100/",
        "labels": ["中字", "HDR10"],
        "hit_and_run": True,
        "category": MediaType.MOVIE.value,
    }

    encoded_config, credential_url = torrents[0]["enclosure"].split("]", 1)
    request_config = json.loads(base64.b64decode(encoded_config[1:]).decode("utf-8"))
    assert credential_url == (
        "https://www.yemapt.org/openApi/torrent/generateDownloadKey.json"
    )
    assert request_config["header"]["Authorization"] == "yema-auth-key"
    assert request_config["params"] == {"id": 100}
    assert request_config["success"] == "success"
    assert request_config["result_path"] == "api/torrent/download1"
    assert request_config["result_query_param"] == "token"


def test_yemapt_search_rejects_business_failure(monkeypatch):
    """HTTP 成功但业务失败时，YemaPT 搜索必须返回错误。"""
    monkeypatch.setattr(
        "app.modules.indexer.spider.yema.RequestUtils.post_res",
        lambda *_args, **_kwargs: _FakeResponse({
            "success": False,
            "errorCode": 403,
            "errorMessage": "need api auth",
        }),
    )

    error, torrents = YemaSpider(_build_indexer()).search(keyword="Movie")

    assert error
    assert torrents == []


def test_yemapt_maps_audio_and_music_video_categories_to_music():
    """YemaPT 音乐和 MV/演唱会分类都应映射为统一音乐类型。"""
    results = YemaSpider(_build_indexer())._parse_result([
        {"id": 101, "showName": "Album FLAC", "categoryId": 8},
        {"id": 102, "showName": "Live Concert", "categoryId": 16},
    ])

    assert [item["category"] for item in results] == [
        MediaType.MUSIC.value,
        MediaType.MUSIC.value,
    ]


def test_yemapt_music_search_queries_both_music_categories(monkeypatch):
    """YemaPT 音乐搜索应分别查询音乐和 MV/演唱会分类。"""
    request_payloads = []

    def fake_post_res(_request, url: str, json: dict = None, **_kwargs):
        """记录音乐分类请求并按分类返回一条种子。"""
        request_payloads.append(json)
        category_id = json["categoryId"]
        return _FakeResponse({
            "success": True,
            "data": [{
                "id": category_id,
                "showName": f"Music {category_id}",
                "categoryId": category_id,
            }],
        })

    monkeypatch.setattr(
        "app.modules.indexer.spider.yema.RequestUtils.post_res",
        fake_post_res,
    )

    error, torrents = YemaSpider(_build_indexer()).search(
        keyword="Artist",
        mtype=MediaType.MUSIC,
    )

    assert not error
    assert [payload["categoryId"] for payload in request_payloads] == [8, 16]
    assert [torrent["category"] for torrent in torrents] == [
        MediaType.MUSIC.value,
        MediaType.MUSIC.value,
    ]


def test_yemapt_tv_search_queries_all_video_categories(monkeypatch):
    """YemaPT 剧集搜索应覆盖剧集、短剧、综艺、动漫、纪录片和体育。"""
    request_payloads = []

    def fake_post_res(_request, url: str, json: dict = None, **_kwargs):
        """记录剧集分类请求并返回空的成功响应。"""
        request_payloads.append(json)
        return _FakeResponse({"success": True, "data": []})

    monkeypatch.setattr(
        "app.modules.indexer.spider.yema.RequestUtils.post_res",
        fake_post_res,
    )

    error, torrents = YemaSpider(_build_indexer()).search(
        keyword="Series",
        mtype=MediaType.TV,
    )

    assert not error
    assert torrents == []
    assert [payload["categoryId"] for payload in request_payloads] == [
        5, 6, 13, 14, 15, 17,
    ]


def test_yemapt_async_search_uses_open_api(monkeypatch):
    """YemaPT 异步搜索应使用与同步搜索相同的开放 API 契约。"""
    captured = {}

    async def fake_post_res(request, url: str, json: dict = None, **_kwargs):
        """记录异步开放 API 请求并回放种子数据。"""
        captured.update({
            "url": url,
            "json": json,
            "headers": request._headers,
            "cookies": request._cookies,
        })
        return _torrent_response()

    monkeypatch.setattr(
        "app.modules.indexer.spider.yema.AsyncRequestUtils.post_res",
        fake_post_res,
    )

    error, torrents = asyncio.run(
        YemaSpider(_build_indexer()).async_search(keyword="Movie", page=0)
    )

    assert not error
    assert len(torrents) == 1
    assert captured["url"].endswith("/openApi/torrent/fetchOpenTorrentList.json")
    assert captured["headers"]["Authorization"] == "yema-auth-key"
    assert captured["cookies"] is None


def test_yemapt_user_parser_uses_basic_info_only(monkeypatch):
    """YemaPT 用户解析器应仅调用开放 API 已提供的基本信息接口。"""
    captured = []
    payload = {
        "success": True,
        "showType": 0,
        "data": {
            "id": 10,
            "name": "yema-user",
            "bonus": 1000000,
            "level": 7,
            "registerTime": "2024-05-01T00:00:00+08:00",
            "promotionUploadSize": 2000000,
            "promotionDownloadSize": 1000000,
        },
    }

    def fake_post_res(request, url: str, json: dict = None, **_kwargs):
        """记录用户基本信息请求并回放开放 API 响应。"""
        captured.append((url, json, request._headers, request._cookies))
        return _FakeResponse(payload)

    monkeypatch.setattr(
        "app.modules.indexer.parser.RequestUtils.post_res",
        fake_post_res,
    )
    parser = YemaSiteUserInfo(
        site_name="YemaPT",
        url="https://www.yemapt.org/",
        site_cookie="legacy-cookie",
        apikey="yema-auth-key",
        token=None,
        ua="MoviePilot-Test",
        proxy=False,
    )

    parser.parse()

    assert len(captured) == 1
    assert captured[0][0] == "https://www.yemapt.org/openApi/user/fetchBasicInfo.json"
    assert captured[0][1] == {}
    assert captured[0][2]["Authorization"] == "yema-auth-key"
    assert captured[0][3] is None
    assert parser.userid == 10
    assert parser.username == "yema-user"
    assert parser.user_level == "7"
    assert parser.upload == 2000000
    assert parser.download == 1000000
    assert parser.ratio == 2.0


def test_yemapt_download_generates_and_urlencodes_temporary_key(monkeypatch):
    """YemaPT 下载应临时生成凭证并在下载 URL 中安全编码。"""
    captured = {}

    def fake_post_res(request, url: str, params: dict = None, **_kwargs):
        """回放下载凭证接口响应。"""
        captured.update({
            "credential_url": url,
            "credential_params": params,
            "credential_headers": request._headers,
            "credential_cookies": request._cookies,
        })
        return _FakeResponse({"success": True, "data": "abc+/="})

    def fake_download_torrent(_helper, **kwargs):
        """记录最终种子文件下载地址并返回有效种子内容。"""
        captured.update(kwargs)
        return None, b"torrent-content", "Movie", ["Movie.mkv"], ""

    monkeypatch.setattr("app.chain.download.RequestUtils.post_res", fake_post_res)
    monkeypatch.setattr(
        "app.chain.download.TorrentHelper.download_torrent",
        fake_download_torrent,
    )
    enclosure = YemaSpider(_build_indexer())._build_download_url(100)
    torrent = TorrentInfo(
        title="Movie.2026",
        enclosure=enclosure,
        site_cookie="legacy-cookie",
        site_ua="MoviePilot-Test",
        site_proxy=False,
    )

    content, folder, files = object.__new__(DownloadChain).download_torrent(torrent)

    assert content == b"torrent-content"
    assert folder == "Movie"
    assert files == ["Movie.mkv"]
    assert captured["credential_url"] == (
        "https://www.yemapt.org/openApi/torrent/generateDownloadKey.json"
    )
    assert captured["credential_params"] == {"id": 100}
    assert captured["credential_headers"]["Authorization"] == "yema-auth-key"
    assert captured["credential_cookies"] is None
    assert captured["url"] == (
        "https://www.yemapt.org/api/torrent/download1?token=abc%2B%2F%3D"
    )
    assert captured["cookie"] is None
    assert captured["cache_invalid"] is False
