# -*- coding: utf-8 -*-
"""
:authors: deker104, python273
:license: Apache License, Version 2.0, see LICENSE file

:copyright: (c) 2019 python273
"""
from enum import Enum

import requests

CHAT_START_ID = int(2E9)


class DotDict(dict):
    __getattr__ = dict.get
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__


class VkBotEventType(Enum):
    MESSAGE_NEW = 'message_new'
    MESSAGE_REPLY = 'message_reply'
    MESSAGE_EDIT = 'message_edit'
    MESSAGE_EVENT = 'message_event'

    MESSAGE_TYPING_STATE = 'message_typing_state'

    MESSAGE_ALLOW = 'message_allow'

    MESSAGE_DENY = 'message_deny'

    PHOTO_NEW = 'photo_new'

    PHOTO_COMMENT_NEW = 'photo_comment_new'
    PHOTO_COMMENT_EDIT = 'photo_comment_edit'
    PHOTO_COMMENT_RESTORE = 'photo_comment_restore'

    PHOTO_COMMENT_DELETE = 'photo_comment_delete'

    AUDIO_NEW = 'audio_new'

    VIDEO_NEW = 'video_new'

    VIDEO_COMMENT_NEW = 'video_comment_new'
    VIDEO_COMMENT_EDIT = 'video_comment_edit'
    VIDEO_COMMENT_RESTORE = 'video_comment_restore'

    VIDEO_COMMENT_DELETE = 'video_comment_delete'

    WALL_POST_NEW = 'wall_post_new'
    WALL_REPOST = 'wall_repost'

    WALL_REPLY_NEW = 'wall_reply_new'
    WALL_REPLY_EDIT = 'wall_reply_edit'
    WALL_REPLY_RESTORE = 'wall_reply_restore'

    WALL_REPLY_DELETE = 'wall_reply_delete'

    BOARD_POST_NEW = 'board_post_new'
    BOARD_POST_EDIT = 'board_post_edit'
    BOARD_POST_RESTORE = 'board_post_restore'

    BOARD_POST_DELETE = 'board_post_delete'

    MARKET_COMMENT_NEW = 'market_comment_new'
    MARKET_COMMENT_EDIT = 'market_comment_edit'
    MARKET_COMMENT_RESTORE = 'market_comment_restore'

    MARKET_COMMENT_DELETE = 'market_comment_delete'

    GROUP_LEAVE = 'group_leave'

    GROUP_JOIN = 'group_join'

    USER_BLOCK = 'user_block'

    USER_UNBLOCK = 'user_unblock'

    POLL_VOTE_NEW = 'poll_vote_new'

    GROUP_OFFICERS_EDIT = 'group_officers_edit'

    GROUP_CHANGE_SETTINGS = 'group_change_settings'

    GROUP_CHANGE_PHOTO = 'group_change_photo'

    VKPAY_TRANSACTION = 'vkpay_transaction'


class VkBotEvent(object):
    """ Событие Bots Long Poll

    :ivar raw: событие, в каком виде было получено от сервера

    :ivar type: тип события
    :vartype type: VkBotEventType or str

    :ivar t: сокращение для type
    :vartype t: VkBotEventType or str

    :ivar object: объект события, в каком виде был получен от сервера
    :ivar obj: сокращение для object

    :ivar group_id: ID группы бота
    :vartype group_id: int
    """

    __slots__ = (
        'raw',
        't', 'type',
        'obj', 'object',
        'client_info', 'message',
        'group_id'
    )

    def __init__(self, raw):
        self.raw = raw

        try:
            self.type = VkBotEventType(raw['type'])
        except ValueError:
            self.type = raw['type']

        self.t = self.type  # shortcut

        self.object = DotDict(raw['object'])
        try:
            self.message = DotDict(raw['object']['message'])
        except KeyError:
            self.message = None
        self.obj = self.object
        try:
            self.client_info = DotDict(raw['object']['client_info'])
        except KeyError:
            self.client_info = None

        self.group_id = raw['group_id']

    def __repr__(self):
        return '<{}({})>'.format(type(self), self.raw)


class VkBotMessageEvent(VkBotEvent):
    """ Событие с сообщением Bots Long Poll

    :ivar from_user: сообщение от пользователя
    :vartype from_user: bool

    :ivar from_chat: сообщение из беседы
    :vartype from_chat: bool

    :ivar from_group: сообщение от группы
    :vartype from_group: bool

    :ivar chat_id: ID чата
    :vartype chat_id: int
    """

    __slots__ = ('from_user', 'from_chat', 'from_group', 'chat_id')

    def __init__(self, raw):
        super(VkBotMessageEvent, self).__init__(raw)

        self.from_user = False
        self.from_chat = False
        self.from_group = False
        self.chat_id = None

        peer_id = self.obj.peer_id or self.message.peer_id
        
        if peer_id < 0:
            self.from_group = True
        elif peer_id < CHAT_START_ID:
            self.from_user = True
        else:
            self.from_chat = True
            self.chat_id = peer_id - CHAT_START_ID


class VkBotLongPoll(object):
    """ Класс для работы с Bots Long Poll сервером

    `Подробнее в документации VK API <https://vk.com/dev/bots_longpoll>`__.

    :param vk: объект :class:`VkApi`
    :param group_id: id группы
    :param wait: время ожидания
    """

    __slots__ = (
        'vk', 'wait', 'group_id',
        'url', 'session',
        'key', 'server', 'ts'
    )

    #: Классы для событий по типам
    CLASS_BY_EVENT_TYPE = {
        VkBotEventType.MESSAGE_NEW.value: VkBotMessageEvent,
        VkBotEventType.MESSAGE_REPLY.value: VkBotMessageEvent,
        VkBotEventType.MESSAGE_EDIT.value: VkBotMessageEvent,
    }

    #: Класс для событий
    DEFAULT_EVENT_CLASS = VkBotEvent

    def __init__(self, vk, group_id, wait=25):
        self.vk = vk
        self.group_id = group_id
        self.wait = wait

        self.url = None
        self.key = None
        self.server = None
        self.ts = None

        self.session = requests.Session()

        self.update_longpoll_server()

    def _parse_event(self, raw_event):
        event_class = self.CLASS_BY_EVENT_TYPE.get(
            raw_event['type'],
            self.DEFAULT_EVENT_CLASS
        )
        return event_class(raw_event)

    def update_longpoll_server(self, update_ts=True):
        values = {
            'group_id': self.group_id
        }
        response = self.vk.method('groups.getLongPollServer', values)

        self.key = response['key']
        self.server = response['server']

        self.url = self.server

        if update_ts:
            self.ts = response['ts']

    def check(self):
        """ Получить события от сервера один раз

        :returns: `list` of :class:`Event`
        """

        values = {
            'act': 'a_check',
            'key': self.key,
            'ts': self.ts,
            'wait': self.wait,
        }

        response = self.session.get(
            self.url,
            params=values,
            timeout=self.wait + 10
        ).json()

        if 'failed' not in response:
            self.ts = response['ts']
            return [
                self._parse_event(raw_event)
                for raw_event in response['updates']
            ]

        elif response['failed'] == 1:
            self.ts = response['ts']

        elif response['failed'] == 2:
            self.update_longpoll_server(update_ts=False)

        elif response['failed'] == 3:
            self.update_longpoll_server()

        return []

    def listen(self):
        """ Слушать сервер

        :yields: :class:`Event`
        """

        while True:
            for event in self.check():
                yield event
