import hashlib
import hmac
import json
import logging
import os
import sys
import time
import uuid
from random import uniform

try:
    from json.decoder import JSONDecodeError
except ImportError:
    JSONDecodeError = ValueError

import requests
import requests.utils
import urllib
from tqdm import tqdm

from . import config, devices
from .api_photo import configure_photo, download_photo, upload_photo
from .api_video import configure_video, download_video, upload_video
from .prepare import delete_credentials, get_credentials

from colorlog import ColoredFormatter


PY2 = sys.version_info[0] == 2

class LoggerAdapter(logging.LoggerAdapter):
    def __init__(self, logger, prefix):
        super(LoggerAdapter, self).__init__(logger, {})
        self.prefix = prefix

    def process(self, msg, kwargs):
        return '[%s] %s' % (self.prefix, msg), kwargs

class API(object):

    _id = 0

    def __init__(self, logs_file, device=None, username=None,  id=None):

        self._id = API._id if not id else id
        API._id += 1
        # Setup device and user_agent
        device = device or devices.DEFAULT_DEVICE
        self.device_settings = devices.DEVICES[device]
        self.user_agent = config.USER_AGENT_BASE.format(**self.device_settings)

        self.is_logged_in = False
        self.last_response = None
        self.total_requests = 0

        # Setup logging
        self.logger = logging.getLogger('[{}]'.format(self._id))

        # fh = HTMLFileHandler(title=self._id, file=logs_file, mode='w')
        # fh.setLevel(logging.INFO)
        # fh.setFormatter(file_formatter())
        # self.logger.addHandler(fh)

        ch = logging.StreamHandler()
        ch.setLevel(logging.DEBUG)
        ch.setFormatter(colred_formatter())
        self.logger.addHandler(ch)

        self.logger.setLevel(logging.DEBUG)
        self.logger = LoggerAdapter(self.logger, username)



        self.last_json = None



    def media_info(self, media_id):
        url = 'media/{media_id}/info/'.format(media_id=media_id)
        return self.send_request(url)


    def send_request(self, endpoint, post=None, login=False, with_signature=True):
            self.logger.info('new request to endpoint %s' % endpoint)
            if (not self.is_logged_in and not login):
                msg = "Not logged in!"
                self.logger.critical(msg)
                raise Exception(msg)

            self.session.headers.update(config.REQUEST_HEADERS)
            self.session.headers.update({'User-Agent': self.user_agent})
            try:
                self.total_requests += 1
                if post is not None:  # POST
                    if with_signature:
                        # Only `send_direct_item` doesn't need a signature
                        post = self.generate_signature(post)
                    response = self.session.post(
                        config.API_URL + endpoint, data=post)
                else:  # GET
                    response = self.session.get(
                        config.API_URL + endpoint)
            except Exception as e:
                self.logger.warning(str(e))
                return False

            if response.status_code == 200:
                self.last_response = response
                try:
                    self.last_json = json.loads(response.text)
                    return True
                except JSONDecodeError:
                    return False
            else:
                self.logger.error("Request returns {} error!".format(response.status_code))
                if response.status_code == 429:
                    sleep_minutes = 5
                    self.logger.warning(
                        "That means 'too many requests'. I'll go to sleep "
                        "for {} minutes.".format(sleep_minutes))
                    time.sleep(sleep_minutes * 60)
                elif response.status_code == 400:
                    response_data = json.loads(response.text)
                    msg = "Instagram's error message: {}"
                    self.logger.info(msg.format(response_data.get('message')))
                    if 'error_type' in response_data:
                        msg = 'Error type: {}'.format(response_data['error_type'])
                        self.logger.info(msg)

                # For debugging
                try:
                    self.last_response = response
                    self.last_json = json.loads(response.text)
                except Exception:
                    pass
                return False

    def edit_profile(self, external_url, phone_number, full_name, biography, email, gender, **rest):
        data = self.json_data({
            'external_url': external_url,
            'phone_number': phone_number,
            'username': self.username,
            'full_name': full_name,
            'biography': biography,
            'email': email,
            'gender': gender,
        })
        return self.send_request('accounts/edit_profile/', data)

    def get_story_feed(self, user_id):
        """
        last_json will have the form:
        {
            reel:
                items: {
                ...
                }
                id
                user
                expiring_at
                location
                reel_type
                title
                ...
            }
        }
        """
        url = "feed/user/{user_id}/story/".format(user_id=user_id)
        return self.send_request(url)

    def get_user_reel(self, user_id):
        url = 'feed/user/{}/reel_media/'.format(user_id)
        return self.send_request(url)

    def get_user_stories(self, user_id):
        url = 'feed/user/{}/story/'.format(user_id)
        return self.send_request(url)

    def get_self_story_viewers(self, story_id):

        url = 'media/{}/list_reel_media_viewer/?supported_capabilities_new={}'.format(story_id,
                                                                                      config.SUPPORTED_CAPABILITIES)
        return self.send_request(url)

    def get_tv_suggestions(self):
        url = 'igtv/tv_guide/'
        return self.send_request(url)

    def get_hashtag_stories(self, hashtag):
        url = 'tags/{}/story/'.format(hashtag)
        return self.send_request(url)

    def follow_hashtag(self, hashtag):
        data = self.json_data({})
        url = 'tags/follow/{}/'.format(hashtag)
        return self.send_request(url, data)

    def unfollow_hashtag(self, hashtag):
        data = self.json_data({})
        url = 'tags/unfollow/{}/'.format(hashtag)
        return self.send_request(url, data)

    def get_tags_followed_by_user(self, user_id):
        url = 'users/{}/following_tags_info/'.format(user_id)
        return self.send_request(url)

    def get_hashtag_sections(self, hashtag):
        data = self.json_data({'supported_tabs': "['top','recent','places']", 'include_persistent': 'true'})
        url = 'tags/{}/sections/'.format(hashtag)
        return self.send_request(url, data)

    def get_media_insight(self, media_id):
        url = 'insights/media_organic_insights/{}/?ig_sig_key_version={}'.format(media_id, config.IG_SIG_KEY)
        return self.send_request(url)

    def get_self_insight(self):
        url = 'insights/account_organic_insights/?show_promotions_in_landing_page=true&first={}'.format()  # todo
        return self.send_request(url)

    def save_media(self, media_id):
        data = self.json_data()
        url = 'media/{}/save/'.format(media_id)
        return self.send_request(url, data)

    def unsave_media(self, media_id):
        data = self.json_data()
        url = 'media/{}/unsave/'.format(media_id)
        return self.send_request(url, data)

    def get_saved_medias(self):
        url = 'feed/saved/'
        return self.send_request(url)

    ########################################################################################################

    def set_user(self, username, password):
        self.username = username
        self.password = password
        self.uuid = self.generate_UUID(uuid_type=True)

    def login(self, username=None, password=None, force=False, proxy=None,
              use_cookie=False, cookie_fname='cookie.txt'):
        if password is None:
            username, password = get_credentials(username=username)

        self.device_id = self.generate_device_id(self.get_seed(username, password))
        self.proxy = proxy
        self.set_user(username, password)

        cookie_is_loaded = False
        if use_cookie:
            try:
                self.load_cookie(cookie_fname)
                cookie_is_loaded = True
                self.is_logged_in = True
                self.set_proxy()  # Only happens if `self.proxy`
                self.logger.info("Logged-in successfully as '{}' using the cookie!".format(self.username))
                return True
            except Exception:
                print("The cookie is not found, but don't worry `instabot`"
                      " will create it for you using your login details.")

        if not cookie_is_loaded and (not self.is_logged_in or force):
            self.session = requests.Session()
            self.set_proxy()  # Only happens if `self.proxy`
            url = 'si/fetch_headers/?challenge_type=signup&guid={uuid}'
            url = url.format(uuid=self.generate_UUID(False))
            if self.send_request(url, login=True):
                data = json.dumps({
                    'phone_id': self.generate_UUID(True),
                    '_csrftoken': self.token,
                    'username': self.username,
                    'guid': self.uuid,
                    'device_id': self.device_id,
                    'password': self.password,
                    'login_attempt_count': '0',
                })

                if self.send_request('accounts/login/', data, True):
                    self.is_logged_in = True
                    self.logger.info("Logged-in successfully as '{}'!".format(self.username))
                    if use_cookie:
                        self.save_cookie(cookie_fname)
                        self.logger.info("Saved cookie!")
                    return True
                else:
                    self.logger.info("Username or password is incorrect.")
                    delete_credentials()
                    return False

    def load_cookie(self, fname):
        # Python2 compatibility
        if PY2:
            FileNotFoundError = IOError

        try:
            with open(fname, 'r') as f:
                self.session = requests.Session()
                self.session.cookies = requests.utils.cookiejar_from_dict(json.load(f))
            cookie_username = self.cookie_dict['ds_user']
            assert cookie_username == self.username
        except FileNotFoundError:
            raise Exception('Cookie file `{}` not found'.format(fname))
        except (TypeError, EOFError):
            os.remove(fname)
            msg = ('An error occured opening the cookie `{}`, '
                   'it will be removed an recreated.')
            raise Exception(msg.format(fname))
        except AssertionError:
            msg = 'The loaded cookie was for {} instead of {}.'
            raise Exception(msg.format(cookie_username, self.username))

    def save_cookie(self, fname):
        with open(fname, 'w') as f:
            json.dump(requests.utils.dict_from_cookiejar(self.session.cookies), f)

    def logout(self, *args, **kwargs):
        if not self.is_logged_in:
            return True
        self.is_logged_in = not self.send_request('accounts/logout/')
        return not self.is_logged_in

    def set_proxy(self):
        if self.proxy:
            parsed = urllib.parse.urlparse(self.proxy)
            scheme = 'http://' if not parsed.scheme else ''
            self.session.proxies['http'] = scheme + self.proxy
            self.session.proxies['https'] = scheme + self.proxy


    @property
    def cookie_dict(self):
        return self.session.cookies.get_dict()

    @property
    def token(self):
        return self.cookie_dict['csrftoken']

    @property
    def user_id(self):
        return self.cookie_dict['ds_user_id']

    @property
    def rank_token(self):
        return "{}_{}".format(self.user_id, self.uuid)

    @property
    def default_data(self):
        return {
            '_uuid': self.uuid,
            '_uid': self.user_id,
            '_csrftoken': self.token,
        }

    def json_data(self, data=None):
        """Adds the default_data to data and dumps it to a json."""
        if data is None:
            data = {}
        data.update(self.default_data)
        return json.dumps(data)

    def sync_features(self):
        data = self.json_data({'id': self.user_id, 'experiments': config.EXPERIMENTS})
        return self.send_request('qe/sync/', data)

    def auto_complete_user_list(self):
        return self.send_request('friendships/autocomplete_user_list/')

    def get_timeline_feed(self):
        """ Returns 8 medias from timeline feed of logged user."""
        return self.send_request('feed/timeline/')

    def get_megaphone_log(self):
        return self.send_request('megaphone/log/')

    def expose(self):
        data = self.json_data({
            'id': self.user_id,
            'experiment': 'ig_android_profile_contextual_feed'
        })
        return self.send_request('qe/expose/', data)

    def upload_photo(self, photo, caption=None, upload_id=None, from_video=False):
        return upload_photo(self, photo, caption, upload_id, from_video)

    def download_photo(self, media_id, filename, media=False, folder='photos'):
        return download_photo(self, media_id, filename, media, folder)

    def configure_photo(self, upload_id, photo, caption=''):
        return configure_photo(self, upload_id, photo, caption)

    def upload_video(self, photo, caption=None, upload_id=None):
        return upload_video(self, photo, caption, upload_id)

    def download_video(self, media_id, filename, media=False, folder='video'):
        return download_video(self, media_id, filename, media, folder)

    def configure_video(self, upload_id, video, thumbnail, width, height, duration, caption=''):
        return configure_video(self, upload_id, video, thumbnail, width, height, duration, caption)

    def edit_media(self, media_id, captionText=''):
        data = self.json_data({'caption_text': captionText})
        url = 'media/{media_id}/edit_media/'.format(media_id=media_id)
        return self.send_request(url, data)

    def remove_self_tag(self, media_id):
        data = self.json_data()
        url = 'media/{media_id}/remove/'.format(media_id=media_id)
        return self.send_request(url, data)

    def media_info(self, media_id):
        # data = self.json_data({'media_id': media_id})
        url = 'media/{media_id}/info/'.format(media_id=media_id)
        return self.send_request(url)

    def archive_media(self, media, undo=False):
        action = 'only_me' if not undo else 'undo_only_me'
        data = self.json_data({'media_id': media['id']})
        url = 'media/{media_id}/{action}/?media_type={media_type}'.format(
            media_id=media['id'],
            action=action,
            media_type=media['media_type']
        )
        return self.send_request(url, data)

    def delete_media(self, media):
        data = self.json_data({'media_id': media.get('id')})
        url = 'media/{media_id}/delete/'.format(media_id=media.get('id'))
        return self.send_request(url, data)

    def change_password(self, new_password):
        data = self.json_data({
            'old_password': self.password,
            'new_password1': new_password,
            'new_password2': new_password
        })
        return self.send_request('accounts/change_password/', data)

    def explore(self):
        return self.send_request('discover/explore/')

    def comment(self, media_id, comment_text):
        data = self.json_data({'comment_text': comment_text})
        url = 'media/{media_id}/comment/'.format(media_id=media_id)
        return self.send_request(url, data)

    def reply_to_comment(self, media_id, comment_text, parent_comment_id):
        data = self.json_data({'comment_text': comment_text, 'replied_to_comment_id': parent_comment_id})
        url = 'media/{media_id}/comment/'.format(media_id=media_id)
        return self.send_request(url, data)

    def delete_comment(self, media_id, comment_id):
        data = self.json_data()
        url = 'media/{media_id}/comment/{comment_id}/delete/'
        url = url.format(media_id=media_id, comment_id=comment_id)
        return self.send_request(url, data)

    def get_username_info(self, user_id):
        url = 'users/{user_id}/info/'.format(user_id=user_id)
        return self.send_request(url)

    def get_self_username_info(self):
        return self.get_username_info(self.user_id)

    def get_recent_activity(self):
        return self.send_request('news/inbox/?')

    def get_following_recent_activity(self):
        return self.send_request('news/?')

    def getv2Inbox(self):
        return self.send_request('direct_v2/inbox/?')

    def get_user_tags(self, user_id):
        url = 'usertags/{user_id}/feed/?rank_token={rank_token}&ranked_content=true&'
        url = url.format(user_id=user_id, rank_token=self.rank_token)
        return self.send_request(url)

    def get_self_user_tags(self):
        return self.get_user_tags(self.user_id)

    def tag_feed(self, tag):
        url = 'feed/tag/{tag}/?rank_token={rank_token}&ranked_content=true&'
        return self.send_request(url.format(tag=tag, rank_token=self.rank_token))

    def get_comment_likers(self, comment_id):
        url = 'media/{comment_id}/comment_likers/?'.format(comment_id=comment_id)
        return self.send_request(url)

    def get_media_likers(self, media_id):
        url = 'media/{media_id}/likers/?'.format(media_id=media_id)
        return self.send_request(url)

    def get_geo_media(self, user_id):
        url = 'maps/user/{user_id}/'.format(user_id=user_id)
        return self.send_request(url)

    def get_self_geo_media(self):
        return self.get_geo_media(self.user_id)

    def sync_from_adress_book(self, contacts):
        url = 'address_book/link/?include=extra_display_name,thumbnails'
        return self.send_request(url, 'contacts=' + json.dumps(contacts))

    def get_timeline(self):
        url = 'feed/timeline/?rank_token={rank_token}&ranked_content=true&'
        return self.send_request(url.format(rank_token=self.rank_token))

    def get_archive_feed(self):
        url = 'feed/only_me_feed/?rank_token={rank_token}&ranked_content=true&'
        return self.send_request(url.format(rank_token=self.rank_token))

    def get_user_feed(self, user_id, max_id='', min_timestamp=None):
        url = 'feed/user/{user_id}/?max_id={max_id}&min_timestamp={min_timestamp}&rank_token={rank_token}&ranked_content=true'
        url = url.format(
            user_id=user_id,
            max_id=max_id,
            min_timestamp=min_timestamp,
            rank_token=self.rank_token
        )
        return self.send_request(url)

    def get_self_user_feed(self, max_id='', min_timestamp=None):
        return self.get_user_feed(self.user_id, max_id, min_timestamp)

    def get_hashtag_feed(self, hashtag, max_id=''):
        url = 'feed/tag/{hashtag}/?max_id={max_id}&rank_token={rank_token}&ranked_content=true&'
        url = url.format(
            hashtag=hashtag,
            max_id=max_id,
            rank_token=self.rank_token
        )
        return self.send_request(url)

    def get_location_feed(self, location_id, max_id=''):
        url = 'feed/location/{location_id}/?max_id={max_id}&rank_token={rank_token}&ranked_content=true&'
        url = url.format(
            location_id=location_id,
            max_id=max_id,
            rank_token=self.rank_token
        )
        return self.send_request(url)

    def get_popular_feed(self):
        url = 'feed/popular/?people_teaser_supported=1&rank_token={rank_token}&ranked_content=true&'
        return self.send_request(url.format(rank_token=self.rank_token))

    def get_user_followings(self, user_id, max_id=''):
        url = 'friendships/{user_id}/following/?max_id={max_id}&ig_sig_key_version={sig_key}&rank_token={rank_token}'
        url = url.format(
            user_id=user_id,
            max_id=max_id,
            sig_key=config.SIG_KEY_VERSION,
            rank_token=self.rank_token
        )
        return self.send_request(url)

    def get_self_users_following(self):
        return self.get_user_followings(self.user_id)

    def get_user_followers(self, user_id, max_id=''):
        url = 'friendships/{user_id}/followers/?rank_token={rank_token}'
        url = url.format(user_id=user_id, rank_token=self.rank_token)
        if max_id:
            url += '&max_id={max_id}'.format(max_id=max_id)
        return self.send_request(url)

    def get_self_user_followers(self):
        return self.followers

    def like_comment(self, comment_id):
        data = self.json_data()
        url = 'media/{comment_id}/comment_like/'.format(comment_id=comment_id)
        return self.send_request(url, data)

    def unlike_comment(self, comment_id):
        data = self.json_data()
        url = 'media/{comment_id}/comment_unlike/'.format(comment_id=comment_id)
        return self.send_request(url, data)

    def like(self, media_id):
        data = self.json_data({'media_id': media_id})
        url = 'media/{media_id}/like/'.format(media_id=media_id)
        return self.send_request(url, data)

    def unlike(self, media_id):
        data = self.json_data({'media_id': media_id})
        url = 'media/{media_id}/unlike/'.format(media_id=media_id)
        return self.send_request(url, data)

    def get_media_comments(self, media_id, max_id=''):
        url = 'media/{media_id}/comments/'.format(media_id=media_id)
        if max_id:
            url += '?max_id={max_id}'.format(max_id=max_id)
        return self.send_request(url)

    def get_direct_share(self):
        return self.send_request('direct_share/inbox/?')

    def follow(self, user_id):
        data = self.json_data({'user_id': user_id})
        url = 'friendships/create/{user_id}/'.format(user_id=user_id)
        return self.send_request(url, data)

    def unfollow(self, user_id):
        data = self.json_data({'user_id': user_id})
        url = 'friendships/destroy/{user_id}/'.format(user_id=user_id)
        return self.send_request(url, data)

    def block(self, user_id):
        data = self.json_data({'user_id': user_id})
        url = 'friendships/block/{user_id}/'.format(user_id=user_id)
        return self.send_request(url, data)

    def unblock(self, user_id):
        data = self.json_data({'user_id': user_id})
        url = 'friendships/unblock/{user_id}/'.format(user_id=user_id)
        return self.send_request(url, data)

    def user_friendship(self, user_id):
        data = self.json_data({'user_id': user_id})
        url = 'friendships/show/{user_id}/'.format(user_id=user_id)
        return self.send_request(url, data)

    @staticmethod
    def _prepare_recipients(users, thread_id=None, use_quotes=False):
        if not isinstance(users, list):
            print('Users must be an list')
            return False
        result = {'users': '[[{}]]'.format(','.join(users))}
        if thread_id:
            template = '["{}"]' if use_quotes else '[{}]'
            result['thread'] = template.format(thread_id)
        return result

    def send_direct_item(self, item_type, users, **options):
        data = {
            'client_context': self.generate_UUID(True),
            'action': 'send_item'
        }

        url = 'direct_v2/threads/broadcast/{}/'.format(item_type)
        text = options.get('text', '')
        if item_type == 'link':
            data['link_text'] = text
            data['link_urls'] = json.dumps(options.get('urls'))
        elif item_type == 'text':
            data['text'] = text
        elif item_type == 'media_share':
            data['text'] = text
            data['media_type'] = options.get('media_type', 'photo')
            data['media_id'] = options.get('media_id', '')
        elif item_type == 'hashtag':
            data['text'] = text
            data['hashtag'] = options.get('hashtag', '')
        elif item_type == 'profile':
            data['text'] = text
            data['profile_user_id'] = options.get('profile_user_id')

        recipients = self._prepare_recipients(users, options.get('thread'), use_quotes=False)
        if not recipients:
            return False
        data['recipient_users'] = recipients.get('users')
        if recipients.get('thread'):
            data['thread_ids'] = recipients.get('thread')
        data.update(self.default_data)
        return self.send_request(url, data, with_signature=False)

    @staticmethod
    def generate_signature(data):
        body = hmac.new(config.IG_SIG_KEY.encode('utf-8'), data.encode('utf-8'),
                        hashlib.sha256).hexdigest() + '.' + urllib.parse.quote(data)
        signature = 'ig_sig_key_version={sig_key}&signed_body={body}'
        return signature.format(sig_key=config.SIG_KEY_VERSION, body=body)

    @staticmethod
    def generate_device_id(seed):
        volatile_seed = "12345"
        m = hashlib.md5()
        m.update(seed.encode('utf-8') + volatile_seed.encode('utf-8'))
        return 'android-' + m.hexdigest()[:16]

    @staticmethod
    def get_seed(*args):
        m = hashlib.md5()
        m.update(b''.join([arg.encode('utf-8') for arg in args]))
        return m.hexdigest()

    @staticmethod
    def generate_UUID(uuid_type):
        generated_uuid = str(uuid.uuid4())
        if uuid_type:
            return generated_uuid
        else:
            return generated_uuid.replace('-', '')

    def get_liked_media(self, max_id=''):
        url = 'feed/liked/?max_id={max_id}'.format(max_id=max_id)
        return self.send_request(url)

    def get_total_followers_or_followings(self,
                                          user_id,
                                          amount=None,
                                          which='followers',
                                          filter_private=False,
                                          filter_business=False,
                                          filter_verified=False,
                                          usernames=False,
                                          to_file=None,
                                          overwrite=False):
        from io import StringIO

        if which == 'followers':
            key = 'follower_count'
            get = self.get_user_followers
        elif which == 'followings':
            key = 'following_count'
            get = self.get_user_followings

        sleep_track = 0
        result = []
        next_max_id = ''
        self.get_username_info(user_id)
        username_info = self.last_json
        if "user" in username_info:
            total = amount or username_info["user"][key]

            if total > 200000:
                print("Consider temporarily saving the result of this big "
                      "operation. This will take a while.\n")
        else:
            return False
        if filter_business:
            print("--> You are going to filter business accounts. This will take time! <--")
            from random import random
        if to_file is not None:
            if os.path.isfile(to_file):
                if not overwrite:
                    print("File `{}` already exists. Not overwriting.".format(to_file))
                    return False
                else:
                    print("Overwriting file `{}`".format(to_file))
            with open(to_file, 'w'):
                pass
        desc = "Getting {} of {}".format(which, user_id)
        with tqdm(total=total, desc=desc, leave=True) as pbar:
            while True:
                get(user_id, next_max_id)
                last_json = self.last_json
                try:
                    with open(to_file, 'a') if to_file is not None else StringIO() as f:
                        for item in last_json["users"]:
                            if filter_private and item['is_private']:
                                continue
                            if filter_business:
                                time.sleep(2 * random())
                                self.get_username_info(item['pk'])
                                item_info = self.last_json
                                if item_info['user']['is_business']:
                                    continue
                            if filter_verified and item['is_verified']:
                                continue
                            if to_file is not None:
                                if usernames:
                                    f.write("{}\n".format(item['username']))
                                else:
                                    f.write("{}\n".format(item['pk']))
                            result.append(item)
                            pbar.update(1)
                            sleep_track += 1
                            if sleep_track >= 20000:
                                sleep_time = uniform(120, 180)
                                msg = "\nWaiting {:.2f} min. due to too many requests."
                                print(msg.format(sleep_time / 60))
                                time.sleep(sleep_time)
                                sleep_track = 0
                    if not last_json["users"] or len(result) >= total:
                        return result[:total]
                except Exception as e:
                    print("ERROR: {}".format(e))
                    return result[:total]

                if last_json["big_list"] is False:
                    return result[:total]

                next_max_id = last_json.get("next_max_id", "")

    def get_total_followers(self, user_id, amount=None):
        return self.get_total_followers_or_followings(
            user_id, amount, 'followers')

    def get_total_followings(self, user_id, amount=None):
        return self.get_total_followers_or_followings(
            user_id, amount, 'followings')

    def get_total_user_feed(self, user_id, min_timestamp=None):
        return self.get_last_user_feed(user_id, amount=float('inf'), min_timestamp=min_timestamp)

    def get_last_user_feed(self, user_id, amount, min_timestamp=None):
        user_feed = []
        next_max_id = ''
        while True:
            if len(user_feed) >= float(amount):
                # one request returns max 13 items
                return user_feed[:amount]
            self.get_user_feed(user_id, next_max_id, min_timestamp)
            last_json = self.last_json
            if 'items' not in last_json:
                return user_feed
            user_feed += last_json["items"]
            if not last_json.get("more_available"):
                return user_feed
            next_max_id = last_json.get("next_max_id", "")

    def get_total_hashtag_feed(self, hashtag_str, amount=100):
        hashtag_feed = []
        next_max_id = ''

        with tqdm(total=amount, desc="Getting hashtag media.", leave=False) as pbar:
            while True:
                self.get_hashtag_feed(hashtag_str, next_max_id)
                last_json = self.last_json
                if 'items' not in last_json:
                    return hashtag_feed[:amount]
                items = last_json['items']
                try:
                    pbar.update(len(items))
                    hashtag_feed += items
                    if not items or len(hashtag_feed) >= amount:
                        return hashtag_feed[:amount]
                except Exception:
                    return hashtag_feed[:amount]
                next_max_id = last_json.get("next_max_id", "")

    def get_total_self_user_feed(self, min_timestamp=None):
        return self.get_total_user_feed(self.user_id, min_timestamp)

    def get_total_self_followers(self):
        return self.get_total_followers(self.user_id)

    def get_total_self_followings(self):
        return self.get_total_followings(self.user_id)

    def get_total_liked_media(self, scan_rate=1):
        next_id = ''
        liked_items = []
        for _ in range(scan_rate):
            self.get_liked_media(next_id)
            last_json = self.last_json
            next_id = last_json.get("next_max_id", "")
            liked_items += last_json["items"]
        return liked_items

    def remove_profile_picture(self):
        data = self.json_data()
        return self.send_request('accounts/remove_profile_picture/', data)

    def set_private_account(self):
        data = self.json_data()
        return self.send_request('accounts/set_private/', data)

    def set_public_account(self):
        data = self.json_data()
        return self.send_request('accounts/set_public/', data)

    def set_name_and_phone(self, name='', phone=''):
        data = self.json_data({'first_name': name, 'phone_number': phone})
        return self.send_request('accounts/set_phone_and_name/', data)

    def get_profile_data(self):
        data = self.json_data()
        return self.send_request('accounts/current_user/?edit=true', data)

    def edit_profile(self, url, phone, first_name, biography, email, gender):
        data = self.json_data({
            'external_url': url,
            'phone_number': phone,
            'username': self.username,
            'full_name': first_name,
            'biography': biography,
            'email': email,
            'gender': gender,
        })
        return self.send_request('accounts/edit_profile/', data)

    def fb_user_search(self, query):
        url = 'fbsearch/topsearch/?context=blended&query={query}&rank_token={rank_token}'
        url = url.format(query=query, rank_token=self.rank_token)
        return self.send_request(url)

    def search_users(self, query):
        url = 'users/search/?ig_sig_key_version={sig_key}&is_typeahead=true&query={query}&rank_token={rank_token}'
        url = url.format(
            sig_key=config.SIG_KEY_VERSION,
            query=query,
            rank_token=self.rank_token
        )
        return self.send_request(url)

    def search_username(self, username):
        url = 'users/{username}/usernameinfo/'.format(username=username)
        return self.send_request(url)

    def search_tags(self, query):
        url = 'tags/search/?is_typeahead=true&q={query}&rank_token={rank_token}'
        url = url.format(query=query, rank_token=self.rank_token)
        return self.send_request(url)

    def search_location(self, query='', lat=None, lng=None):
        url = 'fbsearch/places/?rank_token={rank_token}&query={query}&lat={lat}&lng={lng}'
        url = url.format(rank_token=self.rank_token, query=query, lat=lat, lng=lng)
        return self.send_request(url)

    def get_user_reel(self, user_id):
        url = 'feed/user/{}/reel_media/'.format(user_id)
        return self.send_request(url)

    def get_user_stories(self, user_id):
        url = 'feed/user/{}/story/'.format(user_id)
        return self.send_request(url)

    def get_self_story_viewers(self, story_id):

        url = 'media/{}/list_reel_media_viewer/?supported_capabilities_new={}'.format(story_id,
                                                                                      config.SUPPORTED_CAPABILITIES)
        return self.send_request(url)

    def get_tv_suggestions(self):
        url = 'igtv/tv_guide/'
        return self.send_request(url)

    def get_hashtag_stories(self, hashtag):
        url = 'tags/{}/story/'.format(hashtag)
        return self.send_request(url)

    def follow_hashtag(self, hashtag):
        data = self.json_data({})
        url = 'tags/follow/{}/'.format(hashtag)
        return self.send_request(url, data)

    def unfollow_hashtag(self, hashtag):
        data = self.json_data({})
        url = 'tags/unfollow/{}/'.format(hashtag)
        return self.send_request(url, data)

    def get_tags_followed_by_user(self, user_id):
        url = 'users/{}/following_tags_info/'.format(user_id)
        return self.send_request(url)

    def get_hashtag_sections(self, hashtag):
        data = self.json_data({'supported_tabs': "['top','recent','places']", 'include_persistent': 'true'})
        url = 'tags/{}/sections/'.format(hashtag)
        return self.send_request(url, data)

    def get_media_insight(self, media_id):
        url = 'insights/media_organic_insights/{}/?ig_sig_key_version={}'.format(media_id, config.IG_SIG_KEY)
        return self.send_request(url)

    def get_self_insight(self):
        url = 'insights/account_organic_insights/?show_promotions_in_landing_page=true&first={}'.format()  # todo
        return self.send_request(url)

    def save_media(self, media_id):
        data = self.json_data()
        url = 'media/{}/save/'.format(media_id)
        return self.send_request(url, data)

    def unsave_media(self, media_id):
        data = self.json_data()
        url = 'media/{}/unsave/'.format(media_id)
        return self.send_request(url, data)

    def get_saved_medias(self):
        url = 'feed/saved/'
        return self.send_request(url)



def colred_formatter():
    format = '%(asctime)s | %(levelname)-8s | %(message)s'
    cformat = '%(log_color)s' + format
    date_format = '%Y-%m-%d %H:%M'
    return ColoredFormatter(cformat, date_format,
                            log_colors={'DEBUG': 'reset', 'INFO': 'green',
                                        'WARNING': 'yellow', 'ERROR': 'red',
                                        'CRITICAL': 'red'})

# def file_formatter():
#     format = '%(asctime)s | %(levelname)-8s | %(message)s'
#     cformat = '%(log_color)s' + format
#     date_format = '%Y-%m-%d %H:%M'
#     return HTMLFormatter(cformat, date_format,)
