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.roles import resolve_runtime_profile
from scripts.runtime.sources import append_packet_sources, rebuild_sources_from_packets
from scripts.runtime.tasks import TaskCard
from scripts.runtime.workers import PacketWorker, build_material_context, build_route_query, build_search_context, normalize_packet_against_context


class FakeSearchProvider:
    def search(self, *, query: str, route: str, num_results: int):
        return [
            {
                "title": f"{route} result for {query}",
                "url": f"https://example.com/{route}",
                "snippet": "候选证据摘要",
                "route": route,
            }
        ][:num_results]


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

    def chat_complete(self, **kwargs) -> str:
        self.calls.append(kwargs)
        return json.dumps(self.response, ensure_ascii=False)


def sample_card() -> TaskCard:
    return TaskCard(
        task_id="ch01-literature",
        chapter_ids=["ch01"],
        topic_axis="literature",
        questions=["围绕临床证据提炼结论。"],
        search_routes=["scholar", "general"],
        output_packet="phase2/packets/ch01-literature.json",
    )


def test_build_search_context_assigns_stable_source_ids() -> None:
    context = build_search_context(sample_card(), FakeSearchProvider(), num_results_per_route=1)

    assert [source["id"] for source in context["candidate_sources"]] == [
        "src_ch01_literature_001",
        "src_ch01_literature_002",
    ]
    assert context["routes_used"] == ["scholar", "general"]


def test_fda_route_query_uses_english_axis_terms_not_chinese_title() -> None:
    card = TaskCard(
        task_id="ch10-fda_enforcement_precedents",
        chapter_ids=["ch10"],
        topic_axis="fda_enforcement_precedents",
        questions=["立即纠偏、体系补强、能力建设三层整改路线图必须绑定 owner、关闭证据和复核机制"],
        search_routes=["fda"],
        output_packet="phase2/packets/ch10-fda_enforcement_precedents.json",
        chapter_title="立即纠偏、体系补强、能力建设三层整改路线图必须绑定 owner、关闭证据和复核机制",
    )

    query = build_route_query(card, "fda")

    assert "立即纠偏" not in query
    assert "CAPA" in query
    assert "remediation" in query
    assert "verification evidence" in query


def test_integrated_scholar_query_does_not_leak_internal_axis_or_cjk_punctuation() -> None:
    card = TaskCard(
        task_id="ch07-chapter_integrated",
        chapter_ids=["ch07"],
        topic_axis="chapter_integrated",
        questions=["人员能力：培训有效性比培训记录更关键"],
        search_routes=["scholar"],
        output_packet="phase2/packets/ch07-chapter_integrated.json",
        chapter_title="人员能力：培训有效性比培训记录更关键",
    )

    query = build_route_query(card, "scholar")

    assert "chapter_integrated" not in query
    assert "、" not in query
    assert "  " not in query
    assert "training" in query
    assert "quality" in query
    assert not any("\u4e00" <= char <= "\u9fff" for char in query)


def test_evidence_route_query_is_short_english_candidate_evidence_query() -> None:
    card = TaskCard(
        task_id="ch08-chapter_integrated",
        chapter_ids=["ch08"],
        topic_axis="chapter_integrated",
        questions=["运营管理需要建立跨部门节奏、问题升级、指标看板和管理层 review"],
        search_routes=["evidence"],
        output_packet="phase2/packets/ch08-chapter_integrated.json",
        chapter_title="运营管理需要建立跨部门节奏、问题升级、指标看板和管理层 review",
    )

    query = build_route_query(card, "evidence")

    assert "evidence" in query
    assert "quality" in query
    assert "operations" in query
    assert "运营管理" not in query
    assert not any("\u4e00" <= char <= "\u9fff" for char in query)


def test_packet_worker_includes_search_context_in_prompt() -> None:
    context = build_search_context(sample_card(), FakeSearchProvider(), num_results_per_route=1)
    response = {
        "task_id": "ch01-literature",
        "claims": [{"claim": "候选证据支持判断", "source_ids": ["src_ch01_literature_001"]}],
        "evidence_items": [{"source_id": "src_ch01_literature_001", "summary": "摘要"}],
        "counter_evidence": [{"claim": "仍需更多数据", "source_ids": ["src_ch01_literature_002"]}],
        "source_ids": ["src_ch01_literature_001", "src_ch01_literature_002"],
        "sources": context["candidate_sources"],
        "source_quality_notes": ["候选来源需要后续评级"],
        "open_questions": [],
        "raw_quotes_or_notes": [],
    }
    role = resolve_runtime_profile(profile="medium").role_for_task("evidence_packet")
    fake = FakeClient(response)

    packet = PacketWorker(role=role, client=fake, search_provider=FakeSearchProvider()).run(sample_card())

    assert packet["sources"][0]["url"] == "https://example.com/scholar"
    assert "candidate_sources" in fake.calls[0]["user"]


