import zipfile

from django.test import override_settings
from django.urls import reverse
from django.core.files.uploadedfile import SimpleUploadedFile

from agent_core.results import AgentResult
from apps.audit.models import AgentAuditLog
from apps.audit.models import NotificationRecord
from apps.chat.models import Conversation
from apps.chat.services import create_conversation_for_batch
from apps.documents.models import ExportedDocument, SubmissionBatch, UploadedDocument


def _create_conversation_with_batch():
    batch = SubmissionBatch.objects.create(
        batch_id="SUB-20260604-001",
        product_name="新型冠状病毒 2019-nCoV 核酸检测试剂盒",
        workflow_type="registration",
        conversation_id="conv-001",
        file_count=2,
        page_count=12,
        import_status="completed",
    )
    conversation = Conversation.objects.create(
        conversation_id="conv-001",
        title="新型冠状病毒 2019-nCoV 核酸检测试剂盒",
        product_name=batch.product_name,
        batch_id=batch.batch_id,
        task_status="processing",
        node_results=[
            {"label": "资料包导入", "status": "已完成"},
            {"label": "目录汇总", "status": "处理中"},
        ],
    )
    return batch, conversation


def test_chat_post_returns_agent_result_and_audit_log(client, db, monkeypatch):
    batch, conversation = _create_conversation_with_batch()
    UploadedDocument.objects.create(
        batch=batch,
        scenario_id="document_review",
        original_name="说明书.md",
        file_type="md",
        size=1,
        status=UploadedDocument.STATUS_INDEXED,
    )

    monkeypatch.setattr(
        "apps.chat.views.run_agent",
        lambda *args, **kwargs: AgentResult(answer="模拟回答", status="success"),
    )

    response = client.post(
        reverse("chat:detail", args=[conversation.conversation_id]),
        {"message": "如何处理异常？"},
    )

    assert response.status_code == 200
    content = response.content.decode("utf-8")
    assert "审核智能体" in content
    assert "模拟回答" in content
    assert AgentAuditLog.objects.count() == 1
    assert AgentAuditLog.objects.get().batch_id == batch.batch_id


def test_chat_rejects_empty_message(client, db):
    _batch, conversation = _create_conversation_with_batch()

    response = client.post(reverse("chat:detail", args=[conversation.conversation_id]), {"message": ""})

    assert response.status_code == 200
    assert AgentAuditLog.objects.count() == 0
    assert "请输入要咨询的问题" in response.content.decode("utf-8")


def test_chat_passes_selected_document_ids_to_agent_core(client, db, monkeypatch):
    batch, conversation = _create_conversation_with_batch()
    selected = UploadedDocument.objects.create(
        batch=batch,
        scenario_id="document_review",
        original_name="selected.md",
        file_type="md",
        size=1,
        status=UploadedDocument.STATUS_INDEXED,
    )
    UploadedDocument.objects.create(
        batch=batch,
        scenario_id="document_review",
        original_name="other.md",
        file_type="md",
        size=1,
        status=UploadedDocument.STATUS_INDEXED,
    )
    captured = {}

    def fake_run_agent(scenario_config, user_input, options=None):
        captured["options"] = options or {}
        return AgentResult(answer="ok", status="success")

    monkeypatch.setattr("apps.chat.views.run_agent", fake_run_agent)

    response = client.post(
        reverse("chat:detail", args=[conversation.conversation_id]),
        {"message": "只查选中文档", "document_ids": [str(selected.id)]},
    )

    assert response.status_code == 200
    assert captured["options"]["document_ids"] == [selected.id]
    assert captured["options"]["conversation_id"] == conversation.conversation_id
    assert captured["options"]["batch_id"] == batch.batch_id


def test_chat_renders_three_column_workspace_and_node_results(client, db):
    batch, conversation = _create_conversation_with_batch()
    UploadedDocument.objects.create(
        batch=batch,
        scenario_id="document_review",
        original_name="说明书.md",
        file_type="md",
        size=1,
        status=UploadedDocument.STATUS_INDEXED,
    )

    response = client.get(reverse("chat:detail", args=[conversation.conversation_id]))

    content = response.content.decode("utf-8")
    assert response.status_code == 200
    assert "会话历史" in content
    assert "对话区与节点导航" in content
    assert "上传区" in content
    assert "资料包导入 / 已完成" in content
    assert "目录汇总 / 处理中" in content


