# -*- coding: utf-8 -*-

"""
Client that Jikers play with
"""

import webbrowser
from threading import Timer
from .session import JikeSession
from .objects import List, Stream, User, Topic, JikeEmitter
from .utils import *
from .constants import ENDPOINTS, URL_VALIDATION_PATTERN, CHECK_UNREAD_COUNT_PERIOD


def check_unread_count_periodically(obj):
    """
    Run periodical task to check unread count and do some automatic task
    """
    obj.get_news_feed_unread_count()
    unread = auto_load_unread(obj)
    notify_update(obj, unread)
    Timer(
        CHECK_UNREAD_COUNT_PERIOD,
        check_unread_count_periodically,
        args=(obj,)
    ).start()


def auto_load_unread(obj):
    unread_news_feed = obj._load_unread('news_feed')
    unread_following_update = obj._load_unread('following_update')
    return unread_news_feed, unread_following_update


def notify_update(obj, unread):
    for t in unread:
        for message in t:
            assert hasattr(message, 'type') and hasattr(message, 'content')
            if message.type == 'OFFICIAL_MESSAGE' and (message.topic['content'] in obj.notified_topics or 'all' in obj.notified_topics):
                title = '{} 更新了'.format(message.topic['content'])
                msg = message.content
                notify(title, msg)
            elif message.type == 'ORIGINAL_POST' and (message.user['screenName'] in obj.notified_users or 'all' in obj.notified_users):
                title = '{} 发动态了'.format(message.user['screenName'])
                msg = message.content
                notify(title, msg)


