import requests
from .._utils.consts import *
from .._utils._exceptions import APIException, BotNotRunning
import json
from .models import Channel, Message, Members, BannedUsers
from typing import Callable, Optional, Union, List
import logging
from .iconkeys import Reaction


def _get_user_id(username):
    response = requests.get(f"{WWW_REDDIT}/user/{username}/about.json", headers={'user-agent': WEB_USERAGENT}).json()
    u_id = response.get('data', {}).get('id')
    if u_id is None:
        return None
    else:
        return f't2_{u_id}'


class Tools:
    def __init__(self, reddit_auth, session_key_getter: Callable[[], str], is_running_getter: Callable[[], bool]):
        self.__reddit_auth = reddit_auth
        self.__req_sesh = requests.Session()
        self.__session_key_getter: Optional[Callable[[], str]] = session_key_getter
        self.__is_running_getter: Callable[[], bool] = is_running_getter

    def __handled_req(self, method: str, uri: str, chatmedia: bool, **kwargs) -> requests.Response:
        while self.__is_running_getter():
            if chatmedia:
                headers = {
                    'User-Agent': USER_AGENT,
                    'SendBird': f'Android,31,3.0.168,{SB_ai}',
                    'SB-User-Agent': SB_User_Agent,
                    'Session-Key': self.__session_key_getter()
                }
            else:
                headers = {
                    'User-Agent': MOBILE_USERAGENT,
                    'Authorization': f'Bearer {self.__reddit_auth.api_token}',
                    'Content-Type': 'application/json',
                }
            response = self.__req_sesh.request(method, uri, headers=headers, **kwargs)
            if response.status_code == 401 and self.__reddit_auth.is_reauthable:
                new_access_token = self.__reddit_auth.refresh_api_token()
                headers.update({'Authorization': f'Bearer {new_access_token}'})
                continue
            elif response.status_code != 200:
                raise APIException(logging.error(response.text))
            else:
                return response
        raise BotNotRunning("Cannot do that without running the bot first")

    def send_reaction(self, reaction_icon_key: Reaction, msg_id: Union[str, int], channel_url: str) -> None:
        data = json.dumps({
            "id": "7628b2213978",
            "variables": {
                "channelSendbirdId": channel_url,
                "messageSendbirdId": str(msg_id),
                "reactionIconKey": reaction_icon_key.value,
                "type": "ADD"
            }
        })
        self.__handled_req(method='POST', uri=GQL_REDDIT, chatmedia=False, data=data)

    def delete_reaction(self, reaction_icon_key: Reaction, msg_id: Union[str, int], channel_url: str) -> None:
        data = json.dumps({
            "id": "7628b2213978",
            "variables": {
                "channelSendbirdId": channel_url,
                "messageSendbirdId": msg_id,
                "reactionIconKey": reaction_icon_key.value,
                "type": "DELETE"
            }
        })
        self.__handled_req(method='POST', uri=GQL_REDDIT, chatmedia=False, data=data)

    def rename_channel(self, name: str, channel_url: str) -> Channel:
        data = json.dumps({'name': name})
        response = self.__handled_req(method='PUT', uri=f"{SB_PROXY_CHATMEDIA}/v3/group_channels/{channel_url}",
                                      chatmedia=True,
                                      data=data)
        return Channel(response.json())

    def delete_message(self, channel_url: str, msg_id: int) -> None:
        self.__handled_req(method='DELETE',
                           uri=f"{SB_PROXY_CHATMEDIA}/v3/group_channels/{channel_url}/messages/{msg_id}",
                           chatmedia=True)

    def kick_user(self, channel_url: str, user_id: str, duration: int) -> None:
        data = json.dumps({
            'channel_url': channel_url,
            'user_id': user_id,
            'duration': duration
        })
        self.__handled_req(method='POST', uri=f'{S_REDDIT}/api/v1/channel/kick/user',
                           chatmedia=False,
                           data=data)

    def invite_user_to_channel(self, channel_url: str, nicknames: Union[str, List[str]]) -> None:
        if isinstance(nicknames, str):
            nicknames = [nicknames]
        users = []
        for nickname in nicknames:
            users.append({'user_id': _get_user_id(nickname), 'nickname': nickname})
        data = json.dumps({'users': users})
        self.__handled_req(method='POST', uri=f'{S_REDDIT}/api/v1/sendbird/group_channels/{channel_url}/invite',
                           chatmedia=False,
                           data=data)

    def accept_chat_invite(self, channel_url):
        data = json.dumps({
            'user_id': self.__reddit_auth.user_id
        })
        response = self.__handled_req(method='PUT', uri=f'{SB_PROXY_CHATMEDIA}/v3/group_channels/{channel_url}/accept',
                                      chatmedia=True, data=data)
        return Channel(response.json())

    def get_channels(self, limit: int = 100, order: str = "latest_last_message", show_member: bool = True,
                     show_read_receipt: bool = True, show_empty: bool = True, member_state_filter: str = "joined_only",
                     super_mode: str = "all", public_mode: str = "all", unread_filter: str = "all",
                     hidden_mode: str = "unhidden_only", show_frozen: bool = True,
                     # custom_types: str = 'direct,group'
                     ) -> List[Channel]:
        params = {
            'limit': limit,
            'order': order,
            'show_member': show_member,
            'show_read_receipt': show_read_receipt,
            'show_delivery_receipt': 'true',
            'show_empty': show_empty,
            'member_state_filter': member_state_filter,
            'super_mode': super_mode,
            'public_mode': public_mode,
            'unread_filter': unread_filter,
            'hidden_mode': hidden_mode,
            'show_frozen': show_frozen,
        }
        response = self.__handled_req(method='GET',
                                      uri=f'{SB_PROXY_CHATMEDIA}/v3/users/{self.__reddit_auth.user_id}/my_group_channels',
                                      chatmedia=True, params=params)
        return [Channel(channel) for channel in response.json()['channels']]

    def get_members(self, channel_url: str, next_token: str = None, limit: int = 20,
                    order: str = "member_nickname_alphabetical", member_state_filter: str = "all",
                    nickname_startswith: str = '') -> Members:
        params = {
            'token': next_token,
            'limit': limit,
            'order': order,
            'muted_member_filter': 'all',
            'member_state_filter': member_state_filter,
            'show_member_is_muted': 'true',
            'show_read_receipt': 'true',
            'show_delivery_receipt': 'true',
            'nickname_startswith': nickname_startswith
        }
        response = self.__handled_req(method='GET', uri=f'{SB_PROXY_CHATMEDIA}/v3/group_channels/{channel_url}/members',
                                      chatmedia=True, params=params)
        return Members(response.json())

    def get_banned_members(self, channel_url, limit: int = 100) -> BannedUsers:
        params = {'limit': limit}
        response = self.__handled_req(method='GET', uri=f'{SB_PROXY_CHATMEDIA}/v3/group_channels/{channel_url}/ban',
                                      chatmedia=True, params=params)
        return BannedUsers(response.json())

    def leave_chat(self, channel_url: str) -> None:
        data = json.dumps({
            'user_id': self.__reddit_auth.user_id
        })
        self.__handled_req(method='PUT', uri=f'{SB_PROXY_CHATMEDIA}/v3/group_channels/{channel_url}/leave',
                           chatmedia=True, data=data)

    def create_channel(self, nicknames: List[str], group_name: str) -> Channel:
        users = []
        for nickname in nicknames:
            users.append({'user_id': _get_user_id(nickname), 'nickname': nickname})
        data = json.dumps({
            'users': users,
            'name': group_name
        })
        response = self.__handled_req(method='POST', uri=f'{S_REDDIT}/api/v1/sendbird/group_channels',
                                      chatmedia=False,
                                      data=data)
        return Channel(response.json())

    def hide_chat(self, channel_url: str, hide_previous_messages: bool = False, allow_auto_unhide: bool = True) -> None:
        data = json.dumps({
            'user_id': self.__reddit_auth.user_id,
            'hide_previous_messages': hide_previous_messages,
            'allow_auto_unhide': allow_auto_unhide
        })
        self.__handled_req(method='PUT', uri=f'{SB_PROXY_CHATMEDIA}/v3/group_channels/{channel_url}/hide',
                           chatmedia=True, data=data)

    def unhide_chat(self, channel_url: str) -> None:
        self.__handled_req(method='DELETE', uri=f'{SB_PROXY_CHATMEDIA}/v3/group_channels/{channel_url}/hide',
                           chatmedia=True)

    def get_older_messages(self, channel_url: str, message_ts: Union[int, str] = 9007199254740991,
                           custom_types: str = '*', prev_limit: int = 40, next_limit: int = 0,
                           reverse: bool = True) -> List[Message]:
        params = {
            'is_sdk': 'true',
            'prev_limit': prev_limit,
            'next_limit': next_limit,
            'include': 'false',
            'reverse': reverse,
            'message_ts': message_ts,
            'custom_types': custom_types,
            'with_sorted_meta_array': 'false',
            'include_reactions': 'false',
            'include_thread_info': 'false',
            'include_replies': 'false',
            'include_parent_message_text': 'false'
        }
        response = self.__handled_req(method='GET',
                                      uri=f'{SB_PROXY_CHATMEDIA}/v3/group_channels/{channel_url}/messages',
                                      chatmedia=True, params=params)
        return [Message(msg) for msg in response.json()['messages']]

    def mute_user(self, channel_url: str, user_id: str, duration: int, description: str) -> None:
        data = json.dumps({
            'user_id': user_id,
            'seconds': duration,
            'description': description
        })
        self.__handled_req(method='POST', uri=f"{SB_PROXY_CHATMEDIA}/v3/group_channels/{channel_url}/mute",
                           chatmedia=True, data=data)

    def unmute_user(self, channel_url: str, user_id: str) -> None:
        self.__handled_req(method='DELETE', uri=f"{SB_PROXY_CHATMEDIA}/v3/group_channels/{channel_url}/mute/{user_id}",
                           chatmedia=True)

    def set_channel_frozen_status(self, channel_url: str, is_frozen: bool) -> None:
        data = json.dumps({
            'freeze': is_frozen,
        })
        self.__handled_req(method='PUT',
                           uri=f"{SB_PROXY_CHATMEDIA}/v3/group_channels/{channel_url}/freeze",
                           chatmedia=True, data=data)

    def delete_channel(self, channel_url: str) -> None:
        self.__handled_req(method='DELETE',
                           uri=f"{SB_PROXY_CHATMEDIA}/v3/group_channels/{channel_url}",
                           chatmedia=True)