def test_chat_execution_creates_notification_record_from_agent_result(client, db, monkeypatch):
    batch, conversation = _create_conversation_with_batch()
    UploadedDocument.objects.create(
        batch=batch,
        scenario_id="document_review",
        original_name="说明书.md",
        file_type="md",
        size=1,
        status=UploadedDocument.STATUS_INDEXED,
    )

    monkeypatch.setattr(
        "apps.chat.views.run_agent",
        lambda *args, **kwargs: AgentResult(
            answer="执行完成",
            status="success",
            notification_payload={
                "batch_id": batch.batch_id,
                "conversation_id": conversation.conversation_id,
                "product_name": batch.product_name,
                "notify_reason": "task_completed",
                "owners": [
                    {
                        "owner_role": "注册资料负责人",
                        "feishu_user_id": "ou_demo_1",
                    }
                ],
            },
        ),
    )

    response = client.post(
        reverse("chat:detail", args=[conversation.conversation_id]),
        {"message": "执行审核"},
    )

    assert response.status_code == 200
    record = NotificationRecord.objects.get()
    assert record.notify_reason == "task_completed"
    assert record.batch_id == batch.batch_id
    assert record.web_detail_url.endswith(f"/audit/{AgentAuditLog.objects.get().id}/")


def test_chat_execution_uses_notification_payload_message_status_and_receipt(client, db, monkeypatch):
    batch, conversation = _create_conversation_with_batch()
    UploadedDocument.objects.create(
        batch=batch,
        scenario_id="document_review",
        original_name="说明书.md",
        file_type="md",
        size=1,
        status=UploadedDocument.STATUS_INDEXED,
    )

    monkeypatch.setattr(
        "apps.chat.views.run_agent",
        lambda *args, **kwargs: AgentResult(
            answer="通知已发送",
            status="success",
            notification_payload={
                "batch_id": batch.batch_id,
                "conversation_id": conversation.conversation_id,
                "product_name": batch.product_name,
                "notify_reason": "task_completed",
                "message_status": "sent",
                "web_detail_url": "https://example.com/audit/custom",
                "receipt": {"message_id": "msg-custom", "status": "sent"},
                "owners": [
                    {
                        "owner_role": "注册资料负责人",
                        "feishu_user_id": "ou_demo_1",
                    }
                ],
            },
        ),
    )

    response = client.post(
        reverse("chat:detail", args=[conversation.conversation_id]),
        {"message": "执行通知任务"},
    )

    assert response.status_code == 200
    record = NotificationRecord.objects.get()
    assert record.message_status == "sent"
    assert record.receipt["message_id"] == "msg-custom"


def test_chat_execution_creates_failed_notification_record_and_updates_conversation(client, db, monkeypatch):
    batch, conversation = _create_conversation_with_batch()
    UploadedDocument.objects.create(
        batch=batch,
        scenario_id="document_review",
        original_name="说明书.md",
        file_type="md",
        size=1,
        status=UploadedDocument.STATUS_INDEXED,
    )

    monkeypatch.setattr(
        "apps.chat.views.run_agent",
        lambda *args, **kwargs: AgentResult(
            answer="执行失败",
            status="failed",
            error="规则执行失败",
            node_results=[
                {"code": "package_import", "label": "资料包导入", "status": "已完成"},
                {"code": "overview", "label": "目录汇总", "status": "已完成"},
                {"code": "risk", "label": "风险预警", "status": "已阻断"},
                {"code": "feishu_notify", "label": "飞书通知", "status": "失败"},
            ],
            notification_payload={
                "batch_id": batch.batch_id,
                "conversation_id": conversation.conversation_id,
                "product_name": batch.product_name,
                "notify_reason": "task_failed",
                "owners": [
                    {
                        "owner_role": "注册申报负责人",
                        "feishu_user_id": "ou_demo_2",
                    }
                ],
            },
        ),
    )

    response = client.post(
        reverse("chat:detail", args=[conversation.conversation_id]),
        {"message": "执行失败任务"},
    )

    assert response.status_code == 200
    record = NotificationRecord.objects.get()
    conversation.refresh_from_db()
    assert record.notify_reason == "task_failed"
    assert record.message_status == "failed"
    assert record.web_detail_url.endswith(f"/audit/{AgentAuditLog.objects.get().id}/")
    assert conversation.task_status == "failed"
    assert conversation.node_results[-1]["label"] == "飞书通知"


