#!/usr/bin/env python3
import os
import sys
import unittest
from pathlib import Path
from typing import Optional
from unittest import TestCase, mock
from unittest.mock import MagicMock, patch

import importlib_metadata as metadata

import zulip_bots.run
from zulip_bots.lib import extract_query_without_mention


class TestDefaultArguments(TestCase):
    our_dir = os.path.dirname(__file__)
    path_to_bot = os.path.abspath(os.path.join(our_dir, "../bots/giphy/giphy.py"))
    packaged_bot_module = MagicMock(__version__="1.0.0")
    packaged_bot_entrypoint = metadata.EntryPoint(
        "packaged_bot", "module_name", "zulip_bots.registry"
    )

    @patch("sys.argv", ["zulip-run-bot", "giphy", "--config-file", "/foo/bar/baz.conf"])
    @patch("zulip_bots.run.run_message_handler_for_bot")
    def test_argument_parsing_with_bot_name(
        self, mock_run_message_handler_for_bot: mock.Mock
    ) -> None:
        with patch("zulip_bots.run.exit_gracefully_if_zulip_config_is_missing"):
            zulip_bots.run.main()

        mock_run_message_handler_for_bot.assert_called_with(
            bot_name="giphy",
            config_file="/foo/bar/baz.conf",
            bot_config_file=None,
            lib_module=mock.ANY,
            bot_source="source",
            quiet=False,
        )

    @patch("sys.argv", ["zulip-run-bot", path_to_bot, "--config-file", "/foo/bar/baz.conf"])
    @patch("zulip_bots.run.run_message_handler_for_bot")
    def test_argument_parsing_with_bot_path(
        self, mock_run_message_handler_for_bot: mock.Mock
    ) -> None:
        with patch("zulip_bots.run.exit_gracefully_if_zulip_config_is_missing"):
            zulip_bots.run.main()

        mock_run_message_handler_for_bot.assert_called_with(
            bot_name="giphy",
            config_file="/foo/bar/baz.conf",
            bot_config_file=None,
            lib_module=mock.ANY,
            bot_source="source",
            quiet=False,
        )

    @patch(
        "sys.argv", ["zulip-run-bot", "packaged_bot", "--config-file", "/foo/bar/baz.conf", "-r"]
    )
    @patch("zulip_bots.run.run_message_handler_for_bot")
    def test_argument_parsing_with_zulip_bot_registry(
        self, mock_run_message_handler_for_bot: mock.Mock
    ) -> None:
        with patch("zulip_bots.run.exit_gracefully_if_zulip_config_is_missing"), patch(
            "zulip_bots.finder.metadata.EntryPoint.load",
            return_value=self.packaged_bot_module,
        ), patch(
            "zulip_bots.finder.metadata.entry_points",
            return_value=(self.packaged_bot_entrypoint,),
        ):
            zulip_bots.run.main()

        mock_run_message_handler_for_bot.assert_called_with(
            bot_name="packaged_bot",
            config_file="/foo/bar/baz.conf",
            bot_config_file=None,
            lib_module=mock.ANY,
            bot_source="packaged_bot: 1.0.0",
            quiet=False,
        )

    def test_adding_bot_parent_dir_to_sys_path_when_bot_name_specified(self) -> None:
        bot_name = "helloworld"  # existing bot's name
        expected_bot_dir_path = Path(
            os.path.dirname(zulip_bots.run.__file__), "bots", bot_name
        ).as_posix()
        self._test_adding_bot_parent_dir_to_sys_path(
            bot_qualifier=bot_name, bot_dir_path=expected_bot_dir_path
        )

    @patch("os.path.isfile", return_value=True)
    def test_adding_bot_parent_dir_to_sys_path_when_bot_path_specified(
        self, mock_os_path_isfile: mock.Mock
    ) -> None:
        bot_path = "/path/to/bot"
        expected_bot_dir_path = Path("/path/to").as_posix()
        self._test_adding_bot_parent_dir_to_sys_path(
            bot_qualifier=bot_path, bot_dir_path=expected_bot_dir_path
        )

    def _test_adding_bot_parent_dir_to_sys_path(
        self, bot_qualifier: str, bot_dir_path: str
    ) -> None:
        with patch(
            "sys.argv", ["zulip-run-bot", bot_qualifier, "--config-file", "/path/to/config"]
        ):
            with patch("zulip_bots.finder.import_module_from_source", return_value=mock.Mock()):
                with patch("zulip_bots.run.run_message_handler_for_bot"):
                    with patch("zulip_bots.run.exit_gracefully_if_zulip_config_is_missing"):
                        zulip_bots.run.main()

        sys_path = [Path(path).as_posix() for path in sys.path]
        self.assertIn(bot_dir_path, sys_path)

    @patch("os.path.isfile", return_value=False)
    def test_run_bot_by_module_name(self, mock_os_path_isfile: mock.Mock) -> None:
        bot_module_name = "bot.module.name"
        mock_bot_module = mock.Mock()
        mock_bot_module.__name__ = bot_module_name
        with patch(
            "sys.argv", ["zulip-run-bot", "bot.module.name", "--config-file", "/path/to/config"]
        ):
            with patch("zulip_bots.run.run_message_handler_for_bot"):
                with patch("zulip_bots.run.exit_gracefully_if_zulip_config_is_missing"):
                    with patch(
                        "importlib.import_module", return_value=mock_bot_module
                    ) as mock_import_module:
                        zulip_bots.run.main()
                        mock_import_module.assert_called_once_with(bot_module_name)


class TestBotLib(TestCase):
    def test_extract_query_without_mention(self) -> None:
        def test_message(name: str, message: str, expected_return: Optional[str]) -> None:
            mock_client = mock.MagicMock()
            mock_client.full_name = name
            mock_message = {"content": message}
            self.assertEqual(
                expected_return, extract_query_without_mention(mock_message, mock_client)
            )

        test_message("xkcd", "@**xkcd**foo", "foo")
        test_message("xkcd", "@**xkcd** foo", "foo")
        test_message("xkcd", "@**xkcd** foo bar baz", "foo bar baz")
        test_message("xkcd", "@**xkcd**         foo bar baz", "foo bar baz")
        test_message("xkcd", "@**xkcd** 123_) (/&%) +}}}l", "123_) (/&%) +}}}l")
        test_message("brokenmention", "@**brokenmention* foo", None)
        test_message("nomention", "foo", None)
        test_message("Max Mustermann", "@**Max Mustermann** foo", "foo")
        test_message(r"Max (Mustermann)#(*$&12]\]", r"@**Max (Mustermann)#(*$&12]\]** foo", "foo")


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