# -*- coding: utf-8 -*-
"""Tests the json history backend."""
# pylint: disable=protected-access
import os
import shlex

import pytest

from xonsh.lazyjson import LazyJSON
from xonsh.history.dummy import DummyHistory
from xonsh.history.json import JsonHistory
from xonsh.history.main import history_main, _xh_parse_args, construct_history


CMDS = ["ls", "cat hello kitty", "abc", "def", "touch me", "grep from me"]


@pytest.fixture
def hist():
    h = JsonHistory(
        filename="xonsh-HISTORY-TEST.json", here="yup", sessionid="SESSIONID", gc=False
    )
    yield h
    os.remove(h.filename)


def test_hist_init(hist):
    """Test initialization of the shell history."""
    with LazyJSON(hist.filename) as lj:
        obs = lj["here"]
    assert "yup" == obs


def test_hist_append(hist, xonsh_builtins):
    """Verify appending to the history works."""
    xonsh_builtins.__xonsh__.env["HISTCONTROL"] = set()
    hf = hist.append({"inp": "still alive", "rtn": 0})
    assert hf is None
    assert "still alive" == hist.buffer[0]["inp"]
    assert 0 == hist.buffer[0]["rtn"]
    assert 0 == hist.rtns[-1]
    hf = hist.append({"inp": "dead now", "rtn": 1})
    assert "dead now" == hist.buffer[1]["inp"]
    assert 1 == hist.buffer[1]["rtn"]
    assert 1 == hist.rtns[-1]
    hf = hist.append({"inp": "reborn", "rtn": 0})
    assert "reborn" == hist.buffer[2]["inp"]
    assert 0 == hist.buffer[2]["rtn"]
    assert 0 == hist.rtns[-1]


def test_hist_flush(hist, xonsh_builtins):
    """Verify explicit flushing of the history works."""
    hf = hist.flush()
    assert hf is None
    xonsh_builtins.__xonsh__.env["HISTCONTROL"] = set()
    hist.append({"inp": "still alive?", "rtn": 0, "out": "yes"})
    hf = hist.flush()
    assert hf is not None
    while hf.is_alive():
        pass
    with LazyJSON(hist.filename) as lj:
        assert len(lj["cmds"]) == 1
        cmd = lj["cmds"][0]
        assert cmd["inp"] == "still alive?"
        assert not cmd.get("out", None)


def test_hist_flush_with_store_stdout(hist, xonsh_builtins):
    """Verify explicit flushing of the history works."""
    hf = hist.flush()
    assert hf is None
    xonsh_builtins.__xonsh__.env["HISTCONTROL"] = set()
    xonsh_builtins.__xonsh__.env["XONSH_STORE_STDOUT"] = True
    hist.append({"inp": "still alive?", "rtn": 0, "out": "yes"})
    hf = hist.flush()
    assert hf is not None
    while hf.is_alive():
        pass
    with LazyJSON(hist.filename) as lj:
        assert len(lj["cmds"]) == 1
        assert lj["cmds"][0]["inp"] == "still alive?"
        assert lj["cmds"][0]["out"].strip() == "yes"


def test_hist_flush_with_hist_control(hist, xonsh_builtins):
    """Verify explicit flushing of the history works."""
    hf = hist.flush()
    assert hf is None
    xonsh_builtins.__xonsh__.env["HISTCONTROL"] = "ignoredups,ignoreerr"
    hist.append({"inp": "ls foo1", "rtn": 0})
    hist.append({"inp": "ls foo1", "rtn": 1})
    hist.append({"inp": "ls foo1", "rtn": 0})
    hist.append({"inp": "ls foo2", "rtn": 2})
    hist.append({"inp": "ls foo3", "rtn": 0})
    hf = hist.flush()
    assert hf is not None
    while hf.is_alive():
        pass
    assert len(hist.buffer) == 0
    with LazyJSON(hist.filename) as lj:
        cmds = list(lj["cmds"])
        assert len(cmds) == 2
        assert [x["inp"] for x in cmds] == ["ls foo1", "ls foo3"]
        assert [x["rtn"] for x in cmds] == [0, 0]


