import logging
import os
import sys
import unittest
from tempfile import mkdtemp

from mock import MagicMock

from errbot.bootstrap import bot_config_defaults

log = logging.getLogger(__name__)

try:
    from errbot.backends import slack

    class TestSlackBackend(slack.SlackBackend):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.test_msgs = []
            self.sc = MagicMock()

        def callback_message(self, msg):
            self.test_msgs.append(msg)

        def username_to_userid(self, username, *args, **kwargs):
            """Have to mock because we don't have a slack server."""
            return "Utest"

        def channelname_to_channelid(self, channelname):
            return "Ctest"

        def channelid_to_channelname(self, channelid):
            return "meh"

        def get_im_channel(self, id_):
            return "Cfoo"

        def find_user(self, user):
            m = MagicMock()
            m.name = user
            return m


except SystemExit:
    log.exception("Can't import backends.slack for testing")


@unittest.skipIf(not slack, "package slackclient not installed")
class SlackTests(unittest.TestCase):
    def setUp(self):
        # make up a config.
        tempdir = mkdtemp()
        # reset the config every time
        sys.modules.pop("errbot.config-template", None)
        __import__("errbot.config-template")
        config = sys.modules["errbot.config-template"]
        bot_config_defaults(config)
        config.BOT_DATA_DIR = tempdir
        config.BOT_LOG_FILE = os.path.join(tempdir, "log.txt")
        config.BOT_EXTRA_PLUGIN_DIR = []
        config.BOT_LOG_LEVEL = logging.DEBUG
        config.BOT_IDENTITY = {"username": "err@localhost", "token": "___"}
        config.BOT_ASYNC = False
        config.BOT_PREFIX = "!"
        config.CHATROOM_FN = "blah"

        self.slack = TestSlackBackend(config)

    def testBotMessageWithAttachments(self):
        attachment = {
            "title": "sometitle",
            "id": 1,
            "fallback": " *Host:* host-01",
            "color": "daa038",
            "fields": [{"title": "Metric", "value": "1", "short": True}],
            "title_link": "https://xx.com",
        }
        bot_id = "B04HMXXXX"
        bot_msg = {
            "channel": "C0XXXXY6P",
            "icons": {"emoji": ":warning:", "image_64": "https://xx.com/26a0.png"},
            "ts": "1444416645.000641",
            "type": "message",
            "text": "",
            "bot_id": bot_id,
            "username": "riemann",
            "subtype": "bot_message",
            "attachments": [attachment],
        }

        self.slack._dispatch_slack_message(bot_msg)
        msg = self.slack.test_msgs.pop()

        self.assertEqual(msg.extras["attachments"], [attachment])

    def testSlackEventObjectAddedToExtras(self):
        bot_id = "B04HMXXXX"
        bot_msg = {
            "channel": "C0XXXXY6P",
            "icons": {"emoji": ":warning:", "image_64": "https://xx.com/26a0.png"},
            "ts": "1444416645.000641",
            "type": "message",
            "text": "",
            "bot_id": bot_id,
            "username": "riemann",
            "subtype": "bot_message",
        }

        self.slack._dispatch_slack_message(bot_msg)
        msg = self.slack.test_msgs.pop()

        self.assertEqual(msg.extras["slack_event"], bot_msg)

    def testPrepareMessageBody(self):
        test_body = """
        hey, this is some code:
            ```
            foobar
            ```
        """
        parts = self.slack.prepare_message_body(test_body, 10000)
        assert parts == [test_body]

        test_body = """this block is unclosed: ``` foobar """
        parts = self.slack.prepare_message_body(test_body, 10000)
        assert parts == [test_body + "\n```\n"]

        test_body = """``` foobar """
        parts = self.slack.prepare_message_body(test_body, 10000)
        assert parts == [test_body + "\n```\n"]

        test_body = """closed ``` foobar ``` not closed ```"""
        # ---------------------------------^ 21st char
        parts = self.slack.prepare_message_body(test_body, 21)
        assert len(parts) == 2
        assert parts[0].count("```") == 2
        assert parts[0].endswith("```")
        assert parts[1].count("```") == 2
        assert parts[1].endswith("```\n")

    def test_extract_identifiers(self):
        extract_from = self.slack.extract_identifiers_from_string

        self.assertEqual(extract_from("<@U12345>"), (None, "U12345", None, None))

        self.assertEqual(
            extract_from("<@U12345|UName>"), ("UName", "U12345", None, None)
        )

        self.assertEqual(extract_from("<@B12345>"), (None, "B12345", None, None))

        self.assertEqual(extract_from("<#C12345>"), (None, None, None, "C12345"))

        self.assertEqual(extract_from("<#G12345>"), (None, None, None, "G12345"))

        self.assertEqual(extract_from("<#D12345>"), (None, None, None, "D12345"))

        self.assertEqual(extract_from("@person"), ("person", None, None, None))

        self.assertEqual(
            extract_from("#general/someuser"), ("someuser", None, "general", None)
        )

        self.assertEqual(extract_from("#general"), (None, None, "general", None))

        with self.assertRaises(ValueError):
            extract_from("")

        with self.assertRaises(ValueError):
            extract_from("general")

        with self.assertRaises(ValueError):
            extract_from("<>")

        with self.assertRaises(ValueError):
            extract_from("<C12345>")

        with self.assertRaises(ValueError):
            extract_from("<@I12345>")

    def test_build_identifier(self):
        build_from = self.slack.build_identifier

        def check_person(person, expected_uid, expected_cid):
            return person.userid == expected_uid and person.channelid == expected_cid

        assert build_from("<#C12345>").name == "meh"
        assert check_person(build_from("<@U12345>"), "U12345", "Cfoo")
        assert check_person(build_from("@user"), "Utest", "Cfoo")
        assert build_from("#channel").name == "meh"  # the mock always return meh ;)

        self.assertEqual(
            build_from("#channel/user"),
            slack.SlackRoomOccupant(None, "Utest", "Cfoo", self.slack),
        )

    def test_uri_sanitization(self):
        sanitize = self.slack.sanitize_uris

        self.assertEqual(
            sanitize("The email is <mailto:test@example.org|test@example.org>."),
            "The email is test@example.org.",
        )

        self.assertEqual(
            sanitize(
                "Pretty URL Testing: <http://example.org|example.org> with " "more text"
            ),
            "Pretty URL Testing: example.org with more text",
        )

        self.assertEqual(sanitize("URL <http://example.org>"), "URL http://example.org")

        self.assertEqual(
            sanitize("Normal &lt;text&gt; that shouldn't be affected"),
            "Normal &lt;text&gt; that shouldn't be affected",
        )

        self.assertEqual(
            sanitize(
                "Multiple uris <mailto:test@example.org|test@example.org>, "
                "<mailto:other@example.org|other@example.org> and "
                "<http://www.example.org>, <https://example.com> and "
                "<http://subdomain.example.org|subdomain.example.org>."
            ),
            "Multiple uris test@example.org, other@example.org and "
            "http://www.example.org, https://example.com and subdomain.example.org.",
        )

    def test_slack_markdown_link_preprocessor(self):
        convert = self.slack.md.convert
        self.assertEqual(
            "This is <http://example.com/|a link>.",
            convert("This is [a link](http://example.com/)."),
        )
        self.assertEqual(
            "This is <https://example.com/|a link> and <mailto:me@comp.org|an email address>.",
            convert(
                "This is [a link](https://example.com/) and [an email address](mailto:me@comp.org)."
            ),
        )
        self.assertEqual(
            "This is <http://example.com/|a link> and a manual URL: https://example.com/.",
            convert(
                "This is [a link](http://example.com/) and a manual URL: https://example.com/."
            ),
        )
        self.assertEqual(
            "<http://example.com/|This is a link>",
            convert("[This is a link](http://example.com/)"),
        )
        self.assertEqual(
            "This is http://example.com/image.png.",
            convert("This is ![an image](http://example.com/image.png)."),
        )
        self.assertEqual(
            "This is [some text] then <http://example.com|a link>",
            convert("This is [some text] then [a link](http://example.com)"),
        )

    def test_mention_processing(self):
        self.slack.sc.server.users.find = MagicMock(side_effect=self.slack.find_user)

        mentions = self.slack.process_mentions

        self.assertEqual(
            mentions("<@U1><@U2><@U3>"),
            (
                "@U1@U2@U3",
                [
                    self.slack.build_identifier("<@U1>"),
                    self.slack.build_identifier("<@U2>"),
                    self.slack.build_identifier("<@U3>"),
                ],
            ),
        )

        self.assertEqual(
            mentions("Is <@U12345>: here?"),
            ("Is @U12345: here?", [self.slack.build_identifier("<@U12345>")]),
        )

        self.assertEqual(
            mentions("<@U12345> told me about @a and <@U56789> told me about @b"),
            (
                "@U12345 told me about @a and @U56789 told me about @b",
                [
                    self.slack.build_identifier("<@U12345>"),
                    self.slack.build_identifier("<@U56789>"),
                ],
            ),
        )

        self.assertEqual(
            mentions("!these!<@UABCDE>!mentions! will !still!<@UFGHIJ>!work!"),
            (
                "!these!@UABCDE!mentions! will !still!@UFGHIJ!work!",
                [
                    self.slack.build_identifier("<@UABCDE>"),
                    self.slack.build_identifier("<@UFGHIJ>"),
                ],
            ),
        )
