"""Tests foreign shells."""

import os
import subprocess

import pytest  # noqa F401

from xonsh.foreign_shells import foreign_shell_data, parse_aliases, parse_env
from xonsh.pytest.tools import skip_if_on_unix, skip_if_on_windows


def test_parse_env():
    exp = {"X": "YES", "Y": "NO"}
    s = "some garbage\n__XONSH_ENV_BEG__\nY=NO\nX=YES\n__XONSH_ENV_END__\nmore filth"
    obs = parse_env(s)
    assert exp == obs


def test_parse_env_newline():
    exp = {"X": "YES", "Y": "NO", "PROMPT": "why\nme "}
    s = (
        "some garbage\n"
        "__XONSH_ENV_BEG__\n"
        "Y=NO\n"
        "PROMPT=why\nme \n"
        "X=YES\n"
        "__XONSH_ENV_END__\n"
        "more filth"
    )
    obs = parse_env(s)
    assert exp == obs


def test_parse_env_equals():
    exp = {"X": "YES", "Y": "NO", "LS_COLORS": "*.tar=5"}
    s = (
        "some garbage\n"
        "__XONSH_ENV_BEG__\n"
        "Y=NO\n"
        "LS_COLORS=*.tar=5\n"
        "X=YES\n"
        "__XONSH_ENV_END__\n"
        "more filth"
    )
    obs = parse_env(s)
    assert exp == obs


def test_parse_env_null_separated():
    """env -0 output with null bytes correctly handles multi-line values (issue #4947)."""
    exp = {
        "SIMPLE": "value",
        "BASH_FUNC_which%%": "() {  ( alias;\n eval ${which_declare} ) | /usr/bin/which\n}",
        "PATH": "/usr/bin:/bin",
    }
    s = (
        "some garbage\n"
        "__XONSH_ENV_BEG__\n"
        "SIMPLE=value\0"
        "BASH_FUNC_which%%=() {  ( alias;\n eval ${which_declare} ) | /usr/bin/which\n}\0"
        "PATH=/usr/bin:/bin\0"
        "__XONSH_ENV_END__\n"
        "more filth"
    )
    obs = parse_env(s)
    assert exp == obs


def test_parse_env_multiline_fallback():
    """Fallback line-separated parsing handles multi-line values (no env -0)."""
    exp = {
        "SIMPLE": "value",
        "BASH_FUNC_which%%": "() {  ( alias;\n eval ${which_declare} ) | /usr/bin/which\n}",
        "PATH": "/usr/bin:/bin",
    }
    s = (
        "some garbage\n"
        "__XONSH_ENV_BEG__\n"
        "SIMPLE=value\n"
        "BASH_FUNC_which%%=() {  ( alias;\n"
        " eval ${which_declare} ) | /usr/bin/which\n"
        "}\n"
        "PATH=/usr/bin:/bin\n"
        "__XONSH_ENV_END__\n"
        "more filth"
    )
    obs = parse_env(s)
    assert exp == obs


def test_parse_env_empty_value():
    """Empty env var value is preserved."""
    exp = {"EMPTY": "", "X": "1"}
    s = "__XONSH_ENV_BEG__\nEMPTY=\nX=1\n__XONSH_ENV_END__"
    obs = parse_env(s)
    assert exp == obs


def test_parse_env_null_empty_value():
    """Empty env var value is preserved with env -0."""
    exp = {"EMPTY": "", "X": "1"}
    s = "__XONSH_ENV_BEG__\nEMPTY=\0X=1\0__XONSH_ENV_END__"
    obs = parse_env(s)
    assert exp == obs


def test_parse_aliases():
    exp = {
        "x": ["yes", "-1"],
        "y": ["echo", "no"],
        "z": ["echo", "True", "&&", "echo", "Next", "||", "echo", "False"],
    }
    s = (
        "some garbage\n"
        "__XONSH_ALIAS_BEG__\n"
        "alias x='yes -1'\n"
        "alias y='echo    no'\n"
        "alias z='echo True && \\\n echo Next || \\\n echo False'\n"  # noqa: E261,W605
        "__XONSH_ALIAS_END__\n"
        "more filth"
    )
    obs = parse_aliases(s, "bash")
    assert exp == obs


@skip_if_on_windows
def test_foreign_bash_data():
    expenv = {"EMERALD": "SWORD", "MIGHTY": "WARRIOR"}
    expaliases = {"l": ["ls", "-CF"], "la": ["ls", "-A"], "ll": ["ls", "-a", "-lF"]}
    rcfile = os.path.join(os.path.dirname(__file__), "bashrc.sh")
    try:
        obsenv, obsaliases = foreign_shell_data(
            "bash", currenv=(), extra_args=("--rcfile", rcfile), safe=False
        )
    except (subprocess.CalledProcessError, FileNotFoundError):
        return
    for key, expval in expenv.items():
        assert expval == obsenv.get(key, False)
    for key, expval in expaliases.items():
        assert expval == obsaliases.get(key, False)


@skip_if_on_unix
def test_foreign_cmd_data():
    env = (("ENV_TO_BE_REMOVED", "test"),)
    batchfile = os.path.join(os.path.dirname(__file__), "batch.bat")
    source_cmd = f'call "{batchfile}"\necho off'
    try:
        obsenv, _ = foreign_shell_data(
            "cmd",
            prevcmd=source_cmd,
            currenv=env,
            interactive=False,
            sourcer="call",
            envcmd="set",
            use_tmpfile=True,
            safe=False,
        )
    except (subprocess.CalledProcessError, FileNotFoundError):
        return
    assert "ENV_TO_BE_ADDED" in obsenv
    assert obsenv["ENV_TO_BE_ADDED"] == "Hallo world"
    assert "ENV_TO_BE_REMOVED" not in obsenv


@skip_if_on_windows
def test_foreign_shell_alias_args_are_escaped(xession):
    """Arguments to foreign shell aliases must be escaped to prevent injection."""
    from xonsh.foreign_shells import ForeignShellFunctionAlias

    alias = ForeignShellFunctionAlias(
        funcname="myfunc", shell="bash", sourcer="source", files=()
    )
    # Simulate calling with a malicious argument
    try:
        out = alias(["; echo INJECTED"], stdout=subprocess.PIPE)
    except (subprocess.CalledProcessError, FileNotFoundError):
        out = None
    # If injection happened, "INJECTED" would appear in output
    if out is not None:
        assert b"INJECTED" not in (out if isinstance(out, bytes) else b"")
