import pytest
from pathlib import Path
from unittest.mock import patch, MagicMock
import json
import stat
import os
import yaml


def _mock_passing_preflight():
    """Return a context manager that mocks PreflightChecker with all passing results."""
    from carta.install.preflight import PreflightResult, PreflightCheck

    def create_passing_result():
        """Create a PreflightResult with all critical checks passing."""
        checks = [
            PreflightCheck("python_version", "pass", "Python 3.11.0 (supported)", "environment"),
            PreflightCheck("pip_availability", "pass", "pip available", "environment"),
            PreflightCheck("virtual_environment", "pass", "Running in virtual environment", "environment"),
            PreflightCheck("network_connectivity", "pass", "Network connectivity OK", "environment"),
            PreflightCheck("docker_installed", "pass", "Docker installed", "infrastructure"),
            PreflightCheck("docker_running", "pass", "Docker daemon running", "infrastructure"),
            PreflightCheck("qdrant_running", "pass", "Qdrant ready at http://localhost:6333", "infrastructure"),
            PreflightCheck("ollama_installed", "pass", "Ollama installed", "infrastructure"),
            PreflightCheck("ollama_running", "pass", "Ollama server running", "infrastructure"),
            PreflightCheck("ports_available", "pass", "Required ports available", "infrastructure"),
        ]
        return PreflightResult(checks)

    return patch("carta.install.preflight.PreflightChecker.run", return_value=create_passing_result())


def _mock_unavailable_qdrant_preflight():
    """Return a context manager that mocks PreflightChecker with Qdrant unavailable (warning, not blocking)."""
    from carta.install.preflight import PreflightResult, PreflightCheck

    def create_warning_result():
        """Create a PreflightResult with Qdrant unavailable as warning (not critical failure)."""
        checks = [
            PreflightCheck("python_version", "pass", "Python 3.11.0 (supported)", "environment"),
            PreflightCheck("pip_availability", "pass", "pip available", "environment"),
            PreflightCheck("virtual_environment", "pass", "Running in virtual environment", "environment"),
            PreflightCheck("network_connectivity", "pass", "Network connectivity OK", "environment"),
            PreflightCheck("docker_installed", "warn", "Docker not installed (optional but recommended)", "infrastructure", fixable=False),
            PreflightCheck("qdrant_running", "warn", "Qdrant not running", "infrastructure", fixable=False),
            PreflightCheck("ollama_installed", "warn", "Ollama not found (optional)", "infrastructure", fixable=False),
            PreflightCheck("ports_available", "pass", "Required ports available", "infrastructure"),
        ]
        return PreflightResult(checks)

    return patch("carta.install.preflight.PreflightChecker.run", return_value=create_warning_result())


def test_bootstrap_creates_carta_dir(tmp_path):
    from carta.install.bootstrap import run_bootstrap
    with _mock_passing_preflight(), \
         patch("carta.install.bootstrap._register_hooks"), \
         patch("carta.install.bootstrap._create_qdrant_collections"):
        run_bootstrap(tmp_path)
    assert (tmp_path / ".carta").exists()
    assert (tmp_path / ".carta" / "config.yaml").exists()

def test_bootstrap_config_has_all_fields(tmp_path):
    from carta.install.bootstrap import run_bootstrap
    with _mock_passing_preflight(), \
         patch("carta.install.bootstrap._register_hooks"), \
         patch("carta.install.bootstrap._create_qdrant_collections"):
        run_bootstrap(tmp_path)
    cfg = yaml.safe_load((tmp_path / ".carta" / "config.yaml").read_text())
    assert "project_name" in cfg
    assert "qdrant_url" in cfg
    assert "modules" in cfg
    assert "embed" in cfg, "embed block missing — _write_config must merge DEFAULTS"
    assert "proactive_recall" in cfg, "proactive_recall block missing"
    assert "cross_project_recall" in cfg, "cross_project_recall block missing"
    assert "contradiction_types" in cfg, "contradiction_types missing"