def test_cmd_field(hist, xonsh_builtins):
    # in-memory
    xonsh_builtins.__xonsh__.env["HISTCONTROL"] = set()
    hf = hist.append({"inp": "ls foo", "rtn": 1})
    assert hf is None
    assert 1 == hist.rtns[0]
    assert 1 == hist.rtns[-1]
    assert None == hist.outs[-1]
    # slice
    assert [1] == hist.rtns[:]
    # on disk
    hf = hist.flush()
    assert hf is not None
    assert 1 == hist.rtns[0]
    assert 1 == hist.rtns[-1]
    assert None == hist.outs[-1]


@pytest.mark.parametrize(
    "inp, commands, offset",
    [
        ("", CMDS, (0, 1)),
        ("-r", list(reversed(CMDS)), (len(CMDS) - 1, -1)),
        ("0", CMDS[0:1], (0, 1)),
        ("1", CMDS[1:2], (1, 1)),
        ("-2", CMDS[-2:-1], (len(CMDS) - 2, 1)),
        ("1:3", CMDS[1:3], (1, 1)),
        ("1::2", CMDS[1::2], (1, 2)),
        ("-4:-2", CMDS[-4:-2], (len(CMDS) - 4, 1)),
    ],
)
def test_show_cmd_numerate(inp, commands, offset, hist, xonsh_builtins, capsys):
    """Verify that CLI history commands work."""
    base_idx, step = offset
    xonsh_builtins.__xonsh__.history = hist
    xonsh_builtins.__xonsh__.env["HISTCONTROL"] = set()
    for ts, cmd in enumerate(CMDS):  # populate the shell history
        hist.append({"inp": cmd, "rtn": 0, "ts": (ts + 1, ts + 1.5)})

    exp = (
        "{}: {}".format(base_idx + idx * step, cmd)
        for idx, cmd in enumerate(list(commands))
    )
    exp = "\n".join(exp)

    history_main(["show", "-n"] + shlex.split(inp))
    out, err = capsys.readouterr()
    assert out.rstrip() == exp


def test_histcontrol(hist, xonsh_builtins):
    """Test HISTCONTROL=ignoredups,ignoreerr"""

    xonsh_builtins.__xonsh__.env["HISTCONTROL"] = "ignoredups,ignoreerr"
    assert len(hist.buffer) == 0

    # An error, buffer remains empty
    hist.append({"inp": "ls foo", "rtn": 2})
    assert len(hist.buffer) == 1
    assert hist.rtns[-1] == 2
    assert hist.inps[-1] == "ls foo"

    # Success
    hist.append({"inp": "ls foobazz", "rtn": 0})
    assert len(hist.buffer) == 2
    assert "ls foobazz" == hist.buffer[-1]["inp"]
    assert 0 == hist.buffer[-1]["rtn"]
    assert hist.rtns[-1] == 0
    assert hist.inps[-1] == "ls foobazz"

    # Error
    hist.append({"inp": "ls foo", "rtn": 2})
    assert len(hist.buffer) == 3
    assert "ls foo" == hist.buffer[-1]["inp"]
    assert 2 == hist.buffer[-1]["rtn"]
    assert hist.rtns[-1] == 2
    assert hist.inps[-1] == "ls foo"

    # File now exists, success
    hist.append({"inp": "ls foo", "rtn": 0})
    assert len(hist.buffer) == 4
    assert "ls foo" == hist.buffer[-1]["inp"]
    assert 0 == hist.buffer[-1]["rtn"]
    assert hist.rtns[-1] == 0
    assert hist.inps[-1] == "ls foo"

    # Success
    hist.append({"inp": "ls", "rtn": 0})
    assert len(hist.buffer) == 5
    assert "ls" == hist.buffer[-1]["inp"]
    assert 0 == hist.buffer[-1]["rtn"]
    assert hist.rtns[-1] == 0
    assert hist.inps[-1] == "ls"

    # Dup
    hist.append({"inp": "ls", "rtn": 0})
    assert len(hist.buffer) == 6
    assert hist.rtns[-1] == 0
    assert hist.inps[-1] == "ls"

    # Success
    hist.append({"inp": "/bin/ls", "rtn": 0})
    assert len(hist.buffer) == 7
    assert "/bin/ls" == hist.buffer[-1]["inp"]
    assert 0 == hist.buffer[-1]["rtn"]
    assert hist.rtns[-1] == 0
    assert hist.inps[-1] == "/bin/ls"

    # Error
    hist.append({"inp": "ls bazz", "rtn": 1})
    assert len(hist.buffer) == 8
    assert "ls bazz" == hist.buffer[-1]["inp"]
    assert 1 == hist.buffer[-1]["rtn"]
    assert hist.rtns[-1] == 1
    assert hist.inps[-1] == "ls bazz"

    # Error
    hist.append({"inp": "ls bazz", "rtn": -1})
    assert len(hist.buffer) == 9
    assert "ls bazz" == hist.buffer[-1]["inp"]
    assert -1 == hist.buffer[-1]["rtn"]
    assert hist.rtns[-1] == -1
    assert hist.inps[-1] == "ls bazz"


