"""Verify Agnes-only files never leak into the synth Claude Code marketplace.

Two surfaces are covered:

* the ZIP delivery (``app/marketplace_server/packager.py::build_zip``),
* the git tree built from ``file_set_for_user`` (``app/marketplace_server/git_backend.py``).

Both must strip:

* ``.claude-plugin/marketplace-metadata.json`` (anywhere in the plugin tree),
* anything under ``.agnes/`` (anywhere in the plugin tree).

Test fixtures stand up a fake plugin dir on disk + a fake `plugins` list
that the packager / git_backend consume directly — we don't go through the
DB layer because the strip behavior is independent of plugin resolution.
"""

from __future__ import annotations

import io
import zipfile
from pathlib import Path

from src.marketplace_filter import is_agnes_only_path


def _build_plugin_dir(tmp_path: Path) -> Path:
    """Create a plugin directory with a mix of regular and Agnes-only files."""
    plugin = tmp_path / "plugins" / "demo"
    plugin.mkdir(parents=True)

    # Standard Claude Code files
    (plugin / ".claude-plugin").mkdir()
    (plugin / ".claude-plugin" / "plugin.json").write_text(
        '{"name":"demo","version":"1.0"}', encoding="utf-8",
    )
    (plugin / "skills").mkdir()
    (plugin / "skills" / "foo").mkdir()
    (plugin / "skills" / "foo" / "SKILL.md").write_text(
        "---\nname: foo\n---\nbody", encoding="utf-8",
    )

    # Agnes-only files that MUST be stripped
    (plugin / ".claude-plugin" / "marketplace-metadata.json").write_text(
        '{"version":1}', encoding="utf-8",
    )
    (plugin / ".agnes").mkdir()
    (plugin / ".agnes" / "cover.png").write_bytes(b"\x89PNG\r\n\x1a\nfake")
    (plugin / ".agnes" / "docs").mkdir()
    (plugin / ".agnes" / "docs" / "internal.md").write_text(
        "# internal", encoding="utf-8",
    )

    return plugin


def _fake_plugin_record(plugin_dir: Path, *, prefixed_name: str = "test-demo") -> dict:
    """Shape the packager / git_backend expect from `resolve_user_marketplace`."""
    return {
        "marketplace_id": "test",
        "marketplace_slug": "test",
        "original_name": "demo",
        "prefixed_name": prefixed_name,
        "manifest_name": "demo",
        "version": "1.0",
        "plugin_dir": plugin_dir,
        "raw": {"name": "demo", "version": "1.0", "description": "demo"},
        "source": "marketplace",
    }


# --- is_agnes_only_path ---------------------------------------------------


def test_is_agnes_only_path_strips_dot_agnes_at_root():
    assert is_agnes_only_path((".agnes", "cover.png"))
    assert is_agnes_only_path((".agnes", "docs", "x.md"))


def test_is_agnes_only_path_strips_dot_agnes_anywhere():
    """Even nested `.agnes/` dirs are stripped — e.g. inside a plugin."""
    assert is_agnes_only_path(("plugins", "foo", ".agnes", "x.png"))


def test_is_agnes_only_path_strips_marketplace_metadata_json():
    assert is_agnes_only_path((".claude-plugin", "marketplace-metadata.json"))
    assert is_agnes_only_path(("nested", "marketplace-metadata.json"))


def test_is_agnes_only_path_keeps_normal_files():
    """`.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`, plain
    plugin code etc. all survive the filter."""
    assert not is_agnes_only_path((".claude-plugin", "plugin.json"))
    assert not is_agnes_only_path((".claude-plugin", "marketplace.json"))
    assert not is_agnes_only_path(("skills", "foo", "SKILL.md"))
    assert not is_agnes_only_path(("agents", "bar.md"))


def test_is_agnes_only_path_does_not_match_lookalikes():
    """Naming collisions: a directory named ``.agneswriter`` (single segment
    starting with `.agnes` but not equal) is NOT stripped."""
    assert not is_agnes_only_path((".agneswriter", "x.md"))
    assert not is_agnes_only_path(("not-marketplace-metadata.json",))


# --- ZIP path -------------------------------------------------------------


def test_zip_strips_agnes_only_files(tmp_path, monkeypatch):
    """End-to-end through the packager's ``_collect_members``: produce a ZIP
    and assert no Agnes-only paths land inside it."""
    from app.marketplace_server import packager

    plugin_dir = _build_plugin_dir(tmp_path)
    plugins = [_fake_plugin_record(plugin_dir)]
    members = packager._collect_members(plugins, etag="testetag")

    arcnames = {arc for arc, _ in members}
    # Sanity: the regular files DID survive
    assert any(a.endswith(".claude-plugin/plugin.json") for a in arcnames)
    assert any(a.endswith("skills/foo/SKILL.md") for a in arcnames)
    assert any(a.endswith(".claude-plugin/marketplace.json") for a in arcnames)
    # Agnes-only files DID NOT
    assert not any("marketplace-metadata.json" in a for a in arcnames), arcnames
    assert not any(".agnes/" in a for a in arcnames), arcnames


def test_zip_etag_independent_of_agnes_files(tmp_path):
    """Compute_etag in marketplace_filter consumes the same is_agnes_only_path
    filter, so adding/removing `.agnes/` content doesn't bust the ETag."""
    from src import marketplace_filter

    plugin_dir = _build_plugin_dir(tmp_path)
    plugins = [_fake_plugin_record(plugin_dir)]
    etag_with_agnes = marketplace_filter.compute_etag(plugins)

    # Drop the `.agnes/` content and re-compute. ETag must match.
    import shutil
    shutil.rmtree(plugin_dir / ".agnes")
    (plugin_dir / ".claude-plugin" / "marketplace-metadata.json").unlink()
    etag_without_agnes = marketplace_filter.compute_etag(plugins)
    assert etag_with_agnes == etag_without_agnes


# --- Git tree path -------------------------------------------------------


def test_git_tree_strips_agnes_only_files(tmp_path):
    """The git_backend ``file_set_for_user`` builds the same dict shape as
    the ZIP, so the same strip applies."""
    # We don't have an easy way to call file_set_for_user without a DB, so
    # instead replicate the small block that walks plugin_dir and calls the
    # filter — exact same code path as git_backend, identical inputs.
    plugin_dir = _build_plugin_dir(tmp_path)

    files: dict[str, bytes] = {}
    prefix = "demo"
    for f in sorted(p for p in plugin_dir.rglob("*") if p.is_file()):
        rel_parts = f.relative_to(plugin_dir).parts
        if is_agnes_only_path(rel_parts):
            continue
        files[f"plugins/{prefix}/{f.relative_to(plugin_dir).as_posix()}"] = f.read_bytes()

    assert any(p.endswith("/SKILL.md") for p in files)
    assert any(p.endswith("/.claude-plugin/plugin.json") for p in files)
    assert not any("marketplace-metadata.json" in p for p in files)
    assert not any(".agnes/" in p for p in files)