def test_normalize_packet_fills_source_ids_and_sources_from_context() -> None:
    context = {
        "candidate_sources": [
            {"id": "src_a", "title": "A", "url": "https://example.com/a", "tier": "Tier 2", "score": 7}
        ]
    }
    packet = {
        "task_id": "ch01",
        "claims": [{"claim": "判断", "source_ids": ["src_a"]}],
        "evidence_items": [{"source_id": "src_a", "summary": "证据"}],
        "counter_evidence": [{"claim": "反方", "source_ids": ["src_a"]}],
        "source_quality_notes": [],
        "open_questions": [],
        "raw_quotes_or_notes": [],
    }

    normalized = normalize_packet_against_context(packet, context, None)

    assert normalized["source_ids"] == ["src_a"]
    assert normalized["sources"] == context["candidate_sources"]


def test_material_context_is_loaded_and_allowed_as_source(tmp_path: Path) -> None:
    project = tmp_path / "project"
    material = project / "phase0/extracted/audit.md"
    material.parent.mkdir(parents=True)
    material.write_text("白帆现场发现：偏差调查未闭环。", encoding="utf-8")
    card = TaskCard(
        task_id="ch01-chapter_integrated",
        chapter_ids=["ch01"],
        topic_axis="chapter_integrated",
        questions=["q"],
        search_routes=[],
        output_packet="phase2/packets/ch01-chapter_integrated.json",
        allowed_materials=["phase0/extracted/audit.md"],
    )
    context = build_material_context(card, project)
    response = {
        "task_id": "ch01-chapter_integrated",
        "claims": [{"claim": "现场材料显示偏差调查需要补强", "source_ids": [context["materials"][0]["source_id"]]}],
        "evidence_items": [{"source_id": context["materials"][0]["source_id"], "summary": "偏差调查未闭环。"}],
        "counter_evidence": [{"claim": "需与完整审计报告交叉确认", "source_ids": [context["materials"][0]["source_id"]]}],
        "source_ids": [context["materials"][0]["source_id"]],
        "sources": [
            {
                "id": context["materials"][0]["source_id"],
                "title": "audit.md",
                "url": "phase0/extracted/audit.md",
                "tier": "local_material",
            }
        ],
        "source_quality_notes": ["本地材料作为起点证据"],
        "open_questions": [],
        "raw_quotes_or_notes": ["白帆现场发现：偏差调查未闭环。"],
    }
    fake = FakeClient(response)
    role = resolve_runtime_profile(profile="medium").role_for_task("evidence_packet")

    packet = PacketWorker(role=role, client=fake, project_root=project).run(card)

    assert packet["source_ids"] == [context["materials"][0]["source_id"]]
    assert "白帆现场发现" in fake.calls[0]["user"]


def test_append_packet_sources_preserves_distinct_source_ids_for_same_url(tmp_path: Path) -> None:
    packet = {
        "sources": [
            {"id": "src_a", "title": "A", "url": "https://example.com/a", "tier": "Tier 2", "score": 7},
            {"id": "src_b", "title": "B", "url": "https://example.com/a", "tier": "Tier 2", "score": 7},
        ]
    }

    written = append_packet_sources(tmp_path / "sources.jsonl", packet)

    assert written == 2
    assert len((tmp_path / "sources.jsonl").read_text(encoding="utf-8").splitlines()) == 2


def test_rebuild_sources_from_packets_preserves_distinct_source_ids(tmp_path: Path) -> None:
    project = tmp_path / "project"
    packets = project / "phase2" / "packets"
    packets.mkdir(parents=True)
    packet = {
        "sources": [
            {"id": "src_001", "title": "A", "url": "https://example.com/a"},
            {"id": "src_002", "title": "A duplicate", "url": "https://example.com/a"},
            {"id": "src_003", "title": "Local", "url": "phase0/extracted/local.md"},
        ]
    }
    (packets / "ch01-a.json").write_text(json.dumps(packet, ensure_ascii=False), encoding="utf-8")

    count = rebuild_sources_from_packets(project)

    lines = (project / "phase2" / "sources.jsonl").read_text(encoding="utf-8").splitlines()
    assert count == 3
    assert len(lines) == 3
    assert "src_001" in lines[0]
    assert "src_002" in lines[1]
    assert "src_003" in lines[2]


def test_rebuild_sources_preserves_cache_metadata(tmp_path: Path) -> None:
    project = tmp_path / "project"
    packets = project / "phase2" / "packets"
    packets.mkdir(parents=True)
    source = {"id": "src_001", "title": "A", "url": "https://example.com/a"}
    (packets / "ch01-a.json").write_text(json.dumps({"sources": [source]}, ensure_ascii=False), encoding="utf-8")
    (project / "phase2/sources.jsonl").write_text(
        json.dumps(
            {
                **source,
                "cached_text_path": "phase2/source_cache/md/src_001.md",
                "cache_status": "fetched",
            },
            ensure_ascii=False,
        )
        + "\n",
        encoding="utf-8",
    )

    rebuild_sources_from_packets(project)

    row = json.loads((project / "phase2/sources.jsonl").read_text(encoding="utf-8"))
    assert row["cached_text_path"] == "phase2/source_cache/md/src_001.md"
    assert row["cache_status"] == "fetched"
