"""Tests for ievo uninstall command."""

from __future__ import annotations

from pathlib import Path
from unittest.mock import patch

import pytest
import typer

from ievo.commands.install import IEVO_LINE
from ievo.commands.uninstall import _clean_settings_local, _unpatch_claude_md, uninstall

# ── helpers ──────────────────────────────────────────────────


def _make_project(base: Path) -> Path:
    """Create minimal .ievo project structure in base."""
    (base / ".ievo").mkdir(parents=True, exist_ok=True)
    (base / ".ievo" / "manifest.yaml").write_text("project: test\n")
    return base


def _make_full_project(base: Path) -> Path:
    """Create project with all integration files."""
    _make_project(base)

    # CLAUDE.md with IEVO_LINE
    (base / "CLAUDE.md").write_text(f"# My Project\n\n{IEVO_LINE}\n")

    # Claude Code hooks
    (base / ".claude" / "hooks").mkdir(parents=True, exist_ok=True)
    (base / ".claude" / "hooks" / "precompact_save.py").write_text("# hook\n")

    # Claude Code settings with iEvo hook
    import json

    (base / ".claude").mkdir(parents=True, exist_ok=True)
    (base / ".claude" / "settings.local.json").write_text(
        json.dumps({"hooks": {"PreCompact": [{"matcher": ""}]}}) + "\n"
    )

    # Claude agents
    (base / ".claude" / "agents").mkdir(parents=True, exist_ok=True)
    (base / ".claude" / "agents" / "spec-writer.md").write_text("# spec-writer\n")
    (base / ".claude" / "agents" / "coder.md").write_text("# coder\n")

    # Skills
    (base / ".claude" / "skills" / "backlog").mkdir(parents=True, exist_ok=True)
    (base / ".claude" / "skills" / "backlog" / "SKILL.md").write_text("# backlog\n")
    (base / ".claude" / "skills" / "ievo").mkdir(parents=True, exist_ok=True)
    (base / ".claude" / "skills" / "ievo" / "SKILL.md").write_text("# ievo\n")

    return base


# ── _unpatch_claude_md unit tests ────────────────────────────


def test_unpatch_claude_md_returns_false_for_missing_file(tmp_path: Path) -> None:
    """_unpatch_claude_md returns False when the file does not exist."""
    result = _unpatch_claude_md(tmp_path / "nonexistent.md")
    assert result is False


def test_unpatch_claude_md_returns_false_when_no_marker(tmp_path: Path) -> None:
    """_unpatch_claude_md returns False when file has no marker block."""
    f = tmp_path / "CLAUDE.md"
    f.write_text("# My Project\nno marker here\n")
    result = _unpatch_claude_md(f)
    assert result is False
    assert f.read_text() == "# My Project\nno marker here\n"


def test_unpatch_claude_md_removes_marker(tmp_path: Path) -> None:
    """_unpatch_claude_md removes the IEVO_LINE and returns True."""
    f = tmp_path / "CLAUDE.md"
    f.write_text(f"# P\n\n{IEVO_LINE}\n")
    result = _unpatch_claude_md(f)
    assert result is True
    assert IEVO_LINE not in f.read_text()
    assert "# P" in f.read_text()


# ── _clean_settings_local unit tests ─────────────────────────


def test_clean_settings_returns_false_for_missing_file(tmp_path: Path) -> None:
    """_clean_settings_local returns False when file does not exist."""
    assert _clean_settings_local(tmp_path / "nope.json") is False


def test_clean_settings_returns_false_when_no_hook(tmp_path: Path) -> None:
    """_clean_settings_local returns False when no PreCompact hook."""
    f = tmp_path / "settings.local.json"
    f.write_text('{"permissions": {"allow": ["*"]}}\n')
    assert _clean_settings_local(f) is False
    # File unchanged
    assert "permissions" in f.read_text()


def test_clean_settings_deletes_file_when_only_hook(tmp_path: Path) -> None:
    """File is deleted when PreCompact was the only content."""
    import json

    f = tmp_path / "settings.local.json"
    f.write_text(json.dumps({"hooks": {"PreCompact": [{"matcher": ""}]}}))
    assert _clean_settings_local(f) is True
    assert not f.exists()


