from __future__ import annotations

import json
import sys
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[1]
if str(REPO_ROOT) not in sys.path:
    sys.path.insert(0, str(REPO_ROOT))

from scripts.runtime.assembly import (
    ChapterAssemblyWorker,
    build_chapter_briefs,
    build_compressed_findings,
    build_chapter_user_prompt,
    run_chapter_assembly_workers,
    validate_chapter_markdown_citations,
    validate_chapter_brief,
    validate_compressed_finding,
)
from scripts.runtime.roles import resolve_runtime_profile


class FakeClient:
    def __init__(self, response: str) -> None:
        self.response = response
        self.calls: list[dict[str, object]] = []

    def chat_complete(self, **kwargs) -> str:
        self.calls.append(kwargs)
        return self.response


class TaggedClient:
    def __init__(self, responses_by_tag: dict[str, str]) -> None:
        self.responses_by_tag = responses_by_tag
        self.calls: list[dict[str, object]] = []

    def chat_complete(self, **kwargs) -> str:
        self.calls.append(kwargs)
        return self.responses_by_tag[str(kwargs["tag"])]


def chapter_brief(chapter_id: str, source_ids: list[str]) -> dict:
    return {
        "chapter_id": chapter_id,
        "chapter_title": "临床证据正在重塑需求判断",
        "packet_ids": [f"{chapter_id}-clinical"],
        "core_claims": [{"claim": "临床证据支持核心判断", "source_ids": source_ids[:1]}],
        "evidence_items": [{"source_id": source_ids[0], "summary": "III 期数据支持主要终点"}],
        "counter_evidence": [{"claim": "长期安全性仍需随访", "source_ids": source_ids[-1:]}],
        "source_ids": source_ids,
        "open_questions": [],
        "assembly_notes": ["按金字塔结构组织。"],
    }


