"""M2 离线测试：用 5 个真实 capture 验证 parse_detail_response 字段映射。"""
from __future__ import annotations

import json
from pathlib import Path

import pytest

from acquisition.crawler import (
    PLATFORMS,
    RateLimiter,
    detect_platform_and_id,
    fetch_post_detail,
    parse_content_id,
    parse_detail_response,
)
from core.config import PgConfig, Settings

FIXTURES = Path(__file__).parent / "fixtures"

# content_id -> (期望作者, 标题里应出现的子串)
EXPECTED = {
    "67e4bdf50000000006028a59": ("海狸教自媒体运营", "短视频脚本"),
    "698481e1000000000a02a7c1": ("Irvin是个编剧（接稿版）", "叙事结构"),
    "67e2e39b0000000003028ff0": ("拾意", "剧本创作"),
    "699308fa0000000016009697": ("方圆的增长飞轮", "选题"),
    "680659e8000000001a007a11": ("故事设计原理拆解学习"[:2], "故事设计"),
}


def _load(content_id: str) -> dict:
    return json.loads((FIXTURES / f"xhs_case_{content_id}.json").read_text("utf-8"))


def _settings() -> Settings:
    return Settings(
        pg=PgConfig(host="h", port=5432, user="u", password="p", database="d"),
        aiddit_crawler_base_url="http://crawler.test",
        crawler_timeout=30,
        openrouter_timeout_seconds=90,
        openrouter_model="m",
        openrouter_base_url="b",
        openrouter_api_key="k",
        llm_model="m",
        max_cards=12,
        frames_dir="f",
        douyin_ratio="540p",
        data_dir="",
    )


class _Resp:
    def __init__(self, payload):
        self._p = payload

    def raise_for_status(self):
        return None

    def json(self):
        return self._p


class _FakeClient:
    def __init__(self, payload):
        self.payload = payload
        self.calls = []
        self.closed = False

    def post(self, url, json=None, headers=None, timeout=None):
        self.calls.append({"url": url, "body": json})
        return _Resp(self.payload)

    def close(self):
        self.closed = True


class _RecordingLimiter:
    def __init__(self):
        self.buckets = []

    def wait(self, bucket: str):
        self.buckets.append(bucket)


@pytest.mark.parametrize("content_id", list(EXPECTED))
def test_parse_detail_fields(content_id: str):
    # 重抓的 xhs_case_*.json 即原始响应 {code,msg,data}，无 response 包裹
    response = _load(content_id)
    post = parse_detail_response(response, fallback_content_id=content_id)

    assert post.id == f"xhs_{content_id}"
    assert post.platform == "xiaohongshu"
    assert post.content_id == content_id
    assert content_id in post.url
    assert post.title, "title 不应为空"
    _, title_sub = EXPECTED[content_id]
    assert title_sub in post.title
    assert post.raw.get("code") == 0


def test_authors():
    for cid, (author, _) in EXPECTED.items():
        post = parse_detail_response(_load(cid), fallback_content_id=cid)
        if cid in ("67e4bdf50000000006028a59", "698481e1000000000a02a7c1",
                   "67e2e39b0000000003028ff0", "699308fa0000000016009697"):
            assert post.author_name == author


def test_shiyi_posts_text_sparse():
    """拾意两条正文几乎只有话题串——M3 多模态提取存在的理由。"""
    for cid in ("67e2e39b0000000003028ff0", "680659e8000000001a007a11"):
        post = parse_detail_response(_load(cid), fallback_content_id=cid)
        stripped = post.body_text.replace("#", "").replace("话题", "").strip()
        assert len(stripped) < 40, f"{cid} body_text 应很稀疏: {post.body_text!r}"


def test_parse_content_id_from_url():
    url = ("https://www.xiaohongshu.com/explore/67e4bdf50000000006028a59"
           "?xsec_token=ABC=&xsec_source=pc_like")
    assert parse_content_id(url) == "67e4bdf50000000006028a59"
    assert parse_content_id("67e4bdf50000000006028a59") == "67e4bdf50000000006028a59"