class JikeClient:
    def __init__(self, sync_unread=False):
        self.auth_token = read_token()
        if self.auth_token is None:
            self.auth_token = login()
            try:
                write_token(self.auth_token)
            except IOError:
                pass
        self.jike_session = JikeSession(self.auth_token)

        self.collection = None
        self.news_feed = None
        self.following_update = None

        self.unread_count = 0
        self.timer = None
        if sync_unread:
            self.timer = Timer(
                CHECK_UNREAD_COUNT_PERIOD,
                check_unread_count_periodically,
                args=(self,)
            ).start()

        self.notified_topics = ['all']
        self.notified_users = ['all']

    def __del__(self):
        if self.timer:
            self.timer.cancel()

    def get_my_profile(self):
        return self.get_user_profile(username=None)

    def get_my_collection(self):
        if self.collection is None:
            self.collection = List(self.jike_session, ENDPOINTS['my_collections'])
            self.collection.load_more()
        return self.collection

    def get_news_feed_unread_count(self):
        res = self.jike_session.get(ENDPOINTS['news_feed_unread_count'])
        if res.status_code == 200:
            result = res.json()
            self.unread_count = result['newMessageCount']
            return self.unread_count
        res.raise_for_status()

    def get_news_feed(self):
        if self.news_feed is None:
            self.news_feed = Stream(self.jike_session, ENDPOINTS['news_feed'])
            self.news_feed.load_more()
        return self.news_feed

    def get_following_update(self):
        if self.following_update is None:
            self.following_update = Stream(self.jike_session, ENDPOINTS['following_update'])
            self.following_update.load_more()
        return self.following_update

    def get_user_profile(self, username):
        res = self.jike_session.get(ENDPOINTS['user_profile'], {
            'username': username
        })
        if res.status_code == 200:
            result = res.json()
            result['user'].update(result['statsCount'])
            return User(**result['user'])
        res.raise_for_status()

    def get_user_post(self, username, limit=20):
        posts = List(self.jike_session, ENDPOINTS['user_post'], {'username': username})
        posts.load_more(limit)
        return posts

    def get_user_created_topic(self, username, limit=20):
        created_topics = List(self.jike_session, ENDPOINTS['user_created_topic'], {'username': username}, Topic)
        created_topics.load_more(limit)
        return created_topics

    def get_user_subscribed_topic(self, username, limit=20):
        subscribed_topics = List(self.jike_session, ENDPOINTS['user_subscribed_topic'], {'username': username}, Topic)
        subscribed_topics.load_more(limit)
        return subscribed_topics

    def get_user_following(self, username, limit=20):
        user_followings = List(self.jike_session, ENDPOINTS['user_following'], {'username': username}, User)
        user_followings.load_more(limit)
        return user_followings

    def get_user_follower(self, username, limit=20):
        user_followers = List(self.jike_session, ENDPOINTS['user_follower'], {'username': username}, User)
        user_followers.load_more(limit)
        return user_followers

    def get_comment(self, message):
        assert hasattr(message, 'id') and hasattr(message, 'type')
        comments = Stream(self.jike_session, ENDPOINTS['list_comment'], {
            'targetId': message.id,
            'targetType': message.type
        })
        comments.load_more()
        return comments

    def get_topic_selected(self, topic_id):
        topic_selected = Stream(self.jike_session, ENDPOINTS['topic_selected'], {
            'topic': topic_id
        })
        topic_selected.load_more()
        return topic_selected

    def get_topic_square(self, topic_id):
        topic_square = Stream(self.jike_session, ENDPOINTS['topic_square'], {
            'topicId': topic_id
        })
        topic_square.load_more()
        return topic_square

    @staticmethod
    def open_in_browser(url_or_message):
        if isinstance(url_or_message, str):
            url = url_or_message
        elif hasattr(url_or_message, 'linkInfo'):
            url = url_or_message.linkInfo['linkUrl']
        elif 'linkInfo' in url_or_message:
            url = url_or_message['linkInfo']['linkUrl']
        elif hasattr(url_or_message, 'content'):
            urls = extract_url(url_or_message.content)
            if urls:
                for url in urls:
                    webbrowser.open(url)
                return
        else:
            raise ValueError('No url found')

        if not URL_VALIDATION_PATTERN.match(url):
            raise ValueError('Url invalid')
        else:
            webbrowser.open(url)

    def create_my_post(self, content, link=None, topic_id=None, pictures=None):
        assert isinstance(content, str)
        if link and pictures:
            raise ValueError('Jike cannot post thought with both pictures and link')

        payload = {
            'content': content
        }
        if link:
            assert URL_VALIDATION_PATTERN.match(link), 'Invalid link'
            payload.update({'linkInfo': extract_link(self.jike_session, link)})
        if topic_id:
            payload.update({'submitToTopic': topic_id})
        if pictures:
            uploaded_picture_keys = upload_pictures(pictures)
            payload.update({'pictureKeys': uploaded_picture_keys})

        res = self.jike_session.post(ENDPOINTS['create_post'], json=payload)
        post = None
        if res.status_code == 200:
            result = res.json()
            if result['success']:
                post = OriginalPost(**result['data'])
            else:
                raise RuntimeError('Post fail')
        res.raise_for_status()
        return post

    def delete_my_post(self, post):
        assert hasattr(post, 'type') and hasattr(post, 'id')
        res = self.jike_session.post(ENDPOINTS['delete_post'], json={
            'id': post.id,
        })
        if res.status_code == 200:
            return res.json()['success']
        res.raise_for_status()

    def _like_action(self, message, action):
        assert hasattr(message, 'type') and hasattr(message, 'id')
        assert message.type in converter, 'Unsupported message type'
        assert action in ['like_it', 'unlike_it']
        message_type = ''.join([w.title() if i != 0 else w.lower()
                                for i, w in enumerate(message.type.split('_'))]) + 's'
        endpoint = ENDPOINTS[action].format(t=message_type)
        payload = {
            'id': message.id,
        }
        if hasattr(message, 'targetType'):
            payload.update({'targetType': message.targetType})
        res = self.jike_session.post(endpoint, json=payload)
        if res.status_code == 200:
            return res.json()['success']
        res.raise_for_status()

    def like_it(self, message):
        return self._like_action(message, 'like_it')

    def unlike_it(self, message):
        return self._like_action(message, 'unlike_it')

    def _collect_action(self, message, action):
        assert hasattr(message, 'type') and hasattr(message, 'id')
        assert message.type in converter, 'Unsupported message type'
        assert action in ['collect_it', 'uncollect_it']
        message_type = ''.join([w.title() if i != 0 else w.lower()
                                for i, w in enumerate(message.type.split('_'))]) + 's'
        endpoint = ENDPOINTS[action].format(t=message_type)
        payload = {
            'id': message.id,
        }
        res = self.jike_session.post(endpoint, json=payload)
        if res.status_code == 200:
            return res.json()['success']
        res.raise_for_status()

    def collect_it(self, message):
        return self._collect_action(message, 'collect_it')

    def uncollect_it(self, message):
        return self._collect_action(message, 'uncollect_it')

    def repost_it(self, content, message, sync_comment=True):
        assert isinstance(content, str)
        assert hasattr(message, 'type') and hasattr(message, 'id')
        assert message.type in converter, 'Unsupported message type'
        payload = {
            'content': content,
            'syncComment': sync_comment,
            'targetId': message.id,
            'targetType': message.type,
        }
        res = self.jike_session.post(ENDPOINTS['repost_it'], json=payload)
        repost = None
        if res.status_code == 200:
            result = res.json()
            if result['success']:
                repost = Repost(**result['data'])
            else:
                raise RuntimeError('Repost fail')
        res.raise_for_status()
        return repost

    def comment_it(self, content, message, pictures=None, sync2personal_updates=True):
        assert isinstance(content, str)
        assert hasattr(message, 'type') and hasattr(message, 'id')
        assert message.type in converter, 'Unsupported message type'
        payload = {
            'content': content,
            'pictureKeys': [],
            'syncToPersonalUpdates': sync2personal_updates,
            'targetId': message.id,
            'targetType': message.type,
        }
        if pictures:
            uploaded_picture_keys = upload_pictures(pictures)
            payload.update({'pictureKeys': uploaded_picture_keys})
        res = self.jike_session.post(ENDPOINTS['comment_it'], json=payload)
        comment = None
        if res.status_code == 200:
            result = res.json()
            if result['success']:
                comment = Comment(**result['data'])
            else:
                raise RuntimeError('Comment fail')
        res.raise_for_status()
        return comment

    def search_topic(self, keywords):
        assert isinstance(keywords, str)
        topics = List(self.jike_session, ENDPOINTS['search_topic'], type_converter=Topic, fixed_extra_payload={
            'keywords': keywords,
            'onlyUserPostEnabled': False,
            'type': 'ALL'
        })
        topics.load_more()
        return topics

    def search_collection(self, keywords):
        assert isinstance(keywords, str)
        messages = List(self.jike_session, ENDPOINTS['search_collection'], fixed_extra_payload={
            'keywords': keywords,
        })
        messages.load_more()
        return messages

    def get_recommended_topic(self):
        topics = List(self.jike_session, ENDPOINTS['recommended_topic'], type_converter=Topic, fixed_extra_payload={
            'categoryAlias': 'RECOMMENDATION',
        })
        topics.load_more()
        return topics

    def create_emitter(self, endpoint, fixed_extra_payload=()):
        """
        BOOM! You find easter egg in this project, now you can use this function to crawl Jike.

        USE IT WISELY !
        """
        assert endpoint in ENDPOINTS.values()
        return JikeEmitter(self.jike_session, endpoint, fixed_extra_payload)

    def schedule_my_post(self, content, link=None, topic_id=None, pictures=None, *, delay=None):
        assert isinstance(delay, int) and delay > 0, 'Please provide a delay time'
        post_fn = self.create_my_post
        timer = Timer(delay, post_fn, args=(content, link, topic_id, pictures))
        timer.start()
        return timer

    def _load_unread(self, choice):
        if choice == 'news_feed':
            if self.news_feed:
                return self.news_feed.load_update(unread_count=self.unread_count+3)
        elif choice == 'following_update':
            if self.following_update:
                return self.following_update.load_update(unread_count=self.unread_count+3)
        else:
            raise ValueError('choice only can be "news_feed" or "following_update"')
        return []

    def set_automatic_rules(self, notified_topics, notified_users):
        self.notified_topics = notified_topics
        self.notified_users = notified_users

    def _create_new_jike_session(self):
        """
        Create a new session of `requests.Session`

        CAUTION: Could be used for concurrency http request, but not tested and verified by author
        """
        return JikeSession(self.auth_token)

    def relogin(self):
        """
        Re-login in case any problem related to auth_token
        """
        self.auth_token = login()
        write_token(self.auth_token)
        self.jike_session.session.close()
        self.jike_session = JikeSession(self.auth_token)