# -*- coding: utf-8 -*-

from unittest.mock import call, Mock

import pytest

from pyee import EventEmitter
from pyee.uplift import unwrap, uplift


class UpliftedEventEmitter(EventEmitter):
    pass


def test_uplift_emit():
    call_me = Mock()

    base_ee = EventEmitter()

    @base_ee.on("base_event")
    def base_handler():
        call_me("base event on base emitter")

    @base_ee.on("shared_event")
    def shared_base_handler():
        call_me("shared event on base emitter")

    uplifted_ee = uplift(UpliftedEventEmitter, base_ee)

    assert isinstance(uplifted_ee, UpliftedEventEmitter), "Returns an uplifted emitter"

    @uplifted_ee.on("uplifted_event")
    def uplifted_handler():
        call_me("uplifted event on uplifted emitter")

    @uplifted_ee.on("shared_event")
    def shared_uplifted_handler():
        call_me("shared event on uplifted emitter")

    # Events on uplifted proxy correctly
    assert uplifted_ee.emit("base_event")
    assert uplifted_ee.emit("shared_event")
    assert uplifted_ee.emit("uplifted_event")

    call_me.assert_has_calls(
        [
            call("base event on base emitter"),
            call("shared event on uplifted emitter"),
            call("shared event on base emitter"),
            call("uplifted event on uplifted emitter"),
        ]
    )

    call_me.reset_mock()

    # Events on underlying proxy correctly
    assert base_ee.emit("base_event")
    assert base_ee.emit("shared_event")
    assert base_ee.emit("uplifted_event")

    call_me.assert_has_calls(
        [
            call("base event on base emitter"),
            call("shared event on base emitter"),
            call("shared event on uplifted emitter"),
            call("uplifted event on uplifted emitter"),
        ]
    )

    call_me.reset_mock()

    # Quick check for unwrap
    unwrap(uplifted_ee)

    with pytest.raises(AttributeError):
        getattr(uplifted_ee, "unwrap")

    with pytest.raises(AttributeError):
        getattr(base_ee, "unwrap")

    assert not uplifted_ee.emit("base_event")
    assert uplifted_ee.emit("shared_event")
    assert uplifted_ee.emit("uplifted_event")

    assert base_ee.emit("base_event")
    assert base_ee.emit("shared_event")
    assert not base_ee.emit("uplifted_event")

    call_me.assert_has_calls(
        [
            # No listener for base event on uplifted
            call("shared event on uplifted emitter"),
            call("uplifted event on uplifted emitter"),
            call("base event on base emitter"),
            call("shared event on base emitter"),
            # No listener for uplifted event on uplifted
        ]
    )


@pytest.mark.parametrize("error_handling", ["new", "underlying", "neither"])
def test_exception_handling(error_handling):
    base_ee = EventEmitter()
    uplifted_ee = uplift(UpliftedEventEmitter, base_ee, error_handling=error_handling)

    # Exception handling always prefers uplifted
    base_error = Exception("base error")
    uplifted_error = Exception("uplifted error")

    # Hold my beer
    base_error_handler = Mock()
    base_ee._emit_handle_potential_error = base_error_handler

    # Hold my other beer
    uplifted_error_handler = Mock()
    uplifted_ee._emit_handle_potential_error = uplifted_error_handler

    base_ee.emit("error", base_error)
    uplifted_ee.emit("error", uplifted_error)

    if error_handling == "new":
        base_error_handler.assert_not_called()
        uplifted_error_handler.assert_has_calls(
            [call("error", base_error), call("error", uplifted_error)]
        )
    elif error_handling == "underlying":
        base_error_handler.assert_has_calls(
            [call("error", base_error), call("error", uplifted_error)]
        )
        uplifted_error_handler.assert_not_called()
    elif error_handling == "neither":
        base_error_handler.assert_called_once_with("error", base_error)
        uplifted_error_handler.assert_called_once_with("error", uplifted_error)
    else:
        raise Exception("unrecognized setting")


@pytest.mark.parametrize(
    "proxy_new_listener", ["both", "neither", "forward", "backward"]
)
def test_proxy_new_listener(proxy_new_listener):
    call_me = Mock()

    base_ee = EventEmitter()

    uplifted_ee = uplift(
        UpliftedEventEmitter, base_ee, proxy_new_listener=proxy_new_listener
    )

    @base_ee.on("new_listener")
    def base_new_listener_handler(event, f):
        assert event in ("event", "new_listener")
        call_me("base new listener handler", f)

    @uplifted_ee.on("new_listener")
    def uplifted_new_listener_handler(event, f):
        assert event in ("event", "new_listener")
        call_me("uplifted new listener handler", f)

    def fresh_base_handler():
        pass

    def fresh_uplifted_handler():
        pass

    base_ee.on("event", fresh_base_handler)
    uplifted_ee.on("event", fresh_uplifted_handler)

    if proxy_new_listener == "both":
        call_me.assert_has_calls(
            [
                call("base new listener handler", fresh_base_handler),
                call("uplifted new listener handler", fresh_base_handler),
                call("uplifted new listener handler", fresh_uplifted_handler),
                call("base new listener handler", fresh_uplifted_handler),
            ]
        )
    elif proxy_new_listener == "neither":
        call_me.assert_has_calls(
            [
                call("base new listener handler", fresh_base_handler),
                call("uplifted new listener handler", fresh_uplifted_handler),
            ]
        )
    elif proxy_new_listener == "forward":
        call_me.assert_has_calls(
            [
                call("base new listener handler", fresh_base_handler),
                call("uplifted new listener handler", fresh_base_handler),
                call("uplifted new listener handler", fresh_uplifted_handler),
            ]
        )
    elif proxy_new_listener == "backward":
        call_me.assert_has_calls(
            [
                call("base new listener handler", fresh_base_handler),
                call("uplifted new listener handler", fresh_uplifted_handler),
                call("base new listener handler", fresh_uplifted_handler),
            ]
        )
    else:
        raise Exception("unrecognized proxy_new_listener")