def test_chat_execution_persists_agent_node_results_to_conversation(client, db, monkeypatch):
    batch, conversation = _create_conversation_with_batch()
    UploadedDocument.objects.create(
        batch=batch,
        scenario_id="document_review",
        original_name="说明书.md",
        file_type="md",
        size=1,
        status=UploadedDocument.STATUS_INDEXED,
    )

    monkeypatch.setattr(
        "apps.chat.views.run_agent",
        lambda *args, **kwargs: AgentResult(
            answer="已生成风险结论",
            status="success",
            node_results=[
                {"code": "package_import", "label": "资料包导入", "status": "已完成"},
                {"code": "overview", "label": "目录汇总", "status": "已完成"},
                {"code": "completeness", "label": "法规完整性检查", "status": "已完成"},
                {"code": "field_extraction", "label": "字段抽取", "status": "已完成"},
                {"code": "consistency", "label": "一致性核查", "status": "待复核"},
                {"code": "risk", "label": "风险预警", "status": "已阻断", "summary": "存在高风险"},
                {"code": "word_export", "label": "Word 回填导出", "status": "待处理"},
                {"code": "feishu_notify", "label": "飞书通知", "status": "待处理"},
            ],
            notification_payload={
                "batch_id": batch.batch_id,
                "conversation_id": conversation.conversation_id,
                "product_name": batch.product_name,
                "notify_reason": "task_completed",
                "owners": [],
            },
        ),
    )

    response = client.post(
        reverse("chat:detail", args=[conversation.conversation_id]),
        {"message": "执行节点任务"},
    )

    assert response.status_code == 200
    conversation.refresh_from_db()
    assert len(conversation.node_results) == 8
    assert conversation.task_status == "success"
    assert conversation.latest_summary["answer"] == "已生成风险结论"


def test_create_conversation_for_batch_initializes_eight_workflow_nodes(db):
    conversation = create_conversation_for_batch(
        "SUB-20260604-001",
        "新型冠状病毒 2019-nCoV 核酸检测试剂盒",
    )

    labels = [node["label"] for node in conversation.node_results]
    assert len(labels) == 8
    assert labels == [
        "资料包导入",
        "目录汇总",
        "法规完整性检查",
        "字段抽取",
        "一致性核查",
        "风险预警",
        "Word 回填导出",
        "飞书通知",
    ]


def test_chat_page_shows_upload_entry_and_dynamic_context_cards(client, db):
    batch, conversation = _create_conversation_with_batch()
    conversation.node_results = [
        {"label": "资料包导入", "status": "已完成"},
        {"label": "目录汇总", "status": "已完成"},
        {"label": "法规完整性检查", "status": "已完成"},
        {"label": "字段抽取", "status": "已完成"},
        {"label": "一致性核查", "status": "待复核"},
        {"label": "风险预警", "status": "已阻断"},
        {"label": "Word 回填导出", "status": "待处理"},
        {"label": "飞书通知", "status": "待处理"},
    ]
    conversation.save(update_fields=["node_results"])
    UploadedDocument.objects.create(
        batch=batch,
        scenario_id="document_review",
        original_name="说明书.md",
        file_type="md",
        size=1,
        status=UploadedDocument.STATUS_INDEXED,
    )

    response = client.get(reverse("chat:detail", args=[conversation.conversation_id]))

    content = response.content.decode("utf-8")
    assert response.status_code == 200
    assert "继续上传资料" in content
    assert "最高风险等级" in content
    assert "是否允许正式导出" in content
    assert "通知状态" in content
    assert "飞书通知 / 待处理" in content