def test_clean_settings_preserves_other_keys(tmp_path: Path) -> None:
    """PreCompact is removed but other keys are preserved."""
    import json

    f = tmp_path / "settings.local.json"
    f.write_text(
        json.dumps(
            {
                "permissions": {"allow": ["Bash"]},
                "hooks": {"PreCompact": [{"matcher": ""}], "PostSession": [{"matcher": ""}]},
            }
        )
    )
    assert _clean_settings_local(f) is True
    data = json.loads(f.read_text())
    assert "PreCompact" not in data["hooks"]
    assert "PostSession" in data["hooks"]
    assert data["permissions"] == {"allow": ["Bash"]}


def test_clean_settings_returns_false_for_invalid_json(tmp_path: Path) -> None:
    """_clean_settings_local returns False for invalid JSON."""
    f = tmp_path / "settings.local.json"
    f.write_text("not json at all")
    assert _clean_settings_local(f) is False
    assert f.read_text() == "not json at all"


def test_uninstall_force_invalid_settings_json(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """--force handles corrupt settings.local.json gracefully (no crash)."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    settings = tmp_path / ".claude" / "settings.local.json"
    settings.parent.mkdir(parents=True, exist_ok=True)
    settings.write_text("not valid json {{")

    # Should not crash — JSON error is caught
    uninstall(force=True)

    assert not (tmp_path / ".ievo" / "manifest.yaml").exists()
    # Invalid settings left untouched (no hook to remove)
    assert settings.read_text() == "not valid json {{"


# ── no project ───────────────────────────────────────────────


def test_uninstall_no_project_exits_with_error(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    """uninstall with no project found prints error and exits with code 1."""
    monkeypatch.chdir(tmp_path)

    with pytest.raises(typer.Exit) as exc_info:
        uninstall(force=False)

    assert exc_info.value.exit_code == 1
    captured = capsys.readouterr()
    assert "No iEvo project found" in captured.out


# ── user declines ─────────────────────────────────────────────


def test_uninstall_user_declines_no_changes(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """When user declines confirmation, no files are modified."""
    _make_full_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    with patch("typer.confirm", return_value=False), pytest.raises(typer.Exit):
        uninstall(force=False)

    # All files should still exist
    assert (tmp_path / "CLAUDE.md").exists()
    assert (tmp_path / ".claude" / "hooks" / "precompact_save.py").exists()
    assert (tmp_path / ".claude" / "settings.local.json").exists()
    assert (tmp_path / ".claude" / "agents" / "spec-writer.md").exists()


# ── --force flag ──────────────────────────────────────────────


def test_uninstall_force_removes_marker_block(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """--force removes marker block from CLAUDE.md; rest of content preserved."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    (tmp_path / "CLAUDE.md").write_text(f"# My Project\n\n## Tech stack\nPython\n\n{IEVO_LINE}\n")

    uninstall(force=True)

    content = (tmp_path / "CLAUDE.md").read_text()
    assert "# My Project" in content
    assert "## Tech stack" in content
    assert "Python" in content
    assert IEVO_LINE not in content


def test_uninstall_force_skips_claude_md_without_marker(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """When CLAUDE.md has no marker, it is not modified; manifest is removed."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    original = "# My Project\n\n## Notes\nsome content\n"
    (tmp_path / "CLAUDE.md").write_text(original)

    # Manifest is always removed; CLAUDE.md without marker is left untouched
    uninstall(force=True)

    assert (tmp_path / "CLAUDE.md").read_text() == original
    assert not (tmp_path / ".ievo" / "manifest.yaml").exists()


def test_uninstall_force_removes_hooks_file(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """--force removes .claude/hooks/precompact_save.py."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    hooks_dir = tmp_path / ".claude" / "hooks"
    hooks_dir.mkdir(parents=True)
    hook_file = hooks_dir / "precompact_save.py"
    hook_file.write_text("# hook\n")

    uninstall(force=True)

    assert not hook_file.exists()


def test_uninstall_force_removes_settings_hook_only(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """--force removes PreCompact hook from settings.local.json, deletes file if empty."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    settings_dir = tmp_path / ".claude"
    settings_dir.mkdir(parents=True)
    settings_file = settings_dir / "settings.local.json"
    import json

    settings_file.write_text(json.dumps({"hooks": {"PreCompact": [{"matcher": ""}]}}) + "\n")

    uninstall(force=True)

    # File deleted because only the hook was there
    assert not settings_file.exists()


def test_uninstall_force_preserves_user_permissions_in_settings(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """--force removes PreCompact hook but preserves user permissions."""
    import json

    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    settings_dir = tmp_path / ".claude"
    settings_dir.mkdir(parents=True)
    settings_file = settings_dir / "settings.local.json"
    settings_file.write_text(
        json.dumps(
            {
                "permissions": {"allow": ["*"]},
                "hooks": {
                    "PreCompact": [
                        {
                            "matcher": "",
                            "hooks": [
                                {
                                    "type": "command",
                                    "command": "python3 .claude/hooks/precompact_save.py",
                                }
                            ],
                        }
                    ],
                },
            }
        )
        + "\n"
    )

    uninstall(force=True)

    # File preserved with permissions intact
    assert settings_file.exists()
    data = json.loads(settings_file.read_text())
    assert "permissions" in data
    assert data["permissions"] == {"allow": ["*"]}
    assert "hooks" not in data  # hook section removed entirely


def test_uninstall_force_removes_agent_md_files(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """--force removes .claude/agents/*.md; directory itself preserved."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    agents_dir = tmp_path / ".claude" / "agents"
    agents_dir.mkdir(parents=True)
    (agents_dir / "spec-writer.md").write_text("# agent\n")
    (agents_dir / "coder.md").write_text("# agent\n")

    uninstall(force=True)

    assert not (agents_dir / "spec-writer.md").exists()
    assert not (agents_dir / "coder.md").exists()
    assert agents_dir.is_dir()  # directory itself is NOT removed


def test_uninstall_force_preserves_user_created_agents(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """--force only removes bundled agents; user-created agents are preserved."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    agents_dir = tmp_path / ".claude" / "agents"
    agents_dir.mkdir(parents=True)

    # Bundled agent — should be removed
    (agents_dir / "spec-writer.md").write_text("# bundled agent\n")
    # User-created agent (not in bundled list) — should be preserved
    (agents_dir / "my-custom-agent.md").write_text("# my custom agent\n")

    uninstall(force=True)

    assert not (agents_dir / "spec-writer.md").exists()
    assert (agents_dir / "my-custom-agent.md").exists()
    assert agents_dir.is_dir()


def test_uninstall_force_preserves_ievo_dir(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """--force removes manifest.yaml but preserves other .ievo/ content."""
    _make_full_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    # Add some spec content to .ievo/ (non-manifest files)
    (tmp_path / ".ievo" / "spec").mkdir(parents=True, exist_ok=True)
    (tmp_path / ".ievo" / "spec" / "SPEC_INDEX.md").write_text("# index\n")

    uninstall(force=True)

    # manifest removed
    assert not (tmp_path / ".ievo" / "manifest.yaml").exists()
    # .ievo/ preserved because it has other content
    assert (tmp_path / ".ievo").is_dir()
    assert (tmp_path / ".ievo" / "spec" / "SPEC_INDEX.md").exists()


def test_uninstall_force_preserves_gitignore(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """--force does not touch .gitignore."""
    _make_full_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    gitignore = tmp_path / ".gitignore"
    gitignore.write_text("*.pyc\n")

    uninstall(force=True)

    assert gitignore.exists()
    assert gitignore.read_text() == "*.pyc\n"


def test_uninstall_force_skips_missing_files(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    """--force on project with no integration files beyond manifest removes manifest."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    # Only manifest exists — uninstall removes it and succeeds
    uninstall(force=True)

    assert not (tmp_path / ".ievo" / "manifest.yaml").exists()
    captured = capsys.readouterr()
    assert "iEvo integration removed" in captured.out


def test_uninstall_prints_summary_before_prompt(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    """Summary lists existing files; missing files are omitted."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    # Create only some integration files
    (tmp_path / "CLAUDE.md").write_text(f"# P\n{IEVO_LINE}\n")
    (tmp_path / ".claude" / "hooks").mkdir(parents=True)
    (tmp_path / ".claude" / "hooks" / "precompact_save.py").write_text("# hook\n")

    with patch("typer.confirm", return_value=False), pytest.raises(typer.Exit):
        uninstall(force=False)

    captured = capsys.readouterr()
    # Summary should list existing files
    assert "CLAUDE.md" in captured.out
    assert "precompact_save.py" in captured.out
    # Non-existent files should NOT be in summary
    assert "spec-writer.yml" not in captured.out
    assert "architect-agent.yml" not in captured.out


# ── integration: init then uninstall ─────────────────────────


def test_init_then_uninstall_roundtrip(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """init() creates project with marker -> uninstall --force removes integration files."""
    from unittest.mock import patch as _patch

    monkeypatch.chdir(tmp_path)
    fake_home = tmp_path / ".fake-ievo"
    fake_home.mkdir()

    with (
        _patch("ievo.commands.install.ensure_home", return_value=fake_home),
        _patch("ievo.core.global_config.get_config", return_value="gh_cli"),
    ):
        from ievo.commands.install import install

        install(name=".")

    # CLAUDE.md should have IEVO_LINE after init
    assert IEVO_LINE in (tmp_path / "CLAUDE.md").read_text()

    # Run uninstall
    uninstall(force=True)

    # IEVO_LINE should be removed
    assert IEVO_LINE not in (tmp_path / "CLAUDE.md").read_text()

    # manifest removed — project is now "uninitialized" from ievo's perspective
    assert not (tmp_path / ".ievo" / "manifest.yaml").exists()
    # .ievo/ dir preserved (contains IEVO.md from init templates)
    assert (tmp_path / ".ievo").is_dir()

    # .claude/agents/ dir exists but .md files gone
    assert (tmp_path / ".claude" / "agents").is_dir()
    md_files = list((tmp_path / ".claude" / "agents").glob("*.md"))
    assert md_files == []

    # Skills should be removed
    assert not (tmp_path / ".claude" / "skills" / "backlog" / "SKILL.md").exists()
    assert not (tmp_path / ".claude" / "skills" / "ievo" / "SKILL.md").exists()
    assert not (tmp_path / ".claude" / "skills").exists()


def test_uninstall_preserves_claude_md_body(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """uninstall --force preserves CLAUDE.md body outside the marker exactly."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    custom_content = "# My Project\n\n## Tech stack\nPython 3.13\n"
    (tmp_path / "CLAUDE.md").write_text(f"{custom_content}\n{IEVO_LINE}\n")

    uninstall(force=True)

    result = (tmp_path / "CLAUDE.md").read_text()
    assert "# My Project" in result
    assert "## Tech stack" in result
    assert "Python 3.13" in result
    assert IEVO_LINE not in result


def test_uninstall_then_reinit(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """After uninstall, re-init recreates the marker block (manifest removed by uninstall)."""
    from unittest.mock import patch as _patch

    monkeypatch.chdir(tmp_path)
    fake_home = tmp_path / ".fake-ievo"
    fake_home.mkdir()

    with (
        _patch("ievo.commands.install.ensure_home", return_value=fake_home),
        _patch("ievo.core.global_config.get_config", return_value="gh_cli"),
    ):
        from ievo.commands.install import install

        # First init
        install(name=".")

    # Uninstall — removes manifest automatically
    uninstall(force=True)

    # Manifest removed by uninstall — project is now uninitialized
    assert not (tmp_path / ".ievo" / "manifest.yaml").exists()

    with (
        _patch("ievo.commands.install.ensure_home", return_value=fake_home),
        _patch("ievo.core.global_config.get_config", return_value="gh_cli"),
    ):
        # Re-init — works because manifest is gone
        install(name=".")

    # IEVO_LINE should be present again
    assert IEVO_LINE in (tmp_path / "CLAUDE.md").read_text()
    assert (tmp_path / ".ievo" / "manifest.yaml").exists()


def test_uninstall_marker_at_different_positions(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Marker removal works when block is at start, middle, and end of file."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    # (a) IEVO_LINE at end of file
    claude_md = tmp_path / "CLAUDE.md"
    claude_md.write_text(f"# Project\n\n{IEVO_LINE}\n")
    uninstall(force=True)
    content = claude_md.read_text()
    assert IEVO_LINE not in content
    assert "# Project" in content

    # Re-create manifest for next sub-test
    (tmp_path / ".ievo").mkdir(exist_ok=True)
    (tmp_path / ".ievo" / "manifest.yaml").write_text("project: test\n")

    # (b) IEVO_LINE in middle of file
    claude_md.write_text(f"# Project\n\n{IEVO_LINE}\n\n## Section 2\nmore content\n")
    uninstall(force=True)
    content = claude_md.read_text()
    assert IEVO_LINE not in content
    assert "# Project" in content
    assert "## Section 2" in content

    # Re-create manifest for next sub-test
    (tmp_path / ".ievo").mkdir(exist_ok=True)
    (tmp_path / ".ievo" / "manifest.yaml").write_text("project: test\n")

    # (c) IEVO_LINE at start of file
    claude_md.write_text(f"{IEVO_LINE}\n\n# Project\n")
    uninstall(force=True)
    content = claude_md.read_text()
    assert IEVO_LINE not in content
    assert "# Project" in content


def test_uninstall_agent_file_already_deleted_is_skipped(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Agent files that disappear between glob and unlink are silently skipped."""
    from unittest.mock import patch as _patch

    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    # Create an agent file so the glob finds it
    agents_dir = tmp_path / ".claude" / "agents"
    agents_dir.mkdir(parents=True)
    agent_file = agents_dir / "spec-writer.md"
    agent_file.write_text("# agent\n")

    # Add CLAUDE.md with IEVO_LINE
    (tmp_path / "CLAUDE.md").write_text(f"# P\n\n{IEVO_LINE}\n")

    # Patch Path.is_file to return False ONLY for agent files during the removal loop
    original_is_file = Path.is_file

    def patched_is_file(self: Path) -> bool:
        if self.parent == agents_dir and self.suffix == ".md":
            return False
        return original_is_file(self)

    with _patch.object(Path, "is_file", patched_is_file):
        uninstall(force=True)

    # Agent file still exists (we didn't unlink it since is_file returned False)
    assert agent_file.exists()


# ── REQ-011: Skills removal ────────────────────────────────────


def test_uninstall_force_removes_backlog_skill(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """--force removes .claude/skills/backlog/SKILL.md."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    skills_dir = tmp_path / ".claude" / "skills"
    skills_dir.mkdir(parents=True, exist_ok=True)
    (skills_dir / "backlog").mkdir()
    (skills_dir / "backlog" / "SKILL.md").write_text("# backlog\n")

    uninstall(force=True)

    assert not (skills_dir / "backlog" / "SKILL.md").exists()


def test_uninstall_force_removes_ievo_skill(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """--force removes .claude/skills/ievo/SKILL.md."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    skills_dir = tmp_path / ".claude" / "skills"
    skills_dir.mkdir(parents=True, exist_ok=True)
    (skills_dir / "ievo").mkdir()
    (skills_dir / "ievo" / "SKILL.md").write_text("# ievo\n")

    uninstall(force=True)

    assert not (skills_dir / "ievo" / "SKILL.md").exists()


def test_uninstall_force_removes_skills_dir_when_empty(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """--force removes .claude/skills/ directory when empty."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    skills_dir = tmp_path / ".claude" / "skills"
    (skills_dir / "backlog").mkdir(parents=True)
    (skills_dir / "backlog" / "SKILL.md").write_text("# backlog\n")
    (skills_dir / "ievo").mkdir(parents=True)
    (skills_dir / "ievo" / "SKILL.md").write_text("# ievo\n")

    uninstall(force=True)

    assert not skills_dir.exists()


def test_uninstall_force_preserves_skills_dir_when_user_skills_remain(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Bundled skills removed but user-created skills preserved."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    skills_dir = tmp_path / ".claude" / "skills"

    # Bundled skills
    (skills_dir / "backlog").mkdir(parents=True)
    (skills_dir / "backlog" / "SKILL.md").write_text("# backlog\n")
    (skills_dir / "ievo").mkdir(parents=True)
    (skills_dir / "ievo" / "SKILL.md").write_text("# ievo\n")

    # User-created skill (not in bundled list)
    (skills_dir / "custom").mkdir(parents=True)
    (skills_dir / "custom" / "SKILL.md").write_text("# custom\n")

    uninstall(force=True)

    # Bundled skills removed
    assert not (skills_dir / "backlog").exists()
    assert not (skills_dir / "ievo").exists()
    # User-created skill preserved
    assert (skills_dir / "custom" / "SKILL.md").exists()
    # Parent dir preserved (not empty)
    assert skills_dir.exists()


def test_uninstall_skills_in_summary(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    """Pre-confirmation summary lists skill paths."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    skills_dir = tmp_path / ".claude" / "skills"
    (skills_dir / "backlog").mkdir(parents=True)
    (skills_dir / "backlog" / "SKILL.md").write_text("# backlog\n")
    (skills_dir / "ievo").mkdir(parents=True)
    (skills_dir / "ievo" / "SKILL.md").write_text("# ievo\n")

    with patch("typer.confirm", return_value=False), pytest.raises(typer.Exit):
        uninstall(force=False)

    captured = capsys.readouterr()
    assert ".claude/skills/backlog/SKILL.md" in captured.out
    assert ".claude/skills/ievo/SKILL.md" in captured.out


def test_uninstall_user_declines_skills_preserved(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Declining prompt leaves skill files intact."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    skills_dir = tmp_path / ".claude" / "skills"
    (skills_dir / "backlog").mkdir(parents=True)
    (skills_dir / "backlog" / "SKILL.md").write_text("# backlog\n")
    (skills_dir / "ievo").mkdir(parents=True)
    (skills_dir / "ievo" / "SKILL.md").write_text("# ievo\n")

    with patch("typer.confirm", return_value=False), pytest.raises(typer.Exit):
        uninstall(force=False)

    # Files still exist
    assert (skills_dir / "backlog" / "SKILL.md").exists()
    assert (skills_dir / "ievo" / "SKILL.md").exists()


def test_uninstall_no_skills_dir_no_crash(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """--force with no .claude/skills/ does not crash."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    # Create at least one file so uninstall has something to do
    hooks_dir = tmp_path / ".claude" / "hooks"
    hooks_dir.mkdir(parents=True, exist_ok=True)
    (hooks_dir / "precompact_save.py").write_text("# hook\n")

    # No .claude/skills/ directory
    uninstall(force=True)
    # Should succeed without error


def test_uninstall_skills_skill_md_missing_in_subdir(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    """Subdirs without SKILL.md not listed or removed."""
    _make_project(tmp_path)
    monkeypatch.chdir(tmp_path)

    skills_dir = tmp_path / ".claude" / "skills"
    # Valid skill
    (skills_dir / "backlog").mkdir(parents=True)
    (skills_dir / "backlog" / "SKILL.md").write_text("# backlog\n")
    # Orphan dir (no SKILL.md)
    (skills_dir / "orphan").mkdir(parents=True)

    with patch("typer.confirm", return_value=False), pytest.raises(typer.Exit):
        uninstall(force=False)

    captured = capsys.readouterr()
    # Backlog listed
    assert ".claude/skills/backlog/SKILL.md" in captured.out
    # Orphan NOT listed
    assert ".claude/skills/orphan" not in captured.out

    # Now uninstall --force
    uninstall(force=True)

    # Backlog removed
    assert not (skills_dir / "backlog" / "SKILL.md").exists()
    # Orphan still exists (not removed)
    assert (skills_dir / "orphan").exists()


def test_init_then_uninstall_skills_roundtrip(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Full roundtrip: init → uninstall → verify skills absent."""
    from unittest.mock import patch as _patch

    monkeypatch.chdir(tmp_path)
    fake_home = tmp_path / ".fake-ievo"
    fake_home.mkdir()

    with (
        _patch("ievo.commands.install.ensure_home", return_value=fake_home),
        _patch("ievo.core.global_config.get_config", return_value="gh_cli"),
    ):
        from ievo.commands.install import install

        install(name=".")

    # Skills should exist after init
    skills_dir = tmp_path / ".claude" / "skills"
    assert (skills_dir / "backlog" / "SKILL.md").exists()
    assert (skills_dir / "ievo" / "SKILL.md").exists()

    uninstall(force=True)

    # Skills should be gone after uninstall
    assert not (skills_dir / "backlog" / "SKILL.md").exists()
    assert not (skills_dir / "ievo" / "SKILL.md").exists()
    assert not skills_dir.exists()

    # manifest removed — project is now uninitialized
    assert not (tmp_path / ".ievo" / "manifest.yaml").exists()
    # .ievo/ dir preserved (contains IEVO.md from init templates)
    assert (tmp_path / ".ievo").is_dir()


def test_uninstall_then_reinit_restores_skills(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """After uninstall + re-init: skills present again (manifest removed by uninstall)."""
    from unittest.mock import patch as _patch

    monkeypatch.chdir(tmp_path)
    fake_home = tmp_path / ".fake-ievo"
    fake_home.mkdir()

    with (
        _patch("ievo.commands.install.ensure_home", return_value=fake_home),
        _patch("ievo.core.global_config.get_config", return_value="gh_cli"),
    ):
        from ievo.commands.install import install

        # First init
        install(name=".")

    # Uninstall — removes manifest automatically
    uninstall(force=True)

    # Manifest should be removed by uninstall
    assert not (tmp_path / ".ievo" / "manifest.yaml").exists()

    with (
        _patch("ievo.commands.install.ensure_home", return_value=fake_home),
        _patch("ievo.core.global_config.get_config", return_value="gh_cli"),
    ):
        # Re-init — works because manifest is gone
        install(name=".")

    # Skills should be present again
    skills_dir = tmp_path / ".claude" / "skills"
    assert (skills_dir / "backlog" / "SKILL.md").exists()
    assert (skills_dir / "ievo" / "SKILL.md").exists()
