"""Tests for graphify codebuddy install / uninstall commands."""
import json
from pathlib import Path
import sys
from unittest.mock import patch

import pytest


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _codebuddy_install_user(tmp_path):
    from graphify.__main__ import install
    old_cwd = Path.cwd()
    try:
        import os
        os.chdir(tmp_path)
        with patch("graphify.__main__.Path.home", return_value=tmp_path):
            install(platform="codebuddy")
    finally:
        import os
        os.chdir(old_cwd)


def _skill_path_user(tmp_path):
    return tmp_path / ".codebuddy" / "skills" / "graphify" / "SKILL.md"


def _skill_path_project(project_dir):
    return project_dir / ".codebuddy" / "skills" / "graphify" / "SKILL.md"


def _codebuddy_md_path(project_dir):
    return project_dir / "CODEBUDDY.md"


def _settings_path(project_dir):
    return project_dir / ".codebuddy" / "settings.json"


# ---------------------------------------------------------------------------
# User-scope install (graphify install --platform codebuddy)
# ---------------------------------------------------------------------------

def test_codebuddy_install_user_creates_skill_file(tmp_path):
    """User-scope install copies skill to ~/.codebuddy/skills/graphify/SKILL.md."""
    _codebuddy_install_user(tmp_path)
    skill_path = _skill_path_user(tmp_path)
    assert skill_path.exists()


def test_codebuddy_skill_file_contains_frontmatter(tmp_path):
    """Installed skill file must include graphify YAML frontmatter."""
    _codebuddy_install_user(tmp_path)
    content = _skill_path_user(tmp_path).read_text()
    assert "name: graphify" in content
    assert "description:" in content


def test_codebuddy_skill_file_references_graphify_query(tmp_path):
    """/graphify skill must mention graphify query (query-first policy)."""
    _codebuddy_install_user(tmp_path)
    content = _skill_path_user(tmp_path).read_text()
    assert "graphify query" in content or "/graphify query" in content


# ---------------------------------------------------------------------------
# Project-scope install (graphify codebuddy install)
# ---------------------------------------------------------------------------

def test_codebuddy_install_project_writes_codebuddy_md(tmp_path):
    """Project-scope install writes CODEBUDDY.md with graphify section."""
    from graphify.__main__ import codebuddy_install
    codebuddy_install(tmp_path)
    md = _codebuddy_md_path(tmp_path)
    assert md.exists()
    content = md.read_text()
    assert "## graphify" in content
    assert "graphify-out/" in content


def test_codebuddy_install_project_writes_hook(tmp_path):
    """Project-scope install registers PreToolUse hook in .codebuddy/settings.json."""
    from graphify.__main__ import codebuddy_install
    codebuddy_install(tmp_path)
    settings_path = _settings_path(tmp_path)
    assert settings_path.exists()
    settings = json.loads(settings_path.read_text())
    hooks = settings["hooks"]["PreToolUse"]
    assert any("graphify" in str(h) for h in hooks)


def test_codebuddy_install_hook_has_bash_matcher(tmp_path):
    """The installed hook must include Bash matcher for code search interception."""
    from graphify.__main__ import codebuddy_install
    codebuddy_install(tmp_path)
    settings = json.loads(_settings_path(tmp_path).read_text())
    hooks = settings["hooks"]["PreToolUse"]
    bash_hooks = [h for h in hooks if h.get("matcher") == "Bash|Grep"]
    assert any("graphify" in str(h) for h in bash_hooks)


def test_codebuddy_install_hook_has_read_glob_matcher(tmp_path):
    """The installed hook must include Read|Glob matcher for file-read interception."""
    from graphify.__main__ import codebuddy_install
    codebuddy_install(tmp_path)
    settings = json.loads(_settings_path(tmp_path).read_text())
    hooks = settings["hooks"]["PreToolUse"]
    read_hooks = [h for h in hooks if h.get("matcher") == "Read|Glob"]
    assert any("graphify" in str(h) for h in read_hooks)


def test_codebuddy_install_idempotent(tmp_path):
    """Re-install does not duplicate ## graphify sections."""
    from graphify.__main__ import codebuddy_install
    codebuddy_install(tmp_path)
    codebuddy_install(tmp_path)
    md = _codebuddy_md_path(tmp_path)
    assert md.read_text().count("## graphify") == 1