def test_chat_page_shows_top_context_and_recommended_prompts(client, db):
    batch, conversation = _create_conversation_with_batch()
    conversation.task_status = "processing"
    conversation.node_results = [
        {"label": "资料包导入", "status": "已完成"},
        {"label": "目录汇总", "status": "已完成"},
        {"label": "法规完整性检查", "status": "已完成"},
        {"label": "字段抽取", "status": "已完成"},
        {"label": "一致性核查", "status": "待复核"},
        {"label": "风险预警", "status": "已阻断"},
        {"label": "Word 回填导出", "status": "待复核"},
        {"label": "飞书通知", "status": "待处理"},
    ]
    conversation.save(update_fields=["task_status", "node_results", "updated_at"])

    response = client.get(reverse("chat:detail", args=[conversation.conversation_id]))

    content = response.content.decode("utf-8")
    assert response.status_code == 200
    assert "顶部对话上下文" in content
    assert "当前流程类型" in content
    assert "registration" in content
    assert "当前审核阶段" in content
    assert "processing" in content
    assert "当前最高风险等级" in content
    assert "推荐提问模板" in content
    assert "请汇总当前资料包的章节点、页数和目录覆盖情况" in content
    assert "请给出当前资料包的高风险项、责任人和整改建议" in content


def test_chat_page_shows_overview_card_from_conversation_summary(client, db):
    batch, conversation = _create_conversation_with_batch()
    conversation.latest_summary = {
        "structured_output": {
            "output_type": "registration_overview_report",
            "batch_id": batch.batch_id,
            "product_name": batch.product_name,
            "file_count": 2,
            "total_page_count": 12,
            "chapter_summary": [
                {"chapter_code": "CH1", "document_count": 2},
            ],
            "documents": [
                {
                    "original_name": "说明书.md",
                    "chapter_code": "CH1",
                    "page_count": 12,
                    "document_role": "product_manual",
                }
            ],
            "warnings": ["Word 页数待复核"],
        }
    }
    conversation.save(update_fields=["latest_summary", "updated_at"])

    response = client.get(reverse("chat:detail", args=[conversation.conversation_id]))

    content = response.content.decode("utf-8")
    assert response.status_code == 200
    assert "目录汇总能力卡" in content
    assert "资料文件数" in content
    assert "CH1 / 2 份" in content
    assert "Word 页数待复核" in content


def test_chat_page_shows_consistency_card_from_conversation_summary(client, db):
    batch, conversation = _create_conversation_with_batch()
    conversation.latest_summary = {
        "structured_output": {
            "output_type": "registration_consistency_report",
            "summary": "检测到跨文档字段冲突。",
            "conflict_items": [
                {"field_name": "产品名称", "issue": "申请表与说明书不一致"},
            ],
            "mixed_document_risks": ["疑似混入其他产品资料"],
            "risk_level": "high",
        }
    }
    conversation.save(update_fields=["latest_summary", "updated_at"])

    response = client.get(reverse("chat:detail", args=[conversation.conversation_id]))

    content = response.content.decode("utf-8")
    assert response.status_code == 200
    assert "一致性核查能力卡" in content
    assert "申请表与说明书不一致" in content
    assert "疑似混入其他产品资料" in content
    assert "high" in content


def test_chat_page_shows_completeness_card_from_conversation_summary(client, db):
    batch, conversation = _create_conversation_with_batch()
    conversation.latest_summary = {
        "structured_output": {
            "output_type": "registration_completeness_report",
            "summary": "当前资料包仍缺少关键必交项。",
            "risk_level": "high",
            "missing_items": [
                {
                    "chapter_code": "CH2",
                    "document_name": "产品技术要求",
                    "reason": "当前资料包未发现对应文件",
                }
            ],
            "misplaced_items": [
                {
                    "chapter_code": "CH4",
                    "document_name": "研究资料",
                    "current_location": "CH6",
                }
            ],
        }
    }
    conversation.save(update_fields=["latest_summary", "updated_at"])

    response = client.get(reverse("chat:detail", args=[conversation.conversation_id]))

    content = response.content.decode("utf-8")
    assert response.status_code == 200
    assert "完整性检查能力卡" in content
    assert "当前资料包仍缺少关键必交项" in content
    assert "产品技术要求" in content
    assert "CH2" in content
    assert "当前资料包未发现对应文件" in content
    assert "研究资料" in content
    assert "当前归类：CH6" in content


