"""
裂变编排层 _score_notes 测试（合格线@80 + 违禁词硬拦截逻辑）
mock llm_score_copy，验证 _passed = passed AND not _block 的安全关键规则。
排序/降级落库逻辑(needs_optimization=0 if passed else 1)在 execute_fission_pipeline，
本文件只锁评分写回的 _score/_block/_passed 三字段语义。
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

import unittest.mock as mock
from app.services.fission_pipeline import _score_notes

SOURCE = {"title": "源", "content": "源正文"}


def _patch_scorer(monkeypatch, results):
    """按调用顺序返回 results 里的评分 dict。"""
    calls = {"i": 0}

    async def fake(client, copy, source, banned=None, pass_score=80):
        r = results[calls["i"] % len(results)]
        calls["i"] += 1
        return r

    monkeypatch.setattr("app.services.ai_engine.llm_scorer.llm_score_copy", fake)


def test_score_high_no_banned_passes(monkeypatch):
    _patch_scorer(monkeypatch, [
        {"score": 88, "passed": True, "banned_words_found": []},
    ])
    notes = [{"title": "a", "content": "c", "tags": []}]
    _score_notes(mock.MagicMock(), notes, SOURCE, [])
    assert notes[0]["_score"] == 88
    assert notes[0]["_block"] is False
    assert notes[0]["_passed"] is True


def test_score_low_marks_not_passed(monkeypatch):
    # 低于合格线 → 不达标，但分数照写（落库时标 needs_optimization 不丢弃）
    _patch_scorer(monkeypatch, [
        {"score": 62, "passed": False, "banned_words_found": []},
    ])
    notes = [{"title": "a", "content": "c", "tags": []}]
    _score_notes(mock.MagicMock(), notes, SOURCE, [])
    assert notes[0]["_score"] == 62
    assert notes[0]["_passed"] is False


def test_score_banned_word_hard_blocks_even_if_passed(monkeypatch):
    # 违禁词硬拦截：即便分数达标 passed=True，命中违禁词也强制 _passed=False
    _patch_scorer(monkeypatch, [
        {"score": 95, "passed": True, "banned_words_found": ["美白"]},
    ])
    notes = [{"title": "美白神器", "content": "c", "tags": []}]
    _score_notes(mock.MagicMock(), notes, SOURCE, ["美白"])
    assert notes[0]["_block"] is True
    assert notes[0]["_passed"] is False


def test_score_exception_records_zero(monkeypatch):
    # 评分接口抛异常 → 记0分不达标，不让整批裂变崩
    async def boom(*a, **k):
        raise RuntimeError("中转站429")
    monkeypatch.setattr("app.services.ai_engine.llm_scorer.llm_score_copy", boom)
    notes = [{"title": "a", "content": "c", "tags": []}]
    _score_notes(mock.MagicMock(), notes, SOURCE, [])
    assert notes[0]["_score"] == 0
    assert notes[0]["_passed"] is False


def test_score_multiple_notes_each_scored(monkeypatch):
    _patch_scorer(monkeypatch, [
        {"score": 90, "passed": True, "banned_words_found": []},
        {"score": 50, "passed": False, "banned_words_found": []},
    ])
    notes = [{"title": "a", "content": "c", "tags": []},
             {"title": "b", "content": "d", "tags": []}]
    _score_notes(mock.MagicMock(), notes, SOURCE, [])
    assert notes[0]["_passed"] is True
    assert notes[1]["_passed"] is False
