import importlib.util
import sys
from pathlib import Path

import pytest


MODULE_PATH = Path(__file__).resolve().parents[2] / "scripts" / "install_all_plugins.py"
SPEC = importlib.util.spec_from_file_location("install_all_plugins", MODULE_PATH)
install_all_plugins = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
sys.modules[SPEC.name] = install_all_plugins
SPEC.loader.exec_module(install_all_plugins)


PLUGIN_HEADER = '''"""
title: Example Plugin
version: 1.0.0
openwebui_id: 12345678-1234-1234-1234-123456789abc
description: Example description.
"""
'''


def write_plugin(path: Path, header: str) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(header + "\nclass Action:\n    pass\n", encoding="utf-8")


def test_should_skip_plugin_file_filters_localized_and_helper_names():
    assert (
        install_all_plugins.should_skip_plugin_file(Path("flash_card_cn.py"))
        == "localized _cn file"
    )
    assert (
        install_all_plugins.should_skip_plugin_file(Path("verify_generation.py"))
        == "test or helper script"
    )
    assert (
        install_all_plugins.should_skip_plugin_file(Path("测试.py"))
        == "non-ascii filename"
    )
    assert install_all_plugins.should_skip_plugin_file(Path("flash_card.py")) is None


def test_build_function_id_prefers_id_then_title_then_filename():
    from_id = install_all_plugins.build_function_id(
        Path("dummy.py"), {"id": "Async Context Compression"}
    )
    from_title = install_all_plugins.build_function_id(
        Path("dummy.py"), {"title": "GitHub Copilot Official SDK Pipe"}
    )
    from_file = install_all_plugins.build_function_id(Path("dummy_plugin.py"), {})

    assert from_id == "async_context_compression"
    assert from_title == "github_copilot_official_sdk_pipe"
    assert from_file == "dummy_plugin"


def test_build_payload_uses_native_tool_shape_for_tools():
    candidate = install_all_plugins.PluginCandidate(
        plugin_type="tool",
        file_path=Path("plugins/tools/demo/demo_tool.py"),
        metadata={
            "title": "Demo Tool",
            "description": "Demo tool description",
            "openwebui_id": "12345678-1234-1234-1234-123456789abc",
        },
        content="class Tools:\n    pass\n",
        function_id="demo_tool",
    )

    payload = install_all_plugins.build_payload(candidate)

    assert payload == {
        "id": "demo_tool",
        "name": "Demo Tool",
        "meta": {
            "description": "Demo tool description",
            "manifest": {},
        },
        "content": "class Tools:\n    pass\n",
        "access_grants": [],
    }


def test_build_api_urls_uses_tool_endpoints_for_tools():
    candidate = install_all_plugins.PluginCandidate(
        plugin_type="tool",
        file_path=Path("plugins/tools/demo/demo_tool.py"),
        metadata={"title": "Demo Tool"},
        content="class Tools:\n    pass\n",
        function_id="demo_tool",
    )

    update_url, create_url = install_all_plugins.build_api_urls(
        "http://localhost:3000", candidate
    )

    assert update_url == "http://localhost:3000/api/v1/tools/id/demo_tool/update"
    assert create_url == "http://localhost:3000/api/v1/tools/create"


def test_discover_plugins_only_returns_supported_openwebui_plugins(
    tmp_path, monkeypatch
):
    actions_dir = tmp_path / "plugins" / "actions"
    filters_dir = tmp_path / "plugins" / "filters"
    pipes_dir = tmp_path / "plugins" / "pipes"
    tools_dir = tmp_path / "plugins" / "tools"

    write_plugin(actions_dir / "flash-card" / "flash_card.py", PLUGIN_HEADER)
    write_plugin(actions_dir / "flash-card" / "flash_card_cn.py", PLUGIN_HEADER)
    write_plugin(actions_dir / "infographic" / "verify_generation.py", PLUGIN_HEADER)
    write_plugin(
        filters_dir / "missing-id" / "missing_id.py", '"""\ntitle: Missing ID\n"""\n'
    )
    write_plugin(pipes_dir / "sdk" / "github_copilot_sdk.py", PLUGIN_HEADER)
    write_plugin(tools_dir / "skills" / "openwebui_skills_manager.py", PLUGIN_HEADER)

    monkeypatch.setattr(
        install_all_plugins,
        "PLUGIN_TYPE_DIRS",
        {
            "action": actions_dir,
            "filter": filters_dir,
            "pipe": pipes_dir,
            "tool": tools_dir,
        },
    )
    monkeypatch.setattr(install_all_plugins, "REPO_ROOT", tmp_path)

    candidates, skipped = install_all_plugins.discover_plugins(
        ["action", "filter", "pipe", "tool"]
    )

    candidate_names = [candidate.file_path.name for candidate in candidates]
    skipped_reasons = {path.name: reason for path, reason in skipped}

    assert candidate_names == [
        "flash_card.py",
        "github_copilot_sdk.py",
        "openwebui_skills_manager.py",
    ]
    assert skipped_reasons["missing_id.py"] == "missing openwebui_id"
    assert skipped_reasons["flash_card_cn.py"] == "localized _cn file"
    assert skipped_reasons["verify_generation.py"] == "test or helper script"


@pytest.mark.parametrize(
    ("header", "expected_reason"),
    [
        ('"""\ntitle: Missing ID\n"""\n', "missing openwebui_id"),
        ("class Action:\n    pass\n", "missing plugin header"),
    ],
)
def test_discover_plugins_reports_missing_metadata(
    tmp_path, monkeypatch, header, expected_reason
):
    action_dir = tmp_path / "plugins" / "actions"
    plugin_file = action_dir / "demo" / "demo.py"
    write_plugin(plugin_file, header)

    monkeypatch.setattr(
        install_all_plugins,
        "PLUGIN_TYPE_DIRS",
        {
            "action": action_dir,
            "filter": tmp_path / "plugins" / "filters",
            "pipe": tmp_path / "plugins" / "pipes",
            "tool": tmp_path / "plugins" / "tools",
        },
    )
    monkeypatch.setattr(install_all_plugins, "REPO_ROOT", tmp_path)

    candidates, skipped = install_all_plugins.discover_plugins(["action"])

    assert candidates == []
    assert skipped == [(plugin_file, expected_reason)]
