import base64
import binascii
import io
import zipfile

import pytest
from fastapi import HTTPException

from server.deepsearch.common.exception.exceptions import (
    ReportConvertExecutionException,
    ReportConvertValidationException,
)
from server.schemas.report import ReportConvertReq, ReportFormat
from server.deepsearch.core.manager.report_manager.report_processor import ReportHtml, ReportWord

def test_report_convert_returns_zip_base64(monkeypatch):
    """Validate that report_convert returns a ZIP bundle encoded as base64.

    Args:
        monkeypatch: pytest monkeypatch fixture.

    Returns:
        None.
    """
    from server.deepsearch.core.manager import report as report_mgr
    from server.schemas import report as report_schema

    class _DummyProcessor:
        """Provide a minimal processor stub for manager tests."""

        def convert_from_final_result_to_bundle_base64(self, final_result):
            """Return a dummy ZIP payload for manager contract testing.

            Args:
                final_result: 输入的 final_result 字典。

            Returns:
                base64 编码后的伪 ZIP 二进制内容。
            """
            assert final_result["response_content"] == "正文"
            return base64.b64encode(b"PK\x03\x04dummy").decode("utf-8")

    req = ReportConvertReq(
        final_result={
            "response_content": "正文",
            "infer_messages": [],
            "chart_messages": [],
            "warning_info": "",
            "exception_info": "",
        },
        convert_type=ReportFormat.HTML,
    )
    monkeypatch.setattr(
        report_schema.ReportFormat,
        "get_processor",
        lambda self: _DummyProcessor(),
    )

    res = report_mgr.report_convert(req)

    assert base64.b64decode(res.convert_content).startswith(b"PK")


def test_report_convert_raises_validation_exception(monkeypatch):
    """Validate that manager raises report convert validation exceptions.

    Args:
        monkeypatch: pytest monkeypatch fixture.

    Returns:
        None.
    """
    from server.deepsearch.core.manager import report as report_mgr
    from server.schemas import report as report_schema

    class _DummyProcessor:
        """Provide a processor stub that triggers base64 validation failures."""

        def convert_from_final_result_to_bundle_base64(self, final_result):
            """Raise the same exception as an invalid base64 decode path.

            Args:
                final_result: 输入的 final_result 字典。

            Raises:
                binascii.Error: 用于模拟非法 base64 内容。
            """
            raise binascii.Error("bad base64")

    req = ReportConvertReq(
        final_result={
            "response_content": "正文",
            "infer_messages": [],
            "chart_messages": [],
            "warning_info": "",
            "exception_info": "",
        },
        convert_type=ReportFormat.HTML,
    )
    monkeypatch.setattr(
        report_schema.ReportFormat,
        "get_processor",
        lambda self: _DummyProcessor(),
    )

    with pytest.raises(ReportConvertValidationException):
        report_mgr.report_convert(req)


@pytest.mark.asyncio
async def test_report_router_maps_convert_exception_to_http(monkeypatch):
    """Validate that the router maps report convert exceptions to HTTP errors.

    Args:
        monkeypatch: pytest monkeypatch fixture.

    Returns:
        None.
    """
    from server.routers import report as report_router

    monkeypatch.setattr(
        report_router.mgr,
        "report_convert",
        lambda req: (_ for _ in ()).throw(ReportConvertExecutionException("convert failed")),
    )

    request = {
        "final_result": {
            "response_content": "正文",
            "infer_messages": [],
            "chart_messages": [],
            "warning_info": "",
            "exception_info": "",
        },
        "convert_type": "html",
    }

    with pytest.raises(HTTPException) as exc_info:
        await report_router.report_convert(request)

    assert exc_info.value.status_code == 500


def test_report_html_processor_returns_bundle_zip_base64():
    """Validate that ReportHtml packages the converted artifact as a ZIP bundle.

    Returns:
        None.
    """
    final_result = {
        "response_content": "正文[结论](#inference:0)",
        "infer_messages": [
            {
                "id": 0,
                "html_base64": base64.b64encode(b"<html>infer</html>").decode("utf-8"),
            }
        ],
        "chart_messages": [],
        "warning_info": "",
        "exception_info": "",
    }

    bundle_b64 = ReportHtml().convert_from_final_result_to_bundle_base64(final_result)
    data = base64.b64decode(bundle_b64)
    with zipfile.ZipFile(io.BytesIO(data)) as zip_file:
        names = set(zip_file.namelist())

    assert "report_bundle/report.md" in names
    assert "report_bundle/report.html" in names
    assert "report_bundle/infer/inference_0.html" in names

def test_report_docx_processor_returns_bundle_zip_base64(monkeypatch):
    """Validate that ReportWord packages DOCX output inside the ZIP bundle.

    Args:
        monkeypatch: pytest monkeypatch fixture.

    Returns:
        None.
    """
    final_result = {
        "response_content": "(#insertChart:chart_0)",
        "infer_messages": [],
        "chart_messages": [
            {
                "chart_id": "chart_0",
                "chart_title": "图表标题",
                "base64": base64.b64encode(b"fakepng").decode("utf-8"),
            }
        ],
        "warning_info": "",
        "exception_info": "",
    }

    monkeypatch.setattr(
        "server.deepsearch.core.manager.report_manager.docx_offline.ensure_pandoc",
        lambda: None,
    )
    monkeypatch.setattr(
        "server.deepsearch.core.manager.report_manager.docx_offline.normalize_docx_fonts",
        lambda *_args, **_kwargs: None,
    )
    monkeypatch.setattr(
        "server.deepsearch.core.manager.report_manager.docx_offline.normalize_docx_tables",
        lambda *_args, **_kwargs: None,
    )

    def _fake_convert_file(*args, **kwargs):
        outputfile = kwargs["outputfile"]
        with open(outputfile, "wb") as file:
            file.write(b"PK\x03\x04docx")

    monkeypatch.setattr("pypandoc.convert_file", _fake_convert_file)

    bundle_b64 = ReportWord().convert_from_final_result_to_bundle_base64(final_result)
    with zipfile.ZipFile(io.BytesIO(base64.b64decode(bundle_b64))) as zip_file:
        names = set(zip_file.namelist())

    assert "report_bundle/report.docx" in names
    assert "report_bundle/charts/chart_0.png" in names