import logging
import re
import typing
from asyncio import iscoroutinefunction

from ..dispatcher import data_
from ..dispatcher.rule import BaseRule
from ..dispatcher.rule import NamedRule
from vk import types
from vk.constants import JSON_LIBRARY
from vk.types.message import Action

try:
    import vbml
except ImportError:
    vbml = None

logger = logging.getLogger(__name__)

"""
Built-in rules.
"""


class Command(BaseRule):
    prefix = "/"
    meta = {
        "name": "Command",
        "description": "A simple rule for checking messages for the specified command",
        "deprecated": False,
    }

    def __init__(self, command: str = None):
        self.command: str = command

    async def check(self, message: types.Message, data: dict):
        msg = message.text.lower()
        result = f"{self.prefix}{self.command}" == msg
        logger.debug(f"Processing text of message. Text in message: {msg}")
        logger.debug(f"Result of Command rule: {result}")
        return result


class Text(NamedRule):
    key = "text"
    IGNORE_CASE = True
    meta = {
        "name": "Text",
        "description": "A simple rule for checking message for the specified text",
        "deprecated": False,
    }

    def __init__(self, text: str):
        self.text: str = text

    async def check(self, message: types.Message, data: dict):
        if self.IGNORE_CASE:
            msg = message.text.lower()
        else:
            msg = message.text
        result = msg == self.text.lower()
        logger.debug(f"Processing text of message. Text in message: {msg}")
        logger.debug(f"Result of Text rule: {result}")
        return result


class Commands(NamedRule):
    key = "commands"

    prefix: typing.Iterable = "/"  # prefixes for command
    IGNORE_CASE = True
    meta = {
        "name": "Commands",
        "description": "A simple rule for checking messages for the specified commands",
        "deprecated": False,
    }

    def __init__(self, commands: typing.List[str]):
        self.commands = commands

    async def check(self, message: types.Message, data: dict):
        passed = False
        if self.IGNORE_CASE:
            msg = message.text.lower().split()[0]
        else:
            msg = message.text.split()[0]
        for command in self.commands:
            if passed:
                break
            for prefix in self.prefix:
                if msg == f"{prefix}{command}":
                    passed = True
                    break

        logger.debug(f"Processing text of message. Text in message: {msg}")
        logger.debug(f"Result of Commands rule: {passed}")
        return passed


class Payload(NamedRule):
    key = "payload"
    meta = {
        "name": "Payload",
        "description": "A simple rule for checking messages for the specified payload",
        "deprecated": False,
    }

    def __init__(self, payload: dict):
        self.payload = payload

    async def check(self, message: types.Message, data: dict):
        if message.payload:
            payload = JSON_LIBRARY.loads(message.payload)
            result = payload == self.payload
            logger.debug(
                f"Processing payload of message. Payload in message: {payload}"
            )
            logger.debug(f"Result of Payload rule: {result}")
            return result


class ChatAction(NamedRule):
    key = "chat_action"
    meta = {
        "name": "ChatAction",
        "description": "A simple rule for checking messages for the specified chat_action",
        "deprecated": False,
    }

    def __init__(self, action: Action):
        self.action = action

    async def check(self, message: types.Message, data: dict):
        if message.action:
            action = Action(message.action.type)
            result = action is self.action
            logger.debug(
                f"Processing action of message. Action in message: {action}"
            )
            logger.debug(f"Result of ChatAction rule: {result}")
            return result


class DataCheck(NamedRule):
    key = "data_check"
    meta = {
        "name": "DataCheck",
        "description": "A simple rule for checking 'data' variable for the specified data",
        "deprecated": False,
    }

    def __init__(self, data: typing.Dict[str, typing.Any]):
        self.data = data  # for example: {"my_key": "my_value"}

    async def check(self, *args):
        data: dict = args[1]
        passed = True
        for key, value in self.data.items():
            value_data = data.get(key)
            if value_data != value:
                passed = False
                break
        logger.debug(f"Result of DataCheck rule: {passed}")
        return passed


class MessageCountArgs(NamedRule):
    """
    Get args and return result of equal len(args) and passed args.
    """

    key = "count_args"
    meta = {
        "name": "MessageCountArgs",
        "description": "A simple rule for checking messages for the specified count of arguments",
        "deprecated": False,
    }

    def __init__(self, count_args: int):
        self.count_args = count_args

    async def check(self, message: types.Message, data: dict):
        count = len(message.get_args())
        result = count == self.count_args
        logger.debug(f"Received {count} args in message")
        logger.debug(f"Result of MessageCountArgs rule: {result}")
        return result


