from app.domain.context import MusicInfo
from app.modules.lrclib import LrclibModule


class _FakeLrclibResponse:
    """模拟 LRCLIB HTTP 响应，供解析、缓存和限流测试复用。"""

    def __init__(self, payload, status_code=200, headers=None):
        self._payload = payload
        self.status_code = status_code
        self.headers = headers or {}
        self.text = ""
        self.closed = False

    def json(self):
        """返回预设 JSON 负载。"""
        return self._payload

    def close(self):
        """记录响应已关闭。"""
        self.closed = True

    def __bool__(self):
        """复现 requests.Response 对 4xx/5xx 响应返回 False 的行为。"""
        return self.status_code < 400


def test_music_lyrics_prefers_exact_signature_and_synced_lyrics(monkeypatch) -> None:
    """元数据完整时应调用精确接口并优先返回同步歌词。"""
    module = LrclibModule()
    requested = []

    def fake_request(path, params=None):
        """记录精确查询参数并返回同步歌词。"""
        requested.append((path, params))
        return {
            "id": 3396226,
            "instrumental": False,
            "plainLyrics": "plain",
            "syncedLyrics": "[00:01.00]synced",
        }

    monkeypatch.setattr(module, "_request_json", fake_request)

    lyrics = module.music_lyrics(
        MusicInfo(
            title="I Want to Live",
            artists=["Borislav Slavov"],
            album="Baldur's Gate 3",
            duration=233,
        )
    )

    assert lyrics is not None
    assert lyrics.provider == "lrclib"
    assert lyrics.provider_id == "3396226"
    assert lyrics.content == "[00:01.00]synced"
    assert lyrics.extension == ".lrc"
    assert requested == [
        (
            "/api/get",
            {
                "track_name": "I Want to Live",
                "artist_name": "Borislav Slavov",
                "album_name": "Baldur's Gate 3",
                "duration": 233,
            },
        )
    ]


def test_music_lyrics_search_fallback_rejects_wrong_duration(monkeypatch) -> None:
    """精确接口未命中后只能选取标题、艺术家和时长均可信的搜索结果。"""
    module = LrclibModule()

    def fake_request(path, params=None):
        """精确查询返回未命中，搜索返回一条错误版本和一条正确版本。"""
        if path == "/api/get":
            return {}
        return [
            {
                "id": 1,
                "trackName": "晴天",
                "artistName": "周杰伦",
                "albumName": "演唱会",
                "duration": 310,
                "plainLyrics": "wrong",
            },
            {
                "id": 2,
                "trackName": "晴天",
                "artistName": "周杰伦",
                "albumName": "叶惠美",
                "duration": 269,
                "plainLyrics": "correct",
            },
        ]

    monkeypatch.setattr(module, "_request_json", fake_request)

    lyrics = module.music_lyrics(
        MusicInfo(
            title="晴天",
            artists=["周杰伦"],
            album="叶惠美",
            duration=270,
        )
    )

    assert lyrics is not None
    assert lyrics.provider_id == "2"
    assert lyrics.content == "correct"
    assert lyrics.extension == ".txt"


def test_request_json_honors_retry_after_once(monkeypatch) -> None:
    """LRCLIB 返回带 Retry-After 的过载响应时应等待并串行重试一次。"""
    responses = iter(
        [
            _FakeLrclibResponse(None, status_code=503, headers={"Retry-After": "2"}),
            _FakeLrclibResponse([{"id": 1}], status_code=200),
        ]
    )
    sleeps = []
    monkeypatch.setattr(LrclibModule, "_request_once", lambda *_args, **_kwargs: next(responses))
    monkeypatch.setattr("app.modules.lrclib.time.sleep", lambda seconds: sleeps.append(seconds))
    LrclibModule._request_json.cache_clear()

    result = LrclibModule._request_json(
        "/api/search",
        params={"track_name": "晴天", "artist_name": "周杰伦"},
    )

    assert result == [{"id": 1}]
    assert sleeps == [2.0]


def test_request_json_retry_network_failure_is_not_cached(monkeypatch) -> None:
    """过载重试遇到网络失败时应安全返回且不把失败写入缓存。"""
    responses = iter(
        [
            _FakeLrclibResponse(None, status_code=429, headers={"Retry-After": "0"}),
            None,
            _FakeLrclibResponse([{"id": 3}], status_code=200),
        ]
    )
    monkeypatch.setattr(LrclibModule, "_request_once", lambda *_args, **_kwargs: next(responses))
    monkeypatch.setattr("app.modules.lrclib.time.sleep", lambda _seconds: None)
    LrclibModule._request_json.cache_clear()
    params = {"track_name": "retry"}

    first = LrclibModule._request_json("/api/search", params=params)
    second = LrclibModule._request_json("/api/search", params=params)

    assert first is None
    assert second == [{"id": 3}]


def test_request_json_caches_not_found_response(monkeypatch) -> None:
    """未匹配结果也应缓存，避免整库重复刮削持续请求同一首歌。"""
    calls = {"count": 0}

    def fake_request(*_args, **_kwargs):
        """记录请求次数并返回 404。"""
        calls["count"] += 1
        return _FakeLrclibResponse(None, status_code=404)

    monkeypatch.setattr(LrclibModule, "_request_once", fake_request)
    LrclibModule._request_json.cache_clear()

    first = LrclibModule._request_json("/api/get", params={"track_name": "missing"})
    second = LrclibModule._request_json("/api/get", params={"track_name": "missing"})

    assert first == second == {}
    assert calls["count"] == 1


def test_request_json_caches_success_response(monkeypatch) -> None:
    """成功歌词响应应进入有界 TLRU 缓存，相同签名只访问一次外部 API。"""
    calls = {"count": 0}

    def fake_request(*_args, **_kwargs):
        """记录请求次数并返回固定歌词。"""
        calls["count"] += 1
        return _FakeLrclibResponse(
            {"id": 2, "syncedLyrics": "[00:01.00]晴天"},
            status_code=200,
        )

    monkeypatch.setattr(LrclibModule, "_request_once", fake_request)
    LrclibModule._request_json.cache_clear()
    params = {
        "track_name": "晴天",
        "artist_name": "周杰伦",
        "album_name": "叶惠美",
        "duration": 269,
    }

    first = LrclibModule._request_json("/api/get", params=params)
    second = LrclibModule._request_json("/api/get", params=params)

    assert first == second == {"id": 2, "syncedLyrics": "[00:01.00]晴天"}
    assert calls["count"] == 1
