"""HOME plugins：布局、加载、OpenAI 工具合并、备份。"""

from __future__ import annotations

import json
from pathlib import Path

from aicd.agent.tools_openai import openai_tools
from aicd.config import AppConfig
from aicd.home import ensure_home_layout, home_paths
from aicd.plugins.backup import backup_plugin_directory, list_plugin_backups
from aicd.plugins.loader import (
    iter_plugin_dirs,
    load_one_plugin,
    plugin_dir_name_is_loadable,
)
from aicd.plugins.manifest import load_manifest_file
from aicd.plugins.plugin_log import (
    prune_plugin_logs,
    read_plugin_log_tail,
    write_plugin_log,
)
from aicd.plugins.registry import PluginRegistry


def test_home_paths_include_plugins(tmp_path: Path) -> None:
    layout = ensure_home_layout(tmp_path)
    assert layout["plugins"] == tmp_path / "plugins"
    assert layout["plugins_backups"] == tmp_path / "plugins" / ".backups"
    assert layout["plugins_state"] == tmp_path / "plugins" / ".state"
    assert (tmp_path / "plugins" / "README.md").is_file()


def test_iter_plugin_dirs_skips_backup_like_dirs(tmp_path: Path) -> None:
    root = tmp_path / "plugins"
    root.mkdir()
    for name in (
        "good",
        "myplug_backup",
        "myplug.bak",
        "webui-copy",
        "tool_old",
        "backup",
    ):
        (root / name).mkdir()
    (root / ".backups").mkdir()
    (root / "_template").mkdir()
    names = {p.name for p in iter_plugin_dirs(root)}
    assert names == {"good"}


def test_plugin_dir_name_is_loadable_rules() -> None:
    assert plugin_dir_name_is_loadable("my_plugin")
    assert not plugin_dir_name_is_loadable(".state")
    assert not plugin_dir_name_is_loadable("demo_backup")
    assert not plugin_dir_name_is_loadable("Copy of demo")
    assert not plugin_dir_name_is_loadable("copy_foo")


def test_load_enabled_plugin_prompt(tmp_path: Path) -> None:
    root = tmp_path
    layout = ensure_home_layout(root)
    pdir = layout["plugins"] / "demo"
    pdir.mkdir(parents=True)
    (pdir / "plugin.yaml").write_text(
        "schema_version: 1\n"
        "id: demo\n"
        "name: Demo\n"
        "version: 0.1.0\n"
        "enabled: true\n"
        "description: test\n"
        "entries:\n"
        "  prompt_file: PROMPT.md\n"
        "  tools_file: tools.json\n",
        encoding="utf-8",
    )
    (pdir / "PROMPT.md").write_text("Hello from demo plugin.\n", encoding="utf-8")
    (pdir / "tools.json").write_text("[]\n", encoding="utf-8")
    cfg = AppConfig()
    reg = PluginRegistry(layout=layout, app=cfg)
    reg.load_all()
    assert "Hello from demo plugin" in reg.prompt_block
    assert reg.openai_extra_tools() == []
    st = tmp_path / "plugins" / ".state" / "load_report.json"
    assert st.is_file()


def test_tools_json_with_stub_handlers(tmp_path: Path) -> None:
    root = tmp_path
    layout = ensure_home_layout(root)
    pdir = layout["plugins"] / "t1"
    pdir.mkdir(parents=True)
    (pdir / "plugin.yaml").write_text(
        "id: t1\nname: T1\nversion: 1\nenabled: true\nentries:\n  tools_file: tools.json\n",
        encoding="utf-8",
    )
    tools = [
        {
            "type": "function",
            "function": {
                "name": "ping",
                "description": "ping",
                "parameters": {"type": "object", "properties": {}},
            },
        }
    ]
    (pdir / "tools.json").write_text(json.dumps(tools), encoding="utf-8")
    cfg = AppConfig()
    o = load_one_plugin(pdir, builtins=frozenset(), app=cfg)
    assert o.ok
    assert len(o.openai_fragments) == 1
    assert o.openai_fragments[0]["function"]["name"] == "plugin_t1__ping"
    assert "plugin_t1__ping" in o.handlers


def test_openai_tools_includes_plugin_management() -> None:
    names = {t["function"]["name"] for t in openai_tools()}
    assert "plugin_list" in names
    assert "plugin_reload" in names
    assert "plugin_read_log" in names