def test_chat_page_shows_field_extraction_card_from_conversation_summary(client, db):
    batch, conversation = _create_conversation_with_batch()
    conversation.latest_summary = {
        "structured_output": {
            "output_type": "registration_field_extraction_report",
            "summary": "已完成核心注册字段抽取。",
            "field_items": [
                {
                    "field_name": "产品名称",
                    "field_value": batch.product_name,
                    "source_document": "申请表.docx",
                },
                {
                    "field_name": "规格型号",
                    "field_value": "48T/盒",
                    "source_document": "产品列表.xlsx",
                },
            ],
            "low_confidence_items": [
                {
                    "field_name": "储存条件",
                    "field_value": "2-8℃",
                    "source_document": "说明书.docx",
                }
            ],
        }
    }
    conversation.save(update_fields=["latest_summary", "updated_at"])

    response = client.get(reverse("chat:detail", args=[conversation.conversation_id]))

    content = response.content.decode("utf-8")
    assert response.status_code == 200
    assert "字段抽取能力卡" in content
    assert "已完成核心注册字段抽取" in content
    assert "产品名称" in content
    assert "48T/盒" in content
    assert "来源：申请表.docx" in content
    assert "储存条件" in content
    assert "来源：说明书.docx" in content


def test_chat_page_blocks_formal_export_when_word_export_node_is_blocked(client, db):
    batch, conversation = _create_conversation_with_batch()
    conversation.node_results = [
        {"label": "资料包导入", "status": "已完成"},
        {"label": "目录汇总", "status": "已完成"},
        {"label": "法规完整性检查", "status": "已完成"},
        {"label": "字段抽取", "status": "已完成"},
        {"label": "一致性核查", "status": "已完成"},
        {"label": "风险预警", "status": "已完成"},
        {"label": "Word 回填导出", "status": "已阻断"},
        {"label": "飞书通知", "status": "待处理"},
    ]
    conversation.latest_summary = {
        "structured_output": {
            "download_url": "/downloads/export.docx",
        }
    }
    conversation.save(update_fields=["node_results", "latest_summary", "updated_at"])

    response = client.get(reverse("chat:detail", args=[conversation.conversation_id]))

    content = response.content.decode("utf-8")
    assert response.status_code == 200
    assert "是否允许正式导出" in content
    assert ">否<" in content
    assert "/downloads/export.docx" in content


def test_chat_page_uses_structured_formal_export_flag_when_node_status_is_completed(client, db):
    batch, conversation = _create_conversation_with_batch()
    conversation.node_results = [
        {"label": "资料包导入", "status": "已完成"},
        {"label": "目录汇总", "status": "已完成"},
        {"label": "法规完整性检查", "status": "已完成"},
        {"label": "字段抽取", "status": "已完成"},
        {"label": "一致性核查", "status": "已完成"},
        {"label": "风险预警", "status": "已完成"},
        {"label": "Word 回填导出", "status": "已完成"},
        {"label": "飞书通知", "status": "待处理"},
    ]
    conversation.latest_summary = {
        "structured_output": {
            "can_export_formally": False,
            "download_url": "/downloads/review-only.docx",
        }
    }
    conversation.save(update_fields=["node_results", "latest_summary", "updated_at"])

    response = client.get(reverse("chat:detail", args=[conversation.conversation_id]))

    content = response.content.decode("utf-8")
    assert response.status_code == 200
    assert "是否允许正式导出" in content
    assert ">否<" in content
    assert "/downloads/review-only.docx" in content


