import asyncio
import io
import zipfile
from types import SimpleNamespace

import pytest

from beaver.interfaces.web.app import _create_skill_upload_draft
from beaver.services.skillhub_service import SkillHubService
from beaver.skills.drafts import DraftService
from beaver.skills.specs import SkillSpecStore
from beaver.tools.mcp.wrapper import MCPToolWrapper


class FakeSkillHubService(SkillHubService):
    async def _get_json(self, path, *, params=None):
        if path == "/skills":
            return {
                "data": {
                    "items": [
                        {
                            "slug": "multi-search-engine",
                            "displayName": "multi-search-engine",
                            "summary": "search",
                            "namespace": "global",
                            "downloadCount": 1,
                            "starCount": 0,
                            "publishedVersion": {"version": "20260413.065325"},
                        }
                    ],
                    "total": 1,
                    "page": 0,
                    "size": 12,
                }
            }
        if path == "/skills/global/multi-search-engine":
            return {
                "data": {
                    "slug": "multi-search-engine",
                    "displayName": "multi-search-engine",
                    "summary": "search",
                    "namespace": "global",
                    "downloadCount": 1,
                    "starCount": 0,
                    "publishedVersion": {"version": "20260413.065325"},
                }
            }
        if path == "/skills/global/multi-search-engine/versions/20260413.065325":
            return {"data": {"version": "20260413.065325"}}
        if path == "/skills/global/multi-search-engine/versions/20260413.065325/files":
            return {"data": [{"filePath": "SKILL.md", "fileSize": 93}, {"filePath": "references/a.txt", "fileSize": 2}]}
        raise AssertionError(path)

    async def _get_text(self, path, *, params):
        if params["path"] == "SKILL.md":
            return "---\nname: multi-search-engine\ndescription: Multi search\ntools:\n  - web_search\n---\nUse search.\n"
        return "ok"


def test_skillhub_search_detail_do_not_install_until_post_install(tmp_path):
    store = SkillSpecStore(tmp_path)
    service = FakeSkillHubService(store)

    search = asyncio.run(service.search(q="multi-search-engine"))
    detail = asyncio.run(service.detail("global", "multi-search-engine"))
    assert search["items"][0]["installed"] is False
    assert detail["installed"] is False
    assert store.get_skill_spec("multi-search-engine") is None

    install = asyncio.run(service.install("global", "multi-search-engine"))
    assert install["ok"] is True
    assert store.get_skill_spec("multi-search-engine") is not None
    assert (tmp_path / "skills" / "multi-search-engine" / "versions" / install["version"] / "references" / "a.txt").read_text() == "ok"


def test_upload_skill_zip_rejects_path_traversal(tmp_path):
    store = SkillSpecStore(tmp_path)
    loaded = SimpleNamespace(skill_spec_store=store, draft_service=DraftService(store))
    buffer = io.BytesIO()
    with zipfile.ZipFile(buffer, "w") as archive:
        archive.writestr("skill/SKILL.md", "---\nname: skill\n---\nBody\n")
        archive.writestr("skill/../evil.txt", "x")

    with pytest.raises(ValueError, match="Unsafe archive entry"):
        _create_skill_upload_draft(loaded, "skill.zip", buffer.getvalue())


def test_upload_skill_zip_keeps_supporting_files_on_draft(tmp_path):
    store = SkillSpecStore(tmp_path)
    loaded = SimpleNamespace(skill_spec_store=store, draft_service=DraftService(store))
    buffer = io.BytesIO()
    with zipfile.ZipFile(buffer, "w") as archive:
        archive.writestr("skill/SKILL.md", "---\nname: skill\n---\nBody\n")
        archive.writestr("skill/references/a.txt", "context")

    draft = _create_skill_upload_draft(loaded, "skill.zip", buffer.getvalue())
    upload_dir = draft["evidence_refs"][0]["supporting_upload_dir"]
    assert (tmp_path / "skills" / "skill" / "draft_uploads" / draft["draft_id"] / "references" / "a.txt").read_text() == "context"
    assert upload_dir.endswith(draft["draft_id"])


def test_mcp_wrapper_metadata_preserves_server_id_with_underscores():
    tool_def = SimpleNamespace(name="auth_status", description="Auth", inputSchema={"type": "object", "properties": {}})

    async def call_tool(_name, _arguments):
        return SimpleNamespace(content=[], structuredContent={"ok": True})

    wrapper = MCPToolWrapper(
        "outlook_mcp",
        tool_def,
        call_tool,
        kind="online",
        category="outlook",
        display_name="Outlook",
    )

    assert wrapper.spec.name == "mcp_outlook_mcp_auth_status"
    assert wrapper.spec.metadata["server_id"] == "outlook_mcp"
    assert wrapper.spec.metadata["original_tool_name"] == "auth_status"