@pytest.mark.parametrize("args", ["-h", "--help", "show -h", "show --help"])
def test_parse_args_help(args, capsys):
    with pytest.raises(SystemExit):
        args = _xh_parse_args(shlex.split(args))
    assert "show this help message and exit" in capsys.readouterr()[0]


@pytest.mark.parametrize(
    "args, exp",
    [
        ("", ("show", "session", [], False, False)),
        ("1:5", ("show", "session", ["1:5"], False, False)),
        ("show", ("show", "session", [], False, False)),
        ("show 15", ("show", "session", ["15"], False, False)),
        ("show bash 3:5 15:66", ("show", "bash", ["3:5", "15:66"], False, False)),
        ("show -r", ("show", "session", [], False, True)),
        ("show -rn bash", ("show", "bash", [], True, True)),
        ("show -n -r -30:20", ("show", "session", ["-30:20"], True, True)),
        ("show -n zsh 1:2:3", ("show", "zsh", ["1:2:3"], True, False)),
    ],
)
def test_parser_show(args, exp):
    # use dict instead of argparse.Namespace for pretty pytest diff
    exp_ns = {
        "action": exp[0],
        "session": exp[1],
        "slices": exp[2],
        "numerate": exp[3],
        "reverse": exp[4],
        "start_time": None,
        "end_time": None,
        "datetime_format": None,
        "timestamp": False,
        "null_byte": False,
    }
    ns = _xh_parse_args(shlex.split(args))
    assert ns.__dict__ == exp_ns


@pytest.mark.parametrize(
    "index, exp",
    [
        (-1, ("grep from me", "out", 0, (5, 6))),
        (1, ("cat hello kitty", "out", 0, (1, 2))),
        (
            slice(1, 3),
            [("cat hello kitty", "out", 0, (1, 2)), ("abc", "out", 0, (2, 3))],
        ),
    ],
)
def test_history_getitem(index, exp, hist, xonsh_builtins):
    xonsh_builtins.__xonsh__.env["HISTCONTROL"] = set()
    attrs = ("inp", "out", "rtn", "ts")

    for ts, cmd in enumerate(CMDS):  # populate the shell history
        entry = {k: v for k, v in zip(attrs, [cmd, "out", 0, (ts, ts + 1)])}
        hist.append(entry)

    entry = hist[index]
    if isinstance(entry, list):
        assert [(e.cmd, e.out, e.rtn, e.ts) for e in entry] == exp
    else:
        assert (entry.cmd, entry.out, entry.rtn, entry.ts) == exp


def test_construct_history_str(xonsh_builtins):
    xonsh_builtins.__xonsh__.env["XONSH_HISTORY_BACKEND"] = "dummy"
    assert isinstance(construct_history(), DummyHistory)


def test_construct_history_class(xonsh_builtins):
    xonsh_builtins.__xonsh__.env["XONSH_HISTORY_BACKEND"] = DummyHistory
    assert isinstance(construct_history(), DummyHistory)


def test_construct_history_instance(xonsh_builtins):
    xonsh_builtins.__xonsh__.env["XONSH_HISTORY_BACKEND"] = DummyHistory()
    assert isinstance(construct_history(), DummyHistory)