def test_chat_page_shows_word_export_field_table_and_governance_entries(client, db):
    batch, conversation = _create_conversation_with_batch()
    conversation.node_results = [
        {"label": "资料包导入", "status": "已完成"},
        {"label": "目录汇总", "status": "已完成"},
        {"label": "法规完整性检查", "status": "已完成"},
        {"label": "字段抽取", "status": "已完成"},
        {"label": "一致性核查", "status": "已完成"},
        {"label": "风险预警", "status": "已阻断"},
        {"label": "Word 回填导出", "status": "待复核"},
        {"label": "飞书通知", "status": "待处理"},
    ]
    conversation.latest_summary = {
        "structured_output": {
            "output_type": "registration_word_export_report",
            "template_name": "注册证导出模板",
            "template_version": "V1.0",
            "export_status": "draft_only",
            "filled_fields": [
                {
                    "placeholder": "{{ product_name }}",
                    "field_name": "产品名称",
                    "field_value": "新型冠状病毒 2019-nCoV 核酸检测试剂盒",
                    "source": "资料包主信息",
                    "fill_status": "filled",
                    "required": True,
                }
            ],
            "blocked_fields": [
                {
                    "field_name": "产品名称跨文档不一致",
                    "block_reason": "待人工复核",
                    "risk_source": "registration_risk_report",
                }
            ],
            "download_url": "/media/exports/20260604/SUB-20260604-001-draft.docx",
        }
    }
    conversation.save(update_fields=["node_results", "latest_summary", "updated_at"])

    response = client.get(reverse("chat:detail", args=[conversation.conversation_id]))

    content = response.content.decode("utf-8")
    assert response.status_code == 200
    assert "Word 导出能力卡" in content
    assert "回填字段表" in content
    assert "产品名称" in content
    assert "资料包主信息" in content
    assert "拦截项区" in content
    assert "产品名称跨文档不一致" in content
    assert "维护 Word 模板" in content
    assert "维护字段映射" in content


def test_chat_page_shows_risk_and_notification_cards_from_conversation_summary(client, db):
    batch, conversation = _create_conversation_with_batch()
    conversation.node_results = [
        {"label": "资料包导入", "status": "已完成"},
        {"label": "目录汇总", "status": "已完成"},
        {"label": "法规完整性检查", "status": "已完成"},
        {"label": "字段抽取", "status": "已完成"},
        {"label": "一致性核查", "status": "待复核"},
        {"label": "风险预警", "status": "已阻断"},
        {"label": "Word 回填导出", "status": "待复核"},
        {"label": "飞书通知", "status": "已完成"},
    ]
    conversation.latest_summary = {
        "structured_output": {
            "output_type": "registration_risk_report",
            "summary": "存在高风险项，需人工复核。",
            "highest_risk_level": "high",
            "pass_status": "blocked",
            "manual_review_items": ["CH1.11.5 沟通记录缺失"],
            "risk_items": [{"title": "产品名称跨文档不一致", "risk_level": "high"}],
            "owner_roles": [
                {
                    "owner_role": "注册资料负责人",
                    "owner_name": "张三",
                    "department": "注册事务部",
                    "chapter_scope": "CH1",
                    "risk_scope": "字段冲突",
                    "feishu_user_id": "ou_demo_1",
                    "feishu_open_id": "on_demo_1",
                    "feishu_name": "张三",
                    "notify_enabled": True,
                }
            ],
            "notify_reason": "task_completed",
            "mentioned_users": ["ou_demo_1"],
            "message_status": "sent",
            "web_detail_url": "https://example.com/audit/1",
        }
    }
    conversation.save(update_fields=["node_results", "latest_summary", "updated_at"])

    response = client.get(reverse("chat:detail", args=[conversation.conversation_id]))

    content = response.content.decode("utf-8")
    assert response.status_code == 200
    assert "风险预警能力卡" in content
    assert "总风险等级" in content
    assert "high" in content
    assert "注册资料负责人" in content
    assert "注册事务部" in content
    assert "字段冲突" in content
    assert "ou_demo_1" in content
    assert "on_demo_1" in content
    assert "张三" in content
    assert "True" in content
    assert "CH1.11.5 沟通记录缺失" in content
    assert "飞书通知能力卡" in content
    assert "task_completed" in content
    assert "sent" in content


def test_chat_upload_keeps_existing_conversation_binding_and_adds_documents(client, db):
    batch, conversation = _create_conversation_with_batch()
    existing_document = UploadedDocument.objects.create(
        batch=batch,
        scenario_id="document_review",
        original_name="原说明书.md",
        file_type="md",
        size=1,
        status=UploadedDocument.STATUS_INDEXED,
    )
    existing_count = UploadedDocument.objects.filter(batch=batch).count()
    upload_file = SimpleUploadedFile(
        "新增补充资料.txt",
        "产品名称：新型冠状病毒 2019-nCoV 核酸检测试剂盒".encode("utf-8"),
        content_type="text/plain",
    )

    response = client.post(
        reverse("chat:upload-documents", args=[conversation.conversation_id]),
        {"files": [upload_file]},
        follow=True,
    )

    content = response.content.decode("utf-8")
    batch.refresh_from_db()
    conversation.refresh_from_db()
    assert response.status_code == 200
    assert SubmissionBatch.objects.count() == 1
    assert Conversation.objects.count() == 1
    assert conversation.conversation_id == "conv-001"
    assert batch.conversation_id == conversation.conversation_id
    assert UploadedDocument.objects.filter(batch=batch).count() == existing_count + 1
    assert UploadedDocument.objects.filter(batch=batch, original_name="新增补充资料.txt").exists()
    assert "新增补充资料.txt" in content
    assert "已补充到当前资料包" in content


