"""Regression tests for the platform default search paths used to
locate the ``bash_completion`` framework script.

The defaults used to live in two places:

* ``xonsh.platform.BASH_COMPLETIONS_DEFAULT`` — the canonical default
  surfaced via the ``$BASH_COMPLETIONS`` env var.
* ``xonsh.completers.bash_completion._bash_completion_paths_default``
  — a standalone fallback inside the bash-completion bridge.

They drifted: ``/opt/homebrew/...`` was added to the canonical list
but forgotten in the bridge fallback, leaving Apple Silicon Macs
without auto-discovery. The bridge fallback now delegates to the
canonical list — these tests pin both ends down so they can't drift
again.
"""

import os
import pathlib
import shutil
import subprocess

import pytest

from xonsh import platform as plat_mod
from xonsh.completers import bash_completion as bc_mod


def test_bridge_fallback_delegates_to_canonical_default():
    """The bridge fallback returns the same tuple as the canonical
    env-var default — they are bound, not parallel copies."""
    assert bc_mod._bash_completion_paths_default() == tuple(
        plat_mod.BASH_COMPLETIONS_DEFAULT
    )


def test_get_bash_completions_source_loads_framework_then_user_dir(tmp_path):
    """User completion directories supplement the first framework script.

    This lets defaults such as Homebrew's ``bash_completion`` coexist
    with extra user scripts in ``~/.bash_completions``.
    """
    homebrew = tmp_path / "homebrew" / "bash_completion"
    fallback = tmp_path / "fallback" / "bash_completion"
    user_dir = tmp_path / ".bash_completions"
    custom_a = user_dir / "a_custom"
    custom_b = user_dir / "b_custom"
    for path in (homebrew, fallback, custom_a, custom_b):
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text("# test completion\n")

    source = bc_mod._get_bash_completions_source([homebrew, fallback, user_dir])

    assert source == "\n".join(
        bc_mod._source_bash_completion_file(path)
        for path in (homebrew, custom_a, custom_b)
    )
    assert fallback.as_posix() not in source


def test_source_bash_completion_file_uses_msys_path_on_windows(monkeypatch):
    """Native Windows paths must be translated before Bash's source builtin.

    Git/MSYS Bash path conversion does not apply to shell builtins, so
    ``source "C:/..."`` is not portable there.
    """
    monkeypatch.setattr(bc_mod.platform, "system", lambda: "Windows")

    path = pathlib.PureWindowsPath(
        r"C:\Users\runneradmin\AppData\Local\Temp\.bash_completions\foo"
    )

    expected = "source /c/Users/runneradmin/AppData/Local/Temp/.bash_completions/foo"
    assert bc_mod._source_bash_completion_file(path) == expected


def test_bash_completions_executes_user_dir_scripts(tmp_path):
    """Scripts in supplemental completion directories must affect results."""
    command = bc_mod._bash_command()
    if shutil.which(command) is None:
        pytest.skip("bash not found on PATH")
    if subprocess.run([command, "-c", "complete -p"], capture_output=True).returncode:
        # Bash built without readline (e.g. the stdenv bash in the Nix
        # build sandbox) has programmable completion compiled out — the
        # ``complete`` builtin does not exist, so the bridge can only
        # ever return an empty set. Nothing to test against.
        pytest.skip("bash lacks programmable completion")

    framework = tmp_path / "bash_completion"
    user_dir = tmp_path / ".bash_completions"
    user_script = user_dir / "foo"
    framework.write_text("# empty test framework\n")
    user_dir.mkdir()
    user_script.write_text(
        """
_foo_completion()
{
    COMPREPLY=(bar)
}
complete -F _foo_completion foo
"""
    )

    completions, lprefix = bc_mod.bash_completions(
        "",
        "foo ",
        4,
        4,
        paths=[framework, user_dir],
        command=command,
    )

    assert completions == {"bar "}
    assert lprefix == 0


def test_hidden_files_in_user_dir_are_skipped(tmp_path):
    """Hidden files (e.g. .DS_Store) inside completion directories
    must not be sourced."""
    framework = tmp_path / "bash_completion"
    user_dir = tmp_path / ".bash_completions"
    framework.write_text("# framework\n")
    user_dir.mkdir()
    (user_dir / ".DS_Store").write_bytes(b"\x00\x00\x00\x01Bud1")
    (user_dir / ".hidden").write_text("# hidden\n")
    (user_dir / "visible").write_text("# visible\n")

    source = bc_mod._get_bash_completions_source([framework, user_dir])

    assert ".DS_Store" not in source
    assert ".hidden" not in source
    assert "visible" in source


def test_empty_string_in_paths_is_ignored(tmp_path):
    """An empty string in paths must not be treated as the current directory."""
    framework = tmp_path / "bash_completion"
    framework.write_text("# framework\n")

    source = bc_mod._get_bash_completions_source(["", framework])

    assert source == bc_mod._source_bash_completion_file(framework)