@pytest.mark.parametrize("url,platform,cid", [
    # 长链
    ("https://www.xiaohongshu.com/explore/67e4bdf50000000006028a59?xsec_token=A",
     "xiaohongshu", "67e4bdf50000000006028a59"),
    ("https://www.xiaohongshu.com/discovery/item/680659e8000000001a007a11",
     "xiaohongshu", "680659e8000000001a007a11"),
    ("https://www.douyin.com/video/7612631899479648866", "douyin", "7612631899479648866"),
    ("https://www.douyin.com/search/x?modal_id=7612631899479648866&type=general",
     "douyin", "7612631899479648866"),
    ("https://www.gifshow.com/fw/photo/3xepqgwddgbickc", "kuaishou", "3xepqgwddgbickc"),
    ("https://www.kuaishou.com/short-video/3xepqgwddgbickc", "kuaishou", "3xepqgwddgbickc"),
    ("https://www.bilibili.com/video/BV1qd4y1x76w/?spm_id_from=333", "bilibili", "BV1qd4y1x76w"),
    # 裸 id 兜底
    ("67e4bdf50000000006028a59", "xiaohongshu", "67e4bdf50000000006028a59"),
    ("7612631899479648866", "douyin", "7612631899479648866"),
    ("BV1qd4y1x76w", "bilibili", "BV1qd4y1x76w"),
])
def test_detect_platform_and_id(url, platform, cid):
    assert detect_platform_and_id(url) == (platform, cid)


def test_parse_detail_platform_prefix():
    """同一 parse 函数 + platform 参数 → 平台名与 id 前缀正确。"""
    response = _load("67e4bdf50000000006028a59")
    for platform, expected_prefix in [(p, c["prefix"]) for p, c in PLATFORMS.items()]:
        post = parse_detail_response(response, platform=platform,
                                     fallback_content_id="67e4bdf50000000006028a59")
        assert post.platform == platform
        assert post.id == f"{expected_prefix}_67e4bdf50000000006028a59"


def test_fetch_douyin_detail_piaoquantv_body_and_provider():
    payload = {"code": 0, "data": {"data": {
        "channel_content_id": "761",
        "content_link": "https://douyin.test/video/761",
        "content_type": "video",
        "title": "视频",
        "video_url_list": [{"video_url": "https://video.test/761.mp4"}],
    }}}
    client = _FakeClient(payload)
    limiter = _RecordingLimiter()

    post = fetch_post_detail(
        "761",
        platform="douyin",
        provider="piaoquantv",
        settings=_settings(),
        http_client=client,
        rate_limiter=limiter,
    )

    assert post.provider == "piaoquantv"
    assert post.raw["detail_provider"] == "piaoquantv"
    assert client.calls[0]["url"].startswith("http://crawapi.piaoquantv.com/")
    assert client.calls[0]["body"]["content_id"] == "761"
    assert client.calls[0]["body"]["account_id"] == "7450041106378522636"
    assert client.calls[0]["body"]["cookie_batch"] == "default"
    assert limiter.buckets == ["douyin"]


def test_fetch_douyin_detail_aiddit_body_and_provider():
    payload = {"code": 0, "data": {"data": {
        "channel_content_id": "762",
        "content_link": "https://douyin.test/video/762",
        "content_type": "video",
        "title": "视频",
        "video_url_list": [{"video_url": "https://video.test/762.mp4"}],
    }}}
    client = _FakeClient(payload)

    post = fetch_post_detail(
        "762",
        platform="douyin",
        provider="aiddit",
        settings=_settings(),
        http_client=client,
        rate_limiter=_RecordingLimiter(),
    )

    assert post.provider == "aiddit"
    assert client.calls[0]["url"].startswith("http://crawler.test/")
    assert client.calls[0]["body"] == {"content_id": "762"}


def test_fetch_douyin_detail_rejects_unknown_provider():
    with pytest.raises(RuntimeError):
        fetch_post_detail(
            "762",
            platform="douyin",
            provider="other",
            settings=_settings(),
            http_client=_FakeClient({}),
            rate_limiter=RateLimiter(min_interval_seconds=0.0),
        )