class MessageArgsValidate(NamedRule):
    """
    Get and validate args by passed validators.
    """

    key = "have_args"
    meta = {
        "name": "MessageArgsValidate",
        "description": "A simple rule for checking messages for the specified args",
        "deprecated": False,
    }

    def __init__(
        self,
        args_validators: typing.Union[
            typing.Tuple[int, typing.List[typing.Callable]],
            typing.List[typing.Callable],
        ],
    ):
        if isinstance(args_validators, list):
            self.args_validators = args_validators
            self.delete_element = 1
        elif isinstance(args_validators, tuple):
            if len(args_validators) < 2:
                self.args_validators = args_validators
                self.delete_element = 1
            else:
                self.delete_element = args_validators[0]
                self.args_validators = args_validators[1]

    async def check(self, message: types.Message, data: dict):
        args = message.get_args(self.delete_element)
        count_args = len(args)
        count_validators = len(self.args_validators)
        if count_args != count_validators:
            logger.debug(
                f"Received {count_args} args in message. Passed validators {count_validators}"
            )
            logger.debug(f"Result of MessageArgsValidate rule: False")
            return False
        passed = True
        for validator, arg in zip(self.args_validators, args):
            if iscoroutinefunction(validator):
                result = await validator(arg, message)
            else:
                result = validator(arg)
            if not result:
                logger.debug("Result of MessageArgsValidate rule: False")
                return False
            if isinstance(result, dict):
                data.update(**result)
                data_.set(data)
        logger.debug(f"Result of MessageArgsValidate rule: {passed}")
        if passed:
            return {"args": message.get_args()}
        return passed


class InChat(NamedRule):
    key = "in_chat"
    meta = {
        "name": "InChat",
        "description": "A simple rule for checking messages for the specified peer_id (in chat)",
        "deprecated": False,
    }

    def __init__(self, in_chat: bool):
        self.in_chat: bool = in_chat

    async def check(self, message: types.Message, data: dict):
        result = self.in_chat is bool(message.peer_id >= 2e9)
        logger.debug(f"Received peer_id: {message.peer_id}")
        logger.debug(f"Result of InChat rule: {result}")

        return result


class InPersonalMessages(NamedRule):
    key = "in_pm"
    meta = {
        "name": "InPersonalMessages",
        "description": "A simple rule for checking messages for the specified peer_id (in "
        "personal messages)",
        "deprecated": False,
    }

    def __init__(self, in_pm: bool):
        self.in_pm: bool = in_pm

    async def check(self, message: types.Message, data: dict):
        result = self.in_pm is bool(message.peer_id < 2e9)
        logger.debug(f"Received peer_id: {message.peer_id}")
        logger.debug(f"Result of InPersonalMessages rule: {result}")

        return result


class FromBot(NamedRule):
    key = "from_bot"
    meta = {
        "name": "FromBot",
        "description": "A simple rule for checking messages for the specified peer_id (from bot)",
        "deprecated": False,
    }

    def __init__(self, from_bot: bool):
        self.from_bot: bool = from_bot

    async def check(self, message: types.Message, data: dict):
        result = self.from_bot is bool(message.from_id < 0)
        logger.debug(f"Received from_id: {message.from_id}")
        logger.debug(f"Result of FromBot rule: {result}")

        return result


class WithReplyMessage(NamedRule):
    key = "with_reply_message"
    meta = {
        "name": "WithReplyMessage",
        "description": "A simple rule for checking messages for the availablity reply message",
        "deprecated": False,
    }

    def __init__(self, with_reply_message: bool):
        self.with_reply_message: bool = with_reply_message

    async def check(self, message: types.Message, data: dict):
        logger.debug(
            f"Result of WithReplyMessage rule: {bool(message.reply_message)}"
        )
        return bool(message.reply_message)


class WithFwdMessages(NamedRule):
    key = "with_fwd_messages"
    meta = {
        "name": "WithFwdMessages",
        "description": "A simple rule for checking messages for the availablity forward messages",
        "deprecated": False,
    }

    def __init__(self, with_fwd_messages: bool):
        self.with_reply_message: bool = with_fwd_messages

    async def check(self, message: types.Message, data: dict):
        logger.debug(
            f"Result of WithFwdMessages rule: {bool(message.fwd_messages)}"
        )
        return bool(message.fwd_messages)


class CountFwdMessages(NamedRule):
    key = "count_fwd_messages"
    meta = {
        "name": "CountFwdMessages",
        "description": "A simple rule for checking messages for count of forwarded messages",
        "deprecated": False,
    }

    def __init__(self, count_fwd_messages: int):
        self.count_fwd_messages: int = count_fwd_messages

    async def check(self, message: types.Message, data: dict):
        count = len(message.fwd_messages)
        result = count == self.count_fwd_messages
        logger.debug(f"Received fwd_messages: {count}")
        logger.debug(f"Result of CountFwdMessages rule: {result}")
        return result


class Regex(NamedRule):
    key = "regex"
    meta = {
        "name": "regex",
        "description": "A simple rule for checking message text via regex",
        "deprecated": False,
    }

    def __init__(self, pattern: str):
        self.pattern: typing.Pattern = re.compile(
            pattern, re.IGNORECASE | re.MULTILINE
        )

    async def check(self, message: types.Message, data: dict):
        msg = message.text.lower()
        result = re.search(self.pattern, msg)
        logger.debug(f"Processing text of message. Text in message: {msg}")
        logger.debug(f"Result of Regex rule: {result}")
        return result


class VBML(NamedRule):
    key = "vbml"

    def __init__(self, pattern: typing.Union["vbml.Pattern", str]):
        if isinstance(pattern, str):
            self.pattern = vbml.Patcher.get_current(no_error=False).pattern(
                pattern
            )
        elif isinstance(pattern, vbml.Pattern):
            self.pattern = pattern

        self._patcher = vbml.Patcher.get_current(no_error=False)

    async def check(self, message: types.Message, data: dict):
        result = await self._patcher.check_async(message.text, self.pattern)
        if result is None:
            return
        local_data = {}
        for k, v in result.items():
            local_data[k] = v

        return local_data