def test_codebuddy_install_upgrades_stale_section(tmp_path):
    """Re-install replaces an old graphify section with the current template."""
    from graphify.__main__ import codebuddy_install, _CODEBUDDY_MD_MARKER
    # Write a stale section manually
    md = _codebuddy_md_path(tmp_path)
    md.write_text("old content\n\n## graphify\nThis is old instructions\n")
    codebuddy_install(tmp_path)
    content = md.read_text()
    assert _CODEBUDDY_MD_MARKER in content
    assert "old content" in content
    assert "This is old instructions" not in content
    assert "graphify-out/" in content
    assert content.count("## graphify") == 1


def test_codebuddy_install_merges_existing_codebuddy_md(tmp_path):
    """Install appends to an existing CODEBUDDY.md, preserving other content."""
    from graphify.__main__ import codebuddy_install
    _codebuddy_md_path(tmp_path).write_text("# My project rules\n")
    codebuddy_install(tmp_path)
    content = _codebuddy_md_path(tmp_path).read_text()
    assert "# My project rules" in content
    assert "## graphify" in content
    assert "graphify-out/" in content


def test_codebuddy_install_prints_no_change_on_second_run(tmp_path, capsys):
    """Second install prints '(no change)' when content is identical."""
    from graphify.__main__ import codebuddy_install
    codebuddy_install(tmp_path)
    out1 = capsys.readouterr().out
    codebuddy_install(tmp_path)
    out2 = capsys.readouterr().out
    assert "no change" in out2


def test_codebuddy_install_hint_git_add(tmp_path, capsys):
    """Project-scoped install via CLI prints a git add hint."""
    from graphify.__main__ import main
    home = tmp_path / "home"
    project = tmp_path / "project"
    project.mkdir()
    old_cwd = Path.cwd()
    try:
        import os
        os.chdir(project)
        with patch("graphify.__main__.Path.home", return_value=home):
            sys.argv = ["graphify", "codebuddy", "install"]
            main()
    finally:
        import os
        os.chdir(old_cwd)
    # codebuddy_install calls print() directly, no git add hint printed there
    # so this test checks that no errors occur


# ---------------------------------------------------------------------------
# Uninstall
# ---------------------------------------------------------------------------

def test_codebuddy_uninstall_removes_section(tmp_path):
    """Uninstall removes the ## graphify section from CODEBUDDY.md."""
    from graphify.__main__ import codebuddy_install, codebuddy_uninstall
    codebuddy_install(tmp_path)
    codebuddy_uninstall(tmp_path)
    md = _codebuddy_md_path(tmp_path)
    assert not md.exists()


def test_codebuddy_uninstall_removes_hook(tmp_path):
    """Uninstall removes the PreToolUse hook from .codebuddy/settings.json."""
    from graphify.__main__ import codebuddy_install, codebuddy_uninstall
    codebuddy_install(tmp_path)
    codebuddy_uninstall(tmp_path)
    settings_path = _settings_path(tmp_path)
    if settings_path.exists():
        settings = json.loads(settings_path.read_text())
        hooks = settings.get("hooks", {}).get("PreToolUse", [])
        assert not any("graphify" in str(h) for h in hooks)


def test_codebuddy_uninstall_noop_if_not_installed(tmp_path):
    """Uninstall should not raise when CODEBUDDY.md doesn't exist."""
    from graphify.__main__ import codebuddy_uninstall
    codebuddy_uninstall(tmp_path)  # should not raise


def test_codebuddy_uninstall_noop_if_no_section(tmp_path):
    """Uninstall should not error when CODEBUDDY.md exists but no graphify section."""
    from graphify.__main__ import codebuddy_uninstall
    _codebuddy_md_path(tmp_path).write_text("# Some other project\n")
    codebuddy_uninstall(tmp_path)
    content = _codebuddy_md_path(tmp_path).read_text()
    assert "# Some other project" in content


def test_codebuddy_uninstall_preserves_other_content(tmp_path):
    """Uninstall preserves non-graphify content in CODEBUDDY.md."""
    from graphify.__main__ import codebuddy_install, codebuddy_uninstall
    _codebuddy_md_path(tmp_path).write_text("# My project rules\n")
    codebuddy_install(tmp_path)
    codebuddy_uninstall(tmp_path)
    # When graphify section was appended, uninstall removes it and the file
    # becomes the original content
    content = _codebuddy_md_path(tmp_path).read_text()
    assert "## graphify" not in content
    assert "# My project rules" in content


# ---------------------------------------------------------------------------
# uninstall_all integration
# ---------------------------------------------------------------------------