def test_bootstrap_updates_gitignore(tmp_path):
    (tmp_path / ".gitignore").write_text("node_modules/\n")
    from carta.install.bootstrap import run_bootstrap
    with _mock_passing_preflight(), \
         patch("carta.install.bootstrap._register_hooks"), \
         patch("carta.install.bootstrap._create_qdrant_collections"):
        run_bootstrap(tmp_path)
    content = (tmp_path / ".gitignore").read_text()
    assert ".carta/scan-results.json" in content


def test_bootstrap_appends_claude_md(tmp_path):
    (tmp_path / "CLAUDE.md").write_text("# My Project\n")
    from carta.install.bootstrap import run_bootstrap
    with _mock_passing_preflight(), \
         patch("carta.install.bootstrap._register_hooks"), \
         patch("carta.install.bootstrap._create_qdrant_collections"):
        run_bootstrap(tmp_path)
    content = (tmp_path / "CLAUDE.md").read_text()
    assert "Carta is active" in content


def test_bootstrap_creates_namespaced_collections(tmp_path):
    from carta.install.bootstrap import run_bootstrap
    mock_create = MagicMock()
    with _mock_passing_preflight(), \
         patch("carta.install.bootstrap._register_hooks"), \
         patch("carta.install.bootstrap._create_qdrant_collections", mock_create):
        run_bootstrap(tmp_path)
    project_name = mock_create.call_args[0][0]
    assert isinstance(project_name, str) and len(project_name) > 0

def test_create_qdrant_collections_uses_namespaced_names():
    from carta.install.bootstrap import _create_qdrant_collections
    with patch("carta.install.bootstrap.requests") as mock_req:
        mock_req.put.return_value.status_code = 200
        _create_qdrant_collections("my-project", "http://localhost:6333")
    called_urls = [call.args[0] for call in mock_req.put.call_args_list]
    assert any("my-project_doc" in url for url in called_urls)
    assert any("my-project_session" in url for url in called_urls)
    assert any("my-project_quirk" in url for url in called_urls)

def test_bootstrap_continues_if_qdrant_unavailable(tmp_path):
    """bootstrap should warn and continue (not exit) when Qdrant is unreachable but not critically failing."""
    from carta.install.bootstrap import run_bootstrap
    with _mock_unavailable_qdrant_preflight(), \
         patch("carta.install.bootstrap._remove_plugin_cache", return_value=True), \
         patch("carta.install.bootstrap._create_qdrant_collections", return_value=True), \
         patch("carta.install.bootstrap._update_gitignore"), \
         patch("carta.install.bootstrap._create_mcp_configs"):
        try:
            run_bootstrap(tmp_path)
        except SystemExit as e:
            raise AssertionError(f"bootstrap exited with code {e.code} when Qdrant was unreachable") from e


def test_bootstrap_uses_qdrant_url_from_env(tmp_path):
    from carta.install.bootstrap import run_bootstrap

    custom_url = "http://qdrant.example:7000"
    with patch.dict(os.environ, {"CARTA_QDRANT_URL": custom_url}, clear=False), \
         _mock_passing_preflight(), \
         patch("carta.install.bootstrap._register_hooks"), \
         patch("carta.install.bootstrap._update_gitignore"), \
         patch("carta.install.bootstrap._append_claude_md"), \
         patch("carta.install.bootstrap.shutil.copytree"), \
         patch("carta.install.bootstrap._create_qdrant_collections") as mock_create:
        run_bootstrap(tmp_path)

    mock_create.assert_called_once()
    assert mock_create.call_args[0][1] == custom_url

    cfg = yaml.safe_load((tmp_path / ".carta" / "config.yaml").read_text())
    assert cfg["qdrant_url"] == custom_url


@pytest.mark.skip(reason="Mocking issue with shutil.copytree - copytree not being captured (pre-existing)")
def test_bootstrap_copytree_ignores_non_runtime_artifacts(tmp_path):
    from carta.install.bootstrap import run_bootstrap

    with _mock_passing_preflight(), \
         patch("carta.install.bootstrap._register_hooks"), \
         patch("carta.install.bootstrap._create_qdrant_collections"), \
         patch("carta.install.bootstrap._update_gitignore"), \
         patch("carta.install.bootstrap._append_claude_md"), \
         patch("carta.install.bootstrap.shutil.copytree") as mock_copytree:
        run_bootstrap(tmp_path)

    mock_copytree.assert_called_once()
    ignore_fn = mock_copytree.call_args.kwargs["ignore"]
    ignored = ignore_fn(
        str(tmp_path),
        ["tests", "__pycache__", "module.pyc", "module.pyo", "cli.py", "hooks"],
    )
    assert "tests" in ignored
    assert "__pycache__" in ignored
    assert "module.pyc" in ignored
    assert "module.pyo" in ignored
    assert "cli.py" not in ignored
    assert "hooks" not in ignored