def test_manifest_display_name(tmp_path: Path) -> None:
    mf = tmp_path / "plugin.yaml"
    mf.write_text(
        "id: x\nname: nx\ndisplay_name: Nice\nversion: 1\ndescription: d1\n",
        encoding="utf-8",
    )
    m = load_manifest_file(mf)
    assert m.display_name == "Nice"
    assert m.description == "d1"


def test_plugin_log_write_and_read(tmp_path: Path) -> None:
    write_plugin_log(
        tmp_path,
        level="ERROR",
        source="t",
        message="m1",
        retention_days=7,
    )
    ok, text = read_plugin_log_tail(tmp_path, max_lines=10)
    assert ok and "m1" in text


def test_plugin_log_prune_old_files(tmp_path: Path) -> None:
    logs = tmp_path / "logs"
    logs.mkdir(parents=True)
    old = logs / "2000-01-01.log"
    old.write_text("x\n", encoding="utf-8")
    prune_plugin_logs(logs, retention_days=7)
    assert not old.is_file()


def test_load_failure_writes_plugin_log(tmp_path: Path) -> None:
    layout = ensure_home_layout(tmp_path)
    pdir = layout["plugins"] / "bad"
    pdir.mkdir(parents=True)
    (pdir / "plugin.yaml").write_text("id: wrongdir\nenabled: true\n", encoding="utf-8")
    cfg = AppConfig()
    reg = PluginRegistry(layout=layout, app=cfg)
    reg.load_all()
    ok, tail = read_plugin_log_tail(pdir, max_lines=50)
    assert ok
    assert "wrongdir" in tail or "load_failed" in tail


def test_summarize_includes_description_and_tools(tmp_path: Path) -> None:
    layout = ensure_home_layout(tmp_path)
    pdir = layout["plugins"] / "d2"
    pdir.mkdir(parents=True)
    (pdir / "plugin.yaml").write_text(
        "id: d2\ndescription: hello\nversion: 2\nenabled: true\n"
        "entries:\n  tools_file: tools.json\n",
        encoding="utf-8",
    )
    (pdir / "tools.json").write_text(
        json.dumps(
            [
                {
                    "type": "function",
                    "function": {
                        "name": "x",
                        "parameters": {"type": "object", "properties": {}},
                    },
                }
            ]
        ),
        encoding="utf-8",
    )
    cfg = AppConfig()
    reg = PluginRegistry(layout=layout, app=cfg)
    reg.load_all()
    rows = reg.summarize()
    row = next(r for r in rows if r["id"] == "d2")
    assert row.get("description") == "hello"
    assert "plugin_d2__x" in row.get("plugin_tools", [])


def test_backup_roundtrip_names(tmp_path: Path) -> None:
    layout = home_paths(tmp_path)
    layout["plugins"].mkdir(parents=True)
    layout["plugins_backups"].mkdir(parents=True)
    pdir = layout["plugins"] / "bx"
    pdir.mkdir()
    (pdir / "plugin.yaml").write_text("id: bx\nenabled: false\n", encoding="utf-8")
    arc = backup_plugin_directory(pdir, layout["plugins_backups"], keep=5)
    assert arc.is_file()
    assert arc.name.endswith(".tar.gz")
    assert arc.name in list_plugin_backups(layout["plugins_backups"], "bx")


def test_iter_plugin_dirs_skips_template(tmp_path: Path) -> None:
    layout = ensure_home_layout(tmp_path)
    (layout["plugins"] / "_template").mkdir(exist_ok=True)
    (layout["plugins"] / "real").mkdir(exist_ok=True)
    (layout["plugins"] / "real" / "plugin.yaml").write_text(
        "id: real\nenabled: false\n", encoding="utf-8"
    )
    dirs = [p.name for p in iter_plugin_dirs(layout["plugins"])]
    assert "_template" not in dirs
    assert "real" in dirs


def test_config_plugins_roundtrip(tmp_path: Path) -> None:
    from aicd.config import load_config, save_config

    p = tmp_path / "c.yaml"
    c = AppConfig()
    c.plugins.allow_python_handlers = True
    c.plugins.backup_keep = 15
    c.plugins.log_retention_days = 14
    save_config(p, c)
    c2 = load_config(p)
    assert c2.plugins.allow_python_handlers is True
    assert c2.plugins.backup_keep == 15
    assert c2.plugins.log_retention_days == 14