def test_source_path_quotes_special_characters():
    """Paths with spaces or quotes must be properly shell-quoted."""
    path = pathlib.Path("/home/user/my dir/completions")
    result = bc_mod._bash_source_path(path)
    assert " " not in result or result.startswith("'")

    path_quote = pathlib.Path('/home/user/my"dir/file')
    result_quote = bc_mod._bash_source_path(path_quote)
    assert '"' not in result_quote or result_quote.startswith("'")


def test_get_bash_completions_sources_is_cached(tmp_path):
    """Repeated calls with the same paths must not re-read the filesystem."""
    framework = tmp_path / "bash_completion"
    user_dir = tmp_path / ".bash_completions"
    framework.write_text("# framework\n")
    user_dir.mkdir()
    (user_dir / "foo").write_text("# foo\n")

    bc_mod._get_bash_completions_sources.cache_clear()
    paths = (str(framework), str(user_dir))

    bc_mod._get_bash_completions_sources(paths)
    bc_mod._get_bash_completions_sources(paths)
    info = bc_mod._get_bash_completions_sources.cache_info()
    assert info.hits >= 1
    assert info.misses == 1


def test_canonical_darwin_default_covers_all_install_prefixes():
    """All four mac install prefixes (Homebrew Intel + Apple Silicon,
    MacPorts, Nix) must appear so the bridge auto-discovers
    bash-completion regardless of which package manager the user
    chose. Regression for two distinct bugs:

    * ``/opt/homebrew/...`` was missing — broke Apple Silicon
      Homebrew users.
    * MacPorts paths were never in the defaults despite an explicit
      docs section (``docs/platforms.rst``) telling MacPorts users to
      add them by hand.
    """
    if not plat_mod.ON_DARWIN:
        # The lazyobject realises platform-conditionally at import
        # time; on Linux/Windows CI there's nothing meaningful to
        # check here. The cross-binding test above still runs.
        import pytest

        pytest.skip("Darwin-only assertion")
    paths = tuple(plat_mod.BASH_COMPLETIONS_DEFAULT)
    # Homebrew Intel + Apple Silicon
    assert "/usr/local/share/bash-completion/bash_completion" in paths
    assert "/opt/homebrew/share/bash-completion/bash_completion" in paths
    # MacPorts
    assert "/opt/local/share/bash-completion/bash_completion" in paths
    # User-defined
    assert os.path.expanduser("~/.bash_completions") in paths
    # Nix shared profile (nix-darwin)
    assert "/run/current-system/sw/share/bash-completion/bash_completion" in paths


def test_canonical_linux_default_covers_brew_and_nix():
    """Linux defaults probe more than just the FHS path: Linuxbrew
    (servers/CI commonly use it) and Nix (NixOS, single-user nix-env)
    are first-class. Without this, users on either get zero
    completion auto-discovery.
    """
    if not plat_mod.ON_LINUX:
        import pytest

        pytest.skip("Linux-only assertion")
    paths = tuple(plat_mod.BASH_COMPLETIONS_DEFAULT)
    assert "/usr/share/bash-completion/bash_completion" in paths
    assert "/home/linuxbrew/.linuxbrew/share/bash-completion/bash_completion" in paths
    assert "/run/current-system/sw/share/bash-completion/bash_completion" in paths


def test_canonical_bsd_default_covers_ports_prefix():
    """BSD installs bash-completion outside the base system, under
    ``/usr/local`` (FreeBSD/DragonFly ports & pkg, OpenBSD pkg) or
    ``/usr/pkg`` (NetBSD pkgsrc). Without these the default is empty
    and the bridge has nothing to source — bash completion silently
    breaks for every user on BSD.

    Only the library file ``bash_completion`` (no extension) is listed
    — the user-facing wrapper ``bash_completion.sh`` shipped by
    bash-completion 2.17+ short-circuits in non-interactive bash, so
    sourcing it from xonsh's ``bash -c`` bridge produces empty
    completions. Pin both behaviours so a future regression that adds
    the wrapper back in front gets caught.
    """
    if not plat_mod.ON_BSD:
        import pytest

        pytest.skip("BSD-only assertion")
    paths = tuple(plat_mod.BASH_COMPLETIONS_DEFAULT)
    # /usr/local — FreeBSD ports / pkg, OpenBSD pkg
    assert "/usr/local/share/bash-completion/bash_completion" in paths
    # /usr/pkg — NetBSD pkgsrc
    assert "/usr/pkg/share/bash-completion/bash_completion" in paths
    # The interactive-only wrapper must NOT be listed — it produces
    # empty completions when sourced from xonsh's non-interactive bash.
    for path in paths:
        assert not path.endswith("bash_completion.sh"), (
            f"interactive-only wrapper leaked into BASH_COMPLETIONS_DEFAULT: {path!r}"
        )