def test_register_hooks_copies_scripts_locally(tmp_path):
    from carta.install.bootstrap import _register_hooks

    _register_hooks(tmp_path)

    hooks_dir = tmp_path / ".carta" / "hooks"
    assert (hooks_dir / "carta-prompt-hook.sh").exists()
    assert (hooks_dir / "carta-stop-hook.sh").exists()


def test_register_hooks_sets_executable_and_does_not_write_claude_settings(tmp_path):
    """_register_hooks copies scripts as executable; does NOT write .claude/settings.json
    (Claude Code hook registration is now plugin-native via hooks/hooks.json)."""
    from carta.install.bootstrap import _register_hooks

    _register_hooks(tmp_path)

    hooks_dir = tmp_path / ".carta" / "hooks"
    prompt_hook = hooks_dir / "carta-prompt-hook.sh"
    stop_hook = hooks_dir / "carta-stop-hook.sh"

    prompt_mode = prompt_hook.stat().st_mode
    stop_mode = stop_hook.stat().st_mode
    assert prompt_mode & stat.S_IXUSR
    assert prompt_mode & stat.S_IXGRP
    assert prompt_mode & stat.S_IXOTH
    assert stop_mode & stat.S_IXUSR
    assert stop_mode & stat.S_IXGRP
    assert stop_mode & stat.S_IXOTH

    # .claude/settings.json must NOT be written — plugin-native handles this
    settings_path = tmp_path / ".claude" / "settings.json"
    assert not settings_path.exists(), \
        "_register_hooks must not write .claude/settings.json (plugin-native handles hooks)"


# ---------------------------------------------------------------------------
# Plugin cache cleanup tests (MCP-07)
# ---------------------------------------------------------------------------

def test_remove_plugin_cache_removes_both_paths(tmp_path):
    """_remove_plugin_cache() removes both known cache dirs and returns True."""
    from carta.install.bootstrap import _remove_plugin_cache

    # Create both cache dirs
    path_a = tmp_path / ".claude/plugins/carta"
    path_b = tmp_path / ".claude/plugins/cache/carta-cc"
    path_a.mkdir(parents=True)
    path_b.mkdir(parents=True)

    with patch("carta.install.bootstrap.Path.home", return_value=tmp_path):
        result = _remove_plugin_cache()

    assert result is True
    assert not path_a.exists()
    assert not path_b.exists()


def test_remove_plugin_cache_noop_when_absent(tmp_path):
    """_remove_plugin_cache() returns True when neither cache dir exists."""
    from carta.install.bootstrap import _remove_plugin_cache

    with patch("carta.install.bootstrap.Path.home", return_value=tmp_path):
        result = _remove_plugin_cache()

    assert result is True


def test_remove_plugin_cache_assertion_on_residue(tmp_path, capsys):
    """_remove_plugin_cache() returns False and prints error when residue remains."""
    from carta.install.bootstrap import _remove_plugin_cache

    # Create one cache dir
    path_a = tmp_path / ".claude/plugins/carta"
    path_a.mkdir(parents=True)

    with patch("carta.install.bootstrap.Path.home", return_value=tmp_path), \
         patch("carta.install.bootstrap.shutil.rmtree"):  # rmtree is a no-op, residue remains
        result = _remove_plugin_cache()

    assert result is False
    captured = capsys.readouterr()
    assert "plugin cache residue" in captured.err


def test_install_skills_removed():
    """_install_skills() must not exist — replaced by _remove_plugin_cache()."""
    import carta.install.bootstrap as bootstrap_module
    assert not hasattr(bootstrap_module, "_install_skills"), \
        "_install_skills should have been removed; use _remove_plugin_cache() instead"