def write_packet(path: Path, task_id: str, claim: str, source_id: str) -> None:
    packet = {
        "task_id": task_id,
        "claims": [{"claim": claim, "source_ids": [source_id]}],
        "evidence_items": [{"source_id": source_id, "summary": f"{claim} 的证据"}],
        "counter_evidence": [{"claim": "仍需关注样本量和外推限制", "source_ids": ["src_counter"]}],
        "source_ids": [source_id, "src_counter"],
        "sources": [
            {"id": source_id, "title": "来源", "url": f"https://example.com/{source_id}"},
            {"id": "src_counter", "title": "反方来源", "url": "https://example.com/counter"},
        ],
        "source_quality_notes": [f"{source_id} Tier 1"],
        "open_questions": ["还需要补充中国市场数据"],
        "raw_quotes_or_notes": ["English note can remain as source material."],
    }
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(packet, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


def test_build_chapter_briefs_aggregates_packets_by_chapter(tmp_path: Path) -> None:
    project = tmp_path / "project"
    cards = [
        {
            "task_id": "ch01-clinical",
            "chapter_ids": ["ch01"],
            "chapter_title": "临床证据正在重塑需求判断",
            "topic_axis": "clinical",
            "questions": ["q"],
            "search_routes": ["scholar"],
            "output_packet": "phase2/packets/ch01-clinical.json",
        },
        {
            "task_id": "ch01-market",
            "chapter_ids": ["ch01"],
            "chapter_title": "临床证据正在重塑需求判断",
            "topic_axis": "market",
            "questions": ["q"],
            "search_routes": ["news"],
            "output_packet": "phase2/packets/ch01-market.json",
        },
    ]
    (project / "phase2").mkdir(parents=True)
    (project / "phase2" / "task_cards.json").write_text(json.dumps(cards, ensure_ascii=False), encoding="utf-8")
    write_packet(project / "phase2/packets/ch01-clinical.json", "ch01-clinical", "临床证据支持核心判断", "src_001")
    write_packet(project / "phase2/packets/ch01-market.json", "ch01-market", "市场数据支持需求增长", "src_002")

    briefs = build_chapter_briefs(project)

    assert len(briefs) == 1
    brief = briefs[0]
    validate_chapter_brief(brief)
    assert brief["chapter_id"] == "ch01"
    assert brief["chapter_title"] == "临床证据正在重塑需求判断"
    assert brief["packet_ids"] == ["ch01-clinical", "ch01-market"]
    assert "src_001" in brief["source_ids"]
    assert "src_002" in brief["source_ids"]
    assert (project / "phase2/chapter_briefs/ch01.json").exists()

    compressed = build_compressed_findings(project)

    assert len(compressed) == 1
    finding = compressed[0]
    validate_compressed_finding(finding)
    assert finding["chapter_id"] == "ch01"
    assert "chapter_thesis" in finding
    assert "evidence_landings" in finding
    assert "src_001" in finding["source_ids"]
    assert (project / "phase2/compressed_findings/ch01.json").exists()


def test_build_chapter_briefs_skips_placeholder_packets(tmp_path: Path) -> None:
    project = tmp_path / "project"
    cards = [
        {
            "task_id": "ch01-good",
            "chapter_ids": ["ch01"],
            "chapter_title": "临床证据正在重塑需求判断",
            "topic_axis": "clinical",
            "questions": ["q"],
            "search_routes": ["scholar"],
            "output_packet": "phase2/packets/ch01-good.json",
        },
        {
            "task_id": "ch01-empty",
            "chapter_ids": ["ch01"],
            "chapter_title": "临床证据正在重塑需求判断",
            "topic_axis": "clinical",
            "questions": ["q"],
            "search_routes": ["scholar"],
            "output_packet": "phase2/packets/ch01-empty.json",
        },
    ]
    (project / "phase2").mkdir(parents=True)
    (project / "phase2/task_cards.json").write_text(json.dumps(cards, ensure_ascii=False), encoding="utf-8")
    write_packet(project / "phase2/packets/ch01-good.json", "ch01-good", "临床证据支持核心判断", "src_001")
    empty = {
        "task_id": "ch01-empty",
        "claims": [],
        "evidence_items": [],
        "counter_evidence": [],
        "source_ids": [],
        "source_quality_notes": [],
        "open_questions": [],
        "raw_quotes_or_notes": [],
    }
    (project / "phase2/packets/ch01-empty.json").write_text(json.dumps(empty, ensure_ascii=False), encoding="utf-8")

    briefs = build_chapter_briefs(project)

    assert len(briefs) == 1
    warnings = json.loads((project / "phase2/brief_warnings.json").read_text(encoding="utf-8"))
    assert warnings[0]["task_id"] == "ch01-empty"


def test_chapter_prompt_contains_brief_and_fragmentation_guard() -> None:
    brief = {
        "chapter_id": "ch01",
        "chapter_title": "临床证据正在重塑需求判断",
        "packet_ids": ["ch01-clinical"],
        "core_claims": [{"claim": "临床证据支持核心判断", "source_ids": ["src_001"]}],
        "evidence_items": [{"source_id": "src_001", "summary": "III 期数据支持主要终点"}],
        "counter_evidence": [{"claim": "长期安全性仍需随访", "source_ids": ["src_002"]}],
        "source_ids": ["src_001", "src_002"],
        "open_questions": [],
        "assembly_notes": ["避免重复 packet 原文；按金字塔结构组织。"],
    }

    prompt = build_chapter_user_prompt(brief)

    assert "临床证据正在重塑需求判断" in prompt
    assert "避免碎片化" in prompt
    assert "compressed finding" in prompt
    assert "只输出 Markdown" in prompt


def test_chapter_assembly_worker_writes_markdown(tmp_path: Path) -> None:
    runtime = resolve_runtime_profile(profile="medium")
    role = runtime.role_for_task("chapter_assembly")
    fake = FakeClient("# 第1章 临床证据正在重塑需求判断\n\n结论先行。[src_001]\n")
    worker = ChapterAssemblyWorker(role=role, client=fake)
    brief = {
        "chapter_id": "ch01",
        "chapter_title": "临床证据正在重塑需求判断",
        "packet_ids": ["ch01-clinical"],
        "core_claims": [{"claim": "临床证据支持核心判断", "source_ids": ["src_001"]}],
        "evidence_items": [{"source_id": "src_001", "summary": "III 期数据支持主要终点"}],
        "counter_evidence": [{"claim": "长期安全性仍需随访", "source_ids": ["src_002"]}],
        "source_ids": ["src_001", "src_002"],
        "open_questions": [],
        "assembly_notes": ["按金字塔结构组织。"],
    }

    output = worker.write_chapter(project_root=tmp_path, brief=brief)

    assert output == tmp_path / "phase2/drafts/ch01.md"
    assert "结论先行" in output.read_text(encoding="utf-8")
    assert fake.calls[0]["model"] == role.model
    assert "章节证据分析师" in fake.calls[0]["system"]
    assert "中文章节组装 worker" in fake.calls[0]["system"]


def test_validate_chapter_markdown_rejects_unknown_source_ids() -> None:
    brief = {
        "chapter_id": "ch01",
        "chapter_title": "临床证据正在重塑需求判断",
        "packet_ids": ["ch01-clinical"],
        "core_claims": [{"claim": "临床证据支持核心判断", "source_ids": ["src_001"]}],
        "evidence_items": [{"source_id": "src_001", "summary": "III 期数据支持主要终点"}],
        "counter_evidence": [{"claim": "长期安全性仍需随访", "source_ids": ["src_002"]}],
        "source_ids": ["src_001", "src_002"],
        "open_questions": [],
        "assembly_notes": ["按金字塔结构组织。"],
    }

    try:
        validate_chapter_markdown_citations("结论引用了不存在的来源。[src_fake]", brief)
    except ValueError as exc:
        assert "unknown citation ids" in str(exc)
        assert "src_fake" in str(exc)
    else:
        raise AssertionError("unknown source id should fail validation")


def test_chapter_assembly_worker_refuses_to_write_unknown_citations(tmp_path: Path) -> None:
    runtime = resolve_runtime_profile(profile="medium")
    role = runtime.role_for_task("chapter_assembly")
    fake = FakeClient("# 第1章 临床证据正在重塑需求判断\n\n结论先行。[src_fake]\n")
    worker = ChapterAssemblyWorker(role=role, client=fake)
    brief = {
        "chapter_id": "ch01",
        "chapter_title": "临床证据正在重塑需求判断",
        "packet_ids": ["ch01-clinical"],
        "core_claims": [{"claim": "临床证据支持核心判断", "source_ids": ["src_001"]}],
        "evidence_items": [{"source_id": "src_001", "summary": "III 期数据支持主要终点"}],
        "counter_evidence": [{"claim": "长期安全性仍需随访", "source_ids": ["src_002"]}],
        "source_ids": ["src_001", "src_002"],
        "open_questions": [],
        "assembly_notes": ["按金字塔结构组织。"],
    }

    try:
        worker.write_chapter(project_root=tmp_path, brief=brief)
    except ValueError as exc:
        assert "unknown citation ids" in str(exc)
    else:
        raise AssertionError("chapter with unknown citation should not be written")
    assert not (tmp_path / "phase2/drafts/ch01.md").exists()


def test_run_chapter_assembly_workers_records_errors_without_aborting_batch(tmp_path: Path) -> None:
    runtime = resolve_runtime_profile(profile="medium")
    fake = TaggedClient(
        {
            "chapter:ch01": "# 第1章 临床证据正在重塑需求判断\n\n结论先行。[src_001]\n",
            "chapter:ch02": "# 第2章 临床证据存在不确定性\n\n错误引用。[src_fake]\n",
        }
    )

    count = run_chapter_assembly_workers(
        project_root=tmp_path,
        briefs=[chapter_brief("ch01", ["src_001", "src_002"]), chapter_brief("ch02", ["src_003", "src_004"])],
        runtime=runtime,
        client_factory=lambda _role: fake,
        workers=2,
    )

    assert count == 1
    assert (tmp_path / "phase2/drafts/ch01.md").exists()
    assert not (tmp_path / "phase2/drafts/ch02.md").exists()
    error_path = tmp_path / "phase2/chapter_errors/ch02.json"
    assert error_path.exists()
    error = json.loads(error_path.read_text(encoding="utf-8"))
    assert error["chapter_id"] == "ch02"
    assert error["status"] == "failed"
    assert "src_fake" in error["error"]
