"""Slack control manager"""
import logging
import slack
import re
from typing import Union, List, Callable, Pattern
from .bots import RTMSlackBot, WebClientOverride
from .parsing import is_event, is_bot_message
from ..control_manager import ControlManager, do_nothing, return_empty


# todo consider using children
logger = logging.getLogger(__name__)

# pattern for help retrieval
help_pattern = re.compile('help', re.IGNORECASE)


class RTMControlManager(RTMSlackBot, ControlManager):
    # default message parsing methods
    DEFAULT_PARSING_METHODS = [
        is_event,
        is_bot_message,
    ]

    help_pattern = help_pattern

    def __init__(self,
                 user_member_ids: Union[str, List[str]] = None,
                 token: str = None,
                 channel_name: str = None,
                 channel_id: str = None,
                 auto_reconnect: bool = True,
                 ping_interval: int = 10,
                 start_action: Callable = do_nothing,
                 stop_action: Callable = do_nothing,
                 resume_action: Callable = do_nothing,
                 pause_action: Callable = do_nothing,
                 status_query: Callable = return_empty,
                 help_query: Callable = return_empty,
                 pre_parsing_methods: List[Callable] = None,
                 ):
        """
        A control manager for executing steps contextually based on slack trigger messages.

        :param str, user_member_ids: Slack user ID(s) for the slack bot to be able to message user. find this by going to
            a user's profile, click the three dots (...) and there is the member ID. Example of a slack member ID: ABCDEF.
        :param str token: token to connect to slack client
        :param str channel_name: channel to message on. for example, #channelname
        :param str channel_id: channel id to listen for events on. If set, only events on this channel will be be
            caught and handled. for example: C1234ABCD. Find more information on finding this here:
            https://stackoverflow.com/questions/40940327/what-is-the-simplest-way-to-find-a-slack-team-id-and-a-channel-id
        :param auto_reconnect: enable/disable RTM auto-reconnect
        :param ping_interval: ping interval for RTM client
        :param start_action: callable action to execute on a start trigger
        :param stop_action: callable action to execute on a stop trigger
        :param resume_action: callable action to execute on a resume trigger
        :param pause_action: callable action to execute on a pause trigger
        :param status_query: callable which returns a string representing the status of the thing being controlled
        :param help_query: callable which returns a help string for the Slack interface
        :param pre_parsing_methods: methods to execute on an RTM payload prior to executing targetted methods.
            These methods must accept an RTM payload and return a bool (False will proceed with action execution,
            True on any parsing method will fail to trigger the action methods). See slack_integration.parsing for
            examples.
        """
        # initialize bot and manager
        RTMSlackBot.__init__(
            self,
            user_member_ids=user_member_ids,
            token=token,
            channel_name=channel_name,
            channel_id=channel_id,
            auto_reconnect=auto_reconnect,
            ping_interval=ping_interval,
        )
        ControlManager.__init__(
            self,
            start_action=start_action,
            stop_action=stop_action,
            resume_action=resume_action,
            pause_action=pause_action,
            status_query=status_query,
        )

        # register catch methods
        self.register_message_catch(self.start_catch)
        self.register_message_catch(self.pause_catch)
        self.register_message_catch(self.stop_catch)
        self.register_message_catch(self.resume_catch)
        self.register_message_catch(self.status_catch)
        self.register_message_catch(self.help_catch)
        # store pre-parsing methods
        if pre_parsing_methods is None:
            pre_parsing_methods = self.DEFAULT_PARSING_METHODS
        self.parsing_methods: List[Callable] = pre_parsing_methods

        self._help_query = return_empty
        self.help_query = help_query

    @property
    def help_query(self) -> Callable:
        """the callable which is used to retrieve a help string"""
        return self._help_query

    @help_query.setter
    def help_query(self, value):
        if callable(value) is False:
            raise TypeError(f'{value} is not callable')
        self._help_query = value

    @help_query.deleter
    def help_query(self):
        self._help_query = return_empty

    def catch_and_match(self, pattern: Union[Pattern, str], full_match: bool = True, **payload) -> bool:
        """
        Attempts to match a regex pattern to the message provided

        :param pattern: regex pattern to compare against the text, or a string that will be compiled into a regex
            pattern with the re.IGNORECASE flag added
        :param full_match:  if True, then the payload is text has to re.fullmatch the pattern to return True,
            else, as long as the pattern is found somewhere in the text (re.search), then the return value will be True
        :param payload: RTM payload
        :return: match truth
        """
        # apply pre-parsing methods
        if any([pre_parse(**payload) is True for pre_parse in self.parsing_methods]):
            return False
        message = payload['data']
        text = message.get('text')
        channel_id = str(message.get('channel'))
        if self.channel_id is not None:
            if self.channel_id != channel_id:
                return False
        if type(pattern) == str:
            pattern = re.compile(pattern, re.IGNORECASE)
        if full_match:
            if pattern.fullmatch(text) is not None:
                logger.debug(f'{pattern} catch triggered by "{text}"')
                return True
        else:
            if pattern.search(text) is not None:
                logger.debug(f'{pattern} catch triggered by "{text}"')
                return True
        return False

    def start_catch(self, **payload):
        """Catches start calls and executes the start action"""
        if self.catch_and_match(self.start_pattern, **payload) is True:
            with WebClientOverride(self, payload['web_client']):
                if self.start_action is not do_nothing:
                    self.post_slack_message(
                        'Sure thing! Starting things up! :science_parrot:'
                    )
                    # assumes non-blocking
                    self.start_action()

    def pause_catch(self, **payload):
        """catches pause calls and executes the pause action"""
        if self.catch_and_match(self.pause_pattern, **payload) is True:
            with WebClientOverride(self, payload['web_client']):
                if self.pause_action is not do_nothing:
                    self.post_slack_message(
                        'Pausing the thing!'
                    )
                    # assumes non-blocking
                    self.pause_action()

    def resume_catch(self, **payload):
        """catches resume calls and execute the resume action"""
        if self.catch_and_match(self.resume_pattern, **payload) is True:
            with WebClientOverride(self, payload['web_client']):
                if self.resume_action is not do_nothing:
                    self.post_slack_message(
                        'Resuming the thing!'
                    )
                    # assumes non-blocking
                    self.resume_action()

    def stop_catch(self, **payload):
        """catches stop calls and executes the stop action"""
        if self.catch_and_match(self.stop_pattern, **payload) is True:
            with WebClientOverride(self, payload['web_client']):
                if self.stop_action is not do_nothing:
                    self.post_slack_message(
                        'Stopping the thing!'
                    )
                    # assumes non-blocking
                    self.stop_action()

    def status_catch(self, **payload):
        """catches and posts the status query string"""
        if self.catch_and_match(self.status_pattern, **payload) is True:
            with WebClientOverride(self, payload['web_client']):
                if self.status_query is not return_empty:
                    self.post_slack_message(
                        f"Here's the current status:\n`{self.status_query()}`"
                    )

    def help_catch(self, **payload):
        """catches and posts the help string"""
        if self.catch_and_match(self.help_pattern, **payload) is True:
            with WebClientOverride(self, payload['web_client']):
                # todo
                #   - iterate through defined
                #   - check if defined
                #   - add to help string
                if self.help_query is not return_empty:
                    self.post_slack_message(
                        f"Here's how to use the Slack interface:\n`{self.help_query()}`"
                    )
