from __future__ import annotations

import io
from types import SimpleNamespace

import pytest

from yuxi.services import chat_service as chat_svc
from yuxi.services import conversation_service as svc


class _DummyUpload:
    def __init__(self, *, filename: str, content_type: str | None, data: bytes):
        self.filename = filename
        self.content_type = content_type
        self._buffer = io.BytesIO(data)

    async def read(self, size: int = -1) -> bytes:
        return self._buffer.read(size)

    async def seek(self, offset: int) -> int:
        return self._buffer.seek(offset)


def test_build_state_files_only_parsed_and_with_content():
    attachments = [
        {
            "status": "parsed",
            "file_path": "/attachments/a.md",
            "markdown": "line1\nline2",
            "uploaded_at": "2026-02-20T00:00:00+00:00",
        },
        {
            "status": "pending",
            "file_path": "/attachments/b.md",
            "markdown": "ignored",
        },
        {
            "status": "parsed",
            "file_path": "/attachments/c.md",
            "markdown": "",
        },
    ]

    files = chat_svc._build_state_files(attachments)

    assert list(files.keys()) == ["/attachments/a.md"]
    assert files["/attachments/a.md"]["content"] == ["line1", "line2"]
    assert files["/attachments/a.md"]["created_at"] == "2026-02-20T00:00:00+00:00"


@pytest.mark.asyncio
async def test_sync_thread_attachment_state_updates_graph(monkeypatch: pytest.MonkeyPatch):
    captured: dict = {}

    class FakeGraph:
        async def aupdate_state(self, *, config, values):
            captured["write_config"] = config
            captured["write_values"] = values

    class FakeAgent:
        async def get_graph(self):
            return FakeGraph()

    monkeypatch.setattr(svc.agent_manager, "get_agent", lambda _agent_id: FakeAgent())

    attachments = [
        {
            "status": "parsed",
            "path": "/home/gem/user-data/uploads/attachments/resume.md",
            "file_name": "resume.md",
            "uploaded_at": "2026-02-20T00:00:00+00:00",
        }
    ]
    await svc._sync_thread_upload_state(
        thread_id="thread-1",
        user_id="u1",
        agent_id="ChatbotAgent",
        attachments=attachments,
    )

    assert captured["write_config"] == {"configurable": {"thread_id": "thread-1", "user_id": "u1"}}
    assert captured["write_values"] == {"uploads": svc._build_state_uploads(attachments)}


@pytest.mark.asyncio
async def test_sync_thread_attachment_state_skips_when_agent_missing(monkeypatch: pytest.MonkeyPatch):
    warnings: list[str] = []
    fake_logger = SimpleNamespace(
        warning=lambda message: warnings.append(message),
    )

    monkeypatch.setattr(svc, "logger", fake_logger)
    monkeypatch.setattr(svc.agent_manager, "get_agent", lambda _agent_id: None)

    await svc._sync_thread_upload_state(
        thread_id="thread-1",
        user_id="u1",
        agent_id="MissingAgent",
        attachments=[],
    )

    assert any("agent not found" in msg for msg in warnings)


@pytest.mark.asyncio
async def test_convert_upload_to_markdown_returns_conversion_result(
    tmp_path,
    monkeypatch: pytest.MonkeyPatch,
):
    async def _fake_aparse(source: str, params=None) -> str:
        return "converted markdown"

    monkeypatch.setattr(svc, "_ensure_workdir", lambda: tmp_path)
    monkeypatch.setattr(svc.Parser, "aparse", _fake_aparse)

    payload = b"hello attachment"
    upload = _DummyUpload(filename="note.txt", content_type="text/plain", data=payload)

    result = await svc._convert_upload_to_markdown(upload)

    assert result.file_name == "note.txt"
    assert result.file_type == "text/plain"
    assert result.file_size == len(payload)
    assert result.markdown == "converted markdown"
    assert result.truncated is False


@pytest.mark.asyncio
async def test_convert_upload_to_markdown_truncates_content(
    tmp_path,
    monkeypatch: pytest.MonkeyPatch,
):
    async def _fake_aparse(source: str, params=None) -> str:
        return "x" * (svc.MAX_ATTACHMENT_MARKDOWN_CHARS + 200)

    monkeypatch.setattr(svc, "_ensure_workdir", lambda: tmp_path)
    monkeypatch.setattr(svc.Parser, "aparse", _fake_aparse)

    upload = _DummyUpload(filename="note.md", content_type="text/markdown", data=b"hello")

    result = await svc._convert_upload_to_markdown(upload)

    assert result.truncated is True
    assert f"超出 {svc.MAX_ATTACHMENT_MARKDOWN_CHARS} 字符限制" in result.markdown


@pytest.mark.asyncio
async def test_convert_upload_to_markdown_rejects_unsupported_extension(monkeypatch: pytest.MonkeyPatch):
    monkeypatch.setattr(svc, "ATTACHMENT_ALLOWED_EXTENSIONS", (".md",))
    upload = _DummyUpload(filename="note.pdf", content_type="application/pdf", data=b"pdf")

    with pytest.raises(ValueError, match="不支持的文件类型"):
        await svc._convert_upload_to_markdown(upload)