@override_settings(MEDIA_URL="/media/")
def test_generate_registration_export_creates_real_docx_file(db, tmp_path, settings):
    from apps.chat.export_service import generate_registration_export

    settings.MEDIA_ROOT = tmp_path / "uploads"
    batch, conversation = _create_conversation_with_batch()
    risk_summary = {
        "output_type": "registration_risk_report",
        "summary": "存在高风险项，正式版导出应被阻断，但允许生成草稿。",
        "highest_risk_level": "high",
        "pass_status": "blocked",
        "manual_review_items": ["CH1.11.5 沟通记录待补齐"],
        "risk_items": [
            {"title": "产品名称跨文档不一致", "risk_level": "high"},
        ],
    }

    report = generate_registration_export(
        batch=batch,
        conversation=conversation,
        upstream_summary=risk_summary,
    )

    export_path = settings.MEDIA_ROOT / report["output_file"]["relative_path"]
    assert report["output_type"] == "registration_word_export_report"
    assert report["can_export_formally"] is False
    assert report["export_status"] == "draft_only"
    assert report["filled_fields"][0]["field_name"] == "产品名称"
    assert report["filled_fields"][0]["fill_status"] == "filled"
    assert report["blocked_fields"][0]["block_reason"] == "待人工复核"
    assert report["output_file"]["output_version"] == "draft"
    assert report["output_file"]["generated_at"]
    assert report["download_url"].startswith("/media/exports/")
    assert export_path.exists()
    with zipfile.ZipFile(export_path) as archive:
        document_xml = archive.read("word/document.xml").decode("utf-8")
    assert batch.product_name in document_xml
    assert "产品名称跨文档不一致" in document_xml


@override_settings(MEDIA_URL="/media/")
def test_chat_export_word_route_persists_real_download_link(client, db, tmp_path, settings):
    settings.MEDIA_ROOT = tmp_path / "uploads"
    batch, conversation = _create_conversation_with_batch()
    conversation.node_results = [
        {"label": "资料包导入", "status": "已完成"},
        {"label": "目录汇总", "status": "已完成"},
        {"label": "法规完整性检查", "status": "已完成"},
        {"label": "字段抽取", "status": "已完成"},
        {"label": "一致性核查", "status": "已完成"},
        {"label": "风险预警", "status": "已阻断"},
        {"label": "Word 回填导出", "status": "待处理"},
        {"label": "飞书通知", "status": "待处理"},
    ]
    conversation.latest_summary = {
        "structured_output": {
            "output_type": "registration_risk_report",
            "summary": "存在高风险项，允许草稿导出。",
            "highest_risk_level": "high",
            "pass_status": "blocked",
            "manual_review_items": ["CH1.11.5 沟通记录待补齐"],
            "risk_items": [{"title": "产品名称跨文档不一致", "risk_level": "high"}],
        }
    }
    conversation.save(update_fields=["node_results", "latest_summary", "updated_at"])

    response = client.post(
        reverse("chat:export-word", args=[conversation.conversation_id]),
        follow=True,
    )

    content = response.content.decode("utf-8")
    conversation.refresh_from_db()
    export_report = conversation.latest_summary["structured_output"]
    export_path = settings.MEDIA_ROOT / export_report["output_file"]["relative_path"]
    assert response.status_code == 200
    assert export_report["output_type"] == "registration_word_export_report"
    assert export_report["download_url"].startswith("/media/exports/")
    assert export_path.exists()
    assert "下载导出文件" in content
    assert export_report["download_url"] in content
    assert AgentAuditLog.objects.filter(conversation_id=conversation.conversation_id).count() == 1
    assert ExportedDocument.objects.filter(batch=batch, conversation_id=conversation.conversation_id).count() == 1