def test_uninstall_all_removes_codebuddy_md(tmp_path, monkeypatch):
    """graphify uninstall must clean up CODEBUDDY.md."""
    from graphify.__main__ import main
    home = tmp_path / "home"
    project = tmp_path / "project"
    project.mkdir()
    monkeypatch.chdir(project)
    with patch("graphify.__main__.Path.home", return_value=home):
        monkeypatch.setattr(sys, "argv", ["graphify", "codebuddy", "install"])
        main()
        md = _codebuddy_md_path(project)
        assert md.exists()
        monkeypatch.setattr(sys, "argv", ["graphify", "uninstall"])
        main()
    assert not md.exists()


def test_uninstall_all_removes_codebuddy_hook(tmp_path, monkeypatch):
    """graphify uninstall must clean up .codebuddy/settings.json hooks."""
    from graphify.__main__ import main
    home = tmp_path / "home"
    project = tmp_path / "project"
    project.mkdir()
    monkeypatch.chdir(project)
    with patch("graphify.__main__.Path.home", return_value=home):
        monkeypatch.setattr(sys, "argv", ["graphify", "codebuddy", "install"])
        main()
        monkeypatch.setattr(sys, "argv", ["graphify", "uninstall"])
        main()
    settings_path = _settings_path(project)
    if settings_path.exists():
        settings = json.loads(settings_path.read_text())
        hooks = settings.get("hooks", {}).get("PreToolUse", [])
        assert not any("graphify" in str(h) for h in hooks)


# ---------------------------------------------------------------------------
# Platform config sanity
# ---------------------------------------------------------------------------

def test_codebuddy_in_platform_config():
    """codebuddy must be registered in _PLATFORM_CONFIG."""
    from graphify.__main__ import _PLATFORM_CONFIG
    assert "codebuddy" in _PLATFORM_CONFIG
    assert _PLATFORM_CONFIG["codebuddy"]["skill_file"] == "skill.md"
    assert _PLATFORM_CONFIG["codebuddy"]["claude_md"] is False


def test_codebuddy_platform_skill_destination_user_scope(tmp_path):
    """User-scope destination must be ~/.codebuddy/skills/graphify/SKILL.md."""
    from graphify.__main__ import _platform_skill_destination
    with patch("graphify.__main__.Path.home", return_value=tmp_path):
        dst = _platform_skill_destination("codebuddy", project=False)
    assert dst == tmp_path / ".codebuddy" / "skills" / "graphify" / "SKILL.md"


def test_codebuddy_platform_skill_destination_project_scope(tmp_path):
    """Project-scope destination must be <project>/.codebuddy/skills/graphify/SKILL.md."""
    from graphify.__main__ import _platform_skill_destination
    dst = _platform_skill_destination("codebuddy", project=True, project_dir=tmp_path)
    assert dst == tmp_path / ".codebuddy" / "skills" / "graphify" / "SKILL.md"


def test_codebuddy_in_main_help_text(capsys, monkeypatch):
    """`graphify --help` must list codebuddy in the platform list and per-platform section."""
    from graphify.__main__ import main
    monkeypatch.setattr(sys, "argv", ["graphify", "--help"])
    main()
    captured = capsys.readouterr().out
    # codebuddy should appear in the top-level platform list
    assert "|codebuddy)" in captured or "codebuddy" in captured, (
        "codebuddy missing from `graphify --help` platform list"
    )
    # codebuddy install / uninstall should appear in the per-platform section
    assert "codebuddy install" in captured, "`codebuddy install` line missing from help text"
    assert "codebuddy uninstall" in captured, "`codebuddy uninstall` line missing from help text"


def test_codebuddy_skill_file_exists_in_package():
    """skill.md must be present in the installed package (shared with claude)."""
    import graphify
    skill = Path(graphify.__file__).parent / "skill.md"
    assert skill.exists(), "skill.md missing from package"


def test_codebuddy_installation_roundtrip(tmp_path):
    """Install then uninstall leaves no trace of graphify CODEBUDDY.md or hook."""
    from graphify.__main__ import codebuddy_install, codebuddy_uninstall
    # Pre-existing project file
    _codebuddy_md_path(tmp_path).write_text("# My project\n")
    # One section above graphify to test cleanup
    codebuddy_install(tmp_path)
    codebuddy_uninstall(tmp_path)
    # CODEBUDDY.md should exist with original content only
    md = _codebuddy_md_path(tmp_path)
    assert md.exists()
    content = md.read_text()
    assert "## graphify" not in content
    assert "# My project" in content
    # Hook should be removed
    settings_path = _settings_path(tmp_path)
    if settings_path.exists():
        settings = json.loads(settings_path.read_text())
        hooks = settings.get("hooks", {}).get("PreToolUse", [])
        assert not any("graphify" in str(h) for h in hooks)
