"""Event tests"""

import inspect

import pytest

from xonsh.events import Event, EventManager, LoadEvent


@pytest.fixture
def events():
    return EventManager()


def test_event_calling(events):
    called = False

    @events.on_test
    def _(spam, **_):
        nonlocal called
        called = spam

    events.on_test.fire(spam="eggs")

    assert called == "eggs"


def test_event_returns(events):
    called = 0

    @events.on_test
    def on_test(**_):
        nonlocal called
        called += 1
        return 1

    @events.on_test
    def second(**_):
        nonlocal called
        called += 1
        return 2

    vals = events.on_test.fire()

    assert called == 2
    assert set(vals) == {1, 2}


def test_fire_iter_yields_handler_and_result(events):
    @events.on_test
    def first(**_):
        return "a"

    @events.on_test
    def second(**_):
        return "b"

    pairs = list(events.on_test.fire_iter())
    handlers = {h for h, _ in pairs}
    results = {rv for _, rv in pairs}

    assert handlers == {first, second}
    assert results == {"a", "b"}


def test_fire_iter_skips_raising_handler(events):
    @events.on_test
    def good(**_):
        return "ok"

    @events.on_test
    def bad(**_):
        raise RuntimeError("boom")

    pairs = list(events.on_test.fire_iter())

    assert pairs == [(good, "ok")]


def test_validator(events):
    called = None

    @events.on_test
    def first(n, **_):
        nonlocal called
        called += 1
        return False

    @first.validator
    def v(n):
        return n == "spam"

    @events.on_test
    def second(n, **_):
        nonlocal called
        called += 1
        return False

    called = 0
    events.on_test.fire(n="egg")
    assert called == 1

    called = 0
    events.on_test.fire(n="spam")
    assert called == 2


def test_eventdoc(events):
    docstring = "Test event"
    events.doc("on_test", docstring)

    assert inspect.getdoc(events.on_test) == docstring


def test_transmogrify(events):
    docstring = "Test event"
    events.doc("on_test", docstring)

    @events.on_test
    def func(**_):
        pass

    assert isinstance(events.on_test, Event)
    assert len(events.on_test) == 1
    assert inspect.getdoc(events.on_test) == docstring

    events.transmogrify("on_test", LoadEvent)

    assert isinstance(events.on_test, LoadEvent)
    assert len(events.on_test) == 1
    assert inspect.getdoc(events.on_test) == docstring


def test_transmogrify_by_string(events):
    docstring = "Test event"
    events.doc("on_test", docstring)

    @events.on_test
    def func(**_):
        pass

    assert isinstance(events.on_test, Event)
    assert len(events.on_test) == 1
    assert inspect.getdoc(events.on_test) == docstring

    events.transmogrify("on_test", "LoadEvent")

    assert isinstance(events.on_test, LoadEvent)
    assert len(events.on_test) == 1
    assert inspect.getdoc(events.on_test) == docstring


def test_load(events):
    events.transmogrify("on_test", "LoadEvent")
    called = 0

    @events.on_test
    def on_test(**_):
        nonlocal called
        called += 1

    assert called == 0

    events.on_test.fire()
    assert called == 1

    @events.on_test
    def second(**_):
        nonlocal called
        called += 1

    assert called == 2


def test_load_2nd_call(events):
    events.transmogrify("on_test", "LoadEvent")
    called = 0

    @events.on_test
    def on_test(**_):
        nonlocal called
        called += 1

    assert called == 0

    events.on_test.fire()
    assert called == 1

    events.on_test.fire()
    assert called == 1


def test_nested_fire_does_not_corrupt_handlers(events):
    """Nested fire must not process delayed adds/discards prematurely."""
    call_log = []
    nesting = False

    @events.on_test
    def handler_a(**_):
        nonlocal nesting
        call_log.append("A start")
        if not nesting:
            nesting = True
            events.on_test.fire()  # one level of nested fire
        call_log.append("A end")

    @events.on_test
    def handler_b(**_):
        call_log.append("B")

    events.on_test.fire()
    assert call_log.count("A start") == 2  # outer + nested
    assert call_log.count("B") == 2


def test_delayed_add_applied_after_outermost_fire(events):
    """Handler added during fire is not called until next fire."""
    call_log = []
    added = False

    @events.on_test
    def adder(**_):
        nonlocal added
        call_log.append("adder")
        if not added:
            added = True

            @events.on_test
            def late_handler(**_):
                call_log.append("late")

    events.on_test.fire()
    assert call_log == ["adder"]  # late_handler not called yet

    events.on_test.fire()
    assert "late" in call_log  # now it's registered and called


def test_typos(xession):
    for name, ev in vars(xession.builtins.events).items():
        if "pytest" in name:
            continue
        assert inspect.getdoc(ev)


def test_exists(events):
    events.doc("on_test", "Test event")
    assert events.exists("on_test")
    assert not events.exists("on_best")
