from copy import deepcopy

from slackminion.dispatcher import MessageDispatcher
from slackminion.exceptions import DuplicateCommandError
from slackminion.tests.fixtures import *

test_data_mapping = []


class TestDispatcher(unittest.TestCase):
    @mock.patch("slackminion.slack.SlackUser")
    def setUp(self, mock_user):
        mock_user.return_value = test_user
        self.dispatcher = MessageDispatcher()
        self.p = DummyPlugin(None)
        self.test_payload = deepcopy(test_payload)

    def tearDown(self):
        self.dispatcher = None
        self.test_payload = None

    def test_register_plugin(self):
        self.dispatcher.register_plugin(self.p)

    def test_register_duplicate_plugin(self):
        self.dispatcher.register_plugin(self.p)
        with self.assertRaises(DuplicateCommandError) as e:
            self.dispatcher.register_plugin(self.p)
            self.assertIn("abc", str(e))

    def test_get_command(self):
        self.dispatcher.register_plugin(self.p)
        method = self.dispatcher._get_command("!abc", None).method
        assert method == self.p.abc
        assert method(None, None) == "abcba"

    def test_get_invalid_command(self):
        self.dispatcher.register_plugin(self.p)
        with self.assertRaises(KeyError):
            self.dispatcher._get_command("!def", None)

    def test_parse_message(self):
        e = SlackEvent(event_type="message", **{"data": {"text": "Hello world"}})
        assert self.dispatcher._parse_message(e) == ["Hello", "world"]

    def test_parse_message_extra_space(self):
        # Strip out extra spaces
        e = SlackEvent(event_type="message", **{"data": {"text": "Hello  world"}})
        assert self.dispatcher._parse_message(e) == ["Hello", "world"]

    def test_parse_message_unicode(self):
        e = SlackEvent(event_type="message", **{"data": {"text": "Hello\xa0world"}})
        assert self.dispatcher._parse_message(e) == ["Hello", "world"]

    @async_test
    async def test_strip_formatting(self):
        test_string = "!stripformat <@U123456> check <#C123456|test-channel> has <https://www.pinterest.com|www.pinterest.com>"
        expected_response = "@U123456 check #test-channel has www.pinterest.com"
        e = SlackEvent(event_type="message", **{"data": {"text": test_string}})
        e.user = mock.Mock()
        e.channel = test_conversation
        self.dispatcher.register_plugin(self.p)
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd_opts.get("strip_formatting") is True
        self.assertEqual(expected_response, output)

    def test_unignore_nonignored_channel(self):
        c = SlackConversation(
            conversation=test_channel, api_client=test_payload.get("api_client")
        )
        self.assertFalse(self.dispatcher.unignore(c))
        assert "testchannel" not in self.dispatcher.ignored_channels

    def test_ignore_duplicate_channel(self):
        c = SlackConversation(
            conversation=test_channel, api_client=test_payload.get("api_client")
        )
        assert self.dispatcher.ignore(c) is True
        assert c.name in self.dispatcher.ignored_channels
        assert self.dispatcher.ignore(c) is False
        assert len(self.dispatcher.ignored_channels) == 1

    def test_ignore_slackchannel(self):
        c = SlackConversation(
            conversation=test_channel, api_client=test_payload.get("api_client")
        )
        c.name = "testchannel"
        assert self.dispatcher.ignore(c) is True
        assert "testchannel" in self.dispatcher.ignored_channels
        assert self.dispatcher._is_channel_ignored(self.p.abc, c) is True

    def test_unignore_slackchannel(self):
        c = SlackConversation(
            conversation=test_channel, api_client=test_payload.get("api_client")
        )
        self.assertTrue(self.dispatcher.ignore(c))
        self.assertTrue(self.dispatcher.unignore(c))
        self.assertNotIn(test_channel.get("name"), self.dispatcher.ignored_channels)
        self.assertFalse(self.dispatcher._is_channel_ignored(self.p.abc, c))

    @async_test
    async def test_push(self):
        self.dispatcher.register_plugin(self.p)
        self.test_payload["data"].update({"text": "!abc"})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == "!abc"
        assert output == "abcba"
        assert type(cmd_opts) == dict
        assert ("reply_in_thread" in list(cmd_opts.keys())) is True
        assert ("reply_broadcast" in list(cmd_opts.keys())) is True
        assert cmd_opts.get("reply_broadcast") is False
        assert cmd_opts.get("reply_in_thread") is False

    @async_test
    async def test_push_alias(self):
        self.dispatcher.register_plugin(self.p)
        self.test_payload["data"].update({"text": "!bca"})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == "!bca"
        assert output == "abcba"
        assert type(cmd_opts) == dict
        assert ("reply_in_thread" in list(cmd_opts.keys())) is True
        assert ("reply_broadcast" in list(cmd_opts.keys())) is True
        assert cmd_opts.get("reply_broadcast") is False
        assert cmd_opts.get("reply_in_thread") is False

    @async_test
    async def test_push_to_thread(self):
        self.dispatcher.register_plugin(self.p)
        self.test_payload["data"].update({"text": "!efg"})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.channel = test_conversation
        e.user = mock.Mock()
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == "!efg"
        assert output == "efgfe"
        assert type(cmd_opts) == dict
        assert ("reply_in_thread" in list(cmd_opts.keys())) is True
        assert ("reply_broadcast" in list(cmd_opts.keys())) is True
        assert cmd_opts.get("reply_broadcast") is False
        assert cmd_opts.get("reply_in_thread") is True

    @async_test
    async def test_push_to_thread_with_broadcast(self):
        self.dispatcher.register_plugin(self.p)
        payload = dict(self.test_payload)
        payload["data"].update({"text": "!hij"})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == "!hij"
        assert output == "hijih"
        assert type(cmd_opts) == dict
        assert ("reply_in_thread" in list(cmd_opts.keys())) is True
        assert ("reply_broadcast" in list(cmd_opts.keys())) is True
        assert cmd_opts.get("reply_broadcast") is True
        assert cmd_opts.get("reply_in_thread") is True

    @async_test
    async def test_push_not_command(self):
        payload = dict(self.test_payload)
        payload["data"].update({"text": "Not a command"})
        e = SlackEvent(event_type="message", **payload)
        assert await self.dispatcher.push(e) == (None, None, None)

    @async_test
    async def test_push_no_text(self):
        payload = dict(self.test_payload)
        payload["data"].update({"text": ""})
        e = SlackEvent(event_type="message", **payload)
        assert await self.dispatcher.push(e) == (None, None, None)

    @async_test
    async def test_push_message_replied_event(self):
        payload = dict(self.test_payload)
        payload["data"].update({"subtype": "message_replied"})
        e = SlackEvent(event_type="message", **payload)
        assert await self.dispatcher.push(e) == (None, None, None)

    @async_test
    async def test_push_no_user(self):
        self.dispatcher.register_plugin(self.p)
        payload = dict(self.test_payload)
        payload["data"].update({"subtype": "message_replied"})
        payload["data"].pop("user")
        e = SlackEvent(event_type="message", **payload)
        assert await self.dispatcher.push(e) == (None, None, None)

    @async_test
    async def test_push_ignored_channel(self):
        c = SlackConversation(
            conversation=test_channel, api_client=test_payload.get("api_client")
        )
        self.dispatcher.ignore(c)
        self.dispatcher.register_plugin(self.p)
        self.dispatcher._is_channel_ignored = mock.Mock(return_value=True)
        self.test_payload["data"].update(
            {"text": "!abc", "user": test_user_id, "channel": test_channel_id}
        )
        e = SlackEvent(event_type="message", **self.test_payload)
        e.channel = test_conversation
        e.user = mock.Mock()
        assert await self.dispatcher.push(e) == ("_ignored_", "", None)

    @async_test
    async def test_async_cmd(self):
        self.dispatcher.register_plugin(self.p)
        payload = dict(self.test_payload)
        payload["data"].update({"text": "!asyncabc"})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == "!asyncabc"
        assert output == "asyncabc response"
        assert type(cmd_opts) == dict
        assert ("reply_in_thread" in list(cmd_opts.keys())) is True
        assert ("reply_broadcast" in list(cmd_opts.keys())) is True
        assert ("parse" in list(cmd_opts.keys())) is True
        assert cmd_opts.get("reply_broadcast") is True
        assert cmd_opts.get("reply_in_thread") is True
        assert cmd_opts.get("parse") is None

    @async_test
    async def test_async_cmd_parse(self):
        self.dispatcher.register_plugin(self.p)
        payload = dict(self.test_payload)
        payload["data"].update({"text": "!asyncparse"})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == "!asyncparse"
        assert output == "async parse command #parse"
        assert type(cmd_opts) == dict
        assert ("reply_in_thread" in list(cmd_opts.keys())) is True
        assert ("reply_broadcast" in list(cmd_opts.keys())) is True
        assert ("parse" in list(cmd_opts.keys())) is True
        assert cmd_opts.get("reply_broadcast") is False
        assert cmd_opts.get("reply_in_thread") is False
        assert cmd_opts.get("parse") is True


if __name__ == "__main__":
    unittest.main()
