# -*- coding: utf-8 -*-
import json
import logging
from datetime import date, datetime, time

from prettytable import PrettyTable

from nectar.instance import shared_blockchain_instance
from nectargraphenebase.py23 import integer_types, string_types

from .blockchainobject import BlockchainObject
from .exceptions import AccountDoesNotExistsException, OfflineHasNoRPCException
from .utils import (
    addTzInfo,
    formatTimeString,
)

log = logging.getLogger(__name__)


class Community(BlockchainObject):
    """This class allows to easily access Community data

    :param str account: Name of the account
    :param Steem/Hive blockchain_instance: Hive or Steem
           instance
    :param bool lazy: Use lazy loading
    :param bool full: Obtain all account data including orders, positions,
           etc.
    :param Hive hive_instance: Hive instance
    :param Steem steem_instance: Steem instance
    :returns: Account data
    :rtype: dictionary
    :raises nectar.exceptions.AccountDoesNotExistsException: if account
            does not exist

    Instances of this class are dictionaries that come with additional
    methods (see below) that allow dealing with an community and its
    corresponding functions.

    .. code-block:: python

        >>> from nectar.community import Community
        >>> from nectar import Hive
        >>> from nectar.nodelist import NodeList
        >>> nodelist = NodeList()
        >>> nodelist.update_nodes()
        >>> stm = Hive(node=nodelist.get_hive_nodes())
        >>> community = Community("hive-139531", blockchain_instance=stm)
        >>> print(community)
        <Community hive-139531>
        >>> print(community.balances) # doctest: +SKIP

    .. note:: This class comes with its own caching function to reduce the
              load on the API server. Instances of this class can be
              refreshed with ``Community.refresh()``. The cache can be
              cleared with ``Community.clear_cache()``

    """

    type_id = 2

    def __init__(
        self, community, observer="", full=True, lazy=False, blockchain_instance=None, **kwargs
    ):
        """Initialize an community

        :param str community: Name of the community
        :param Hive/Steem blockchain_instance: Hive/Steem
               instance
        :param bool lazy: Use lazy loading
        :param bool full: Obtain all community data including orders, positions,
               etc.
        """
        self.full = full
        self.lazy = lazy
        self.observer = observer
        if blockchain_instance is None:
            if kwargs.get("steem_instance"):
                blockchain_instance = kwargs["steem_instance"]
            elif kwargs.get("hive_instance"):
                blockchain_instance = kwargs["hive_instance"]
        self.blockchain = blockchain_instance or shared_blockchain_instance()
        if isinstance(community, dict):
            community = self._parse_json_data(community)
        super(Community, self).__init__(
            community, lazy=lazy, full=full, id_item="name", blockchain_instance=blockchain_instance
        )

    def refresh(self):
        """Refresh/Obtain an community's data from the API server"""
        if not self.blockchain.is_connected():
            return
        self.blockchain.rpc.set_next_node_on_empty_reply(True)
        community = self.blockchain.rpc.get_community(
            {"name": self.identifier, "observer": self.observer}, api="bridge"
        )

        if not community:
            raise AccountDoesNotExistsException(self.identifier)
        community = self._parse_json_data(community)
        self.identifier = community["name"]
        # self.blockchain.refresh_data()

        super(Community, self).__init__(
            community,
            id_item="name",
            lazy=self.lazy,
            full=self.full,
            blockchain_instance=self.blockchain,
        )

    def _parse_json_data(self, community):
        parse_int = [
            "sum_pending",
            "subscribers",
            "num_pending",
            "num_authors",
        ]
        for p in parse_int:
            if p in community and isinstance(community.get(p), string_types):
                community[p] = int(community.get(p, 0))
        parse_times = ["created_at"]
        for p in parse_times:
            if p in community and isinstance(community.get(p), string_types):
                community[p] = addTzInfo(
                    datetime.strptime(community.get(p, "1970-01-01 00:00:00"), "%Y-%m-%d %H:%M:%S")
                )
        return community

    def json(self):
        output = self.copy()
        parse_int = [
            "sum_pending",
            "subscribers",
            "num_pending",
            "num_authors",
        ]
        parse_int_without_zero = []
        for p in parse_int:
            if p in output and isinstance(output[p], integer_types):
                output[p] = str(output[p])
        for p in parse_int_without_zero:
            if p in output and isinstance(output[p], integer_types) and output[p] != 0:
                output[p] = str(output[p])

        parse_times = [
            "created_at",
        ]
        for p in parse_times:
            if p in output:
                p_date = output.get(p, datetime(1970, 1, 1, 0, 0))
                if isinstance(p_date, (datetime, date, time)):
                    output[p] = formatTimeString(p_date).replace("T", " ")
                else:
                    output[p] = p_date
        return json.loads(str(json.dumps(output)))

    def get_community_roles(self):
        """Lists community roles"""
        community = self["name"]
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        return self.blockchain.rpc.list_community_roles({"community": community}, api="bridge")

    def get_subscribers(self):
        """Returns subscribers"""
        community = self["name"]
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        return self.blockchain.rpc.list_subscribers({"community": community}, api="bridge")

    def get_activities(self, limit=100, last_id=None):
        """Returns community activity"""
        community = self["name"]
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        return self.blockchain.rpc.account_notifications(
            {"account": community, "limit": limit, "last_id": last_id}, api="bridge"
        )

    def get_ranked_posts(
        self, observer=None, limit=100, start_author=None, start_permlink=None, sort="created"
    ):
        """Returns community post"""
        community = self["name"]
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        self.blockchain.rpc.set_next_node_on_empty_reply(False)
        return self.blockchain.rpc.get_ranked_posts(
            {
                "tag": community,
                "observer": observer,
                "limit": limit,
                "start_author": start_author,
                "start_permlink": start_permlink,
                "sort": sort,
            },
            api="bridge",
        )

    def set_role(self, account, role, mod_account):
        """Set role for a given account

        :param str account: Set role of this account
        :param str role: Can be member, mod, admin, owner, guest
        :param str mod_account: Account who broadcast this, (mods or higher)

        """
        community = self["name"]
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        json_body = [
            "setRole",
            {
                "community": community,
                "account": account,
                "role": role,
            },
        ]
        return self.blockchain.custom_json(
            "community", json_body, required_posting_auths=[mod_account]
        )

    def set_user_title(self, account, title, mod_account):
        """Set title for a given account

        :param str account: Set role of this account
        :param str title: Title
        :param str mod_account: Account who broadcast this, (mods or higher)

        """
        community = self["name"]
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        json_body = [
            "setUserTitle",
            {
                "community": community,
                "account": account,
                "title": title,
            },
        ]
        return self.blockchain.custom_json(
            "community", json_body, required_posting_auths=[mod_account]
        )

    def mute_post(self, account, permlink, notes, mod_account):
        """Mutes a post

        :param str account: Set role of this account
        :param str permlink: permlink
        :param str notes: permlink
        :param str mod_account: Account who broadcast this, (mods or higher)

        """
        community = self["name"]
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        json_body = [
            "mutePost",
            {"community": community, "account": account, "permlink": permlink, "notes": notes},
        ]
        return self.blockchain.custom_json(
            "community", json_body, required_posting_auths=[mod_account]
        )

    def unmute_post(self, account, permlink, notes, mod_account):
        """Unmute a post

        :param str account: post author
        :param str permlink: permlink
        :param str notes: notes
        :param str mod_account: Account who broadcast this, (mods or higher)

        """
        community = self["name"]
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        json_body = [
            "unmutePost",
            {"community": community, "account": account, "permlink": permlink, "notes": notes},
        ]
        return self.blockchain.custom_json(
            "community", json_body, required_posting_auths=[mod_account]
        )

    def update_props(self, title, about, is_nsfw, description, flag_text, admin_account):
        """Updates the community properties

        :param str title: Community title
        :param str about: about
        :param bool is_nsfw: is_nsfw
        :param str description: description
        :param str flag_text: flag_text
        :param str admin_account: Account who broadcast this, (admin or higher)

        """
        community = self["name"]
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        json_body = [
            "updateProps",
            {
                "community": community,
                "props": {
                    "title": title,
                    "about": about,
                    "is_nsfw": is_nsfw,
                    "description": description,
                    "flag_text": flag_text,
                },
            },
        ]
        return self.blockchain.custom_json(
            "community", json_body, required_posting_auths=[admin_account]
        )

    def subscribe(self, account):
        """subscribe to a community

        :param str account: account who suscribe to the community (is also broadcasting the custom_json)

        """
        community = self["name"]
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        json_body = [
            "subscribe",
            {
                "community": community,
            },
        ]
        return self.blockchain.custom_json("community", json_body, required_posting_auths=[account])

    def pin_post(self, account, permlink, mod_account):
        """Stickes a post to the top of a community

        :param str account: post author
        :param str permlink: permlink
        :param str mod_account: Account who broadcast this, (mods or higher)

        """
        community = self["name"]
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        json_body = [
            "pinPost",
            {
                "community": community,
                "account": account,
                "permlink": permlink,
            },
        ]
        return self.blockchain.custom_json(
            "community", json_body, required_posting_auths=[mod_account]
        )

    def unsubscribe(self, account):
        """unsubscribe a community

        :param str account: account who unsuscribe to the community (is also broadcasting the custom_json)

        """
        community = self["name"]
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        json_body = [
            "unsubscribe",
            {
                "community": community,
            },
        ]
        return self.blockchain.custom_json("community", json_body, required_posting_auths=[account])

    def unpin_post(self, account, permlink, mod_account):
        """Removes a post from the top of a community

        :param str account: post author
        :param str permlink: permlink
        :param str mod_account: Account who broadcast this, (mods or higher)

        """
        community = self["name"]
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        json_body = [
            "unpinPost",
            {
                "community": community,
                "account": account,
                "permlink": permlink,
            },
        ]
        return self.blockchain.custom_json(
            "community", json_body, required_posting_auths=[mod_account]
        )

    def flag_post(self, account, permlink, notes, reporter):
        """Suggest a post for the review queue

        :param str account: post author
        :param str permlink: permlink
        :param str notes: notes
        :param str reporter: Account who broadcast this
        """
        community = self["name"]
        if not self.blockchain.is_connected():
            raise OfflineHasNoRPCException("No RPC available in offline mode!")
        json_body = [
            "flagPost",
            {"community": community, "account": account, "permlink": permlink, "notes": notes},
        ]
        return self.blockchain.custom_json(
            "community", json_body, required_posting_auths=[reporter]
        )


class CommunityObject(list):
    def printAsTable(self):
        t = PrettyTable(
            [
                "Nr.",
                "Name",
                "Title",
                "lang",
                "subscribers",
                "sum_pending",
                "num_pending",
                "num_authors",
            ]
        )
        t.align = "l"
        count = 0
        for community in self:
            count += 1
            t.add_row(
                [
                    str(count),
                    community["name"],
                    community["title"],
                    community["lang"],
                    community["subscribers"],
                    community["sum_pending"],
                    community["num_pending"],
                    community["num_authors"],
                ]
            )
        print(t)


class Communities(CommunityObject):
    """Obtain a list of communities

    :param list name_list: list of accounts to fetch
    :param int batch_limit: (optional) maximum number of accounts
        to fetch per call, defaults to 100
    :param Steem/Hive blockchain_instance: Steem() or Hive() instance to use when
        accessing a RPCcreator = Account(creator, blockchain_instance=self)
    """

    def __init__(
        self,
        sort="rank",
        observer=None,
        last=None,
        limit=100,
        lazy=False,
        full=True,
        blockchain_instance=None,
        **kwargs,
    ):
        if blockchain_instance is None:
            if kwargs.get("steem_instance"):
                blockchain_instance = kwargs["steem_instance"]
            elif kwargs.get("hive_instance"):
                blockchain_instance = kwargs["hive_instance"]
        self.blockchain = blockchain_instance or shared_blockchain_instance()

        if not self.blockchain.is_connected():
            return
        communities = []
        community_cnt = 0
        batch_limit = 100
        if batch_limit > limit:
            batch_limit = limit

        while community_cnt < limit:
            self.blockchain.rpc.set_next_node_on_empty_reply(False)
            communities += self.blockchain.rpc.list_communities(
                {"sort": sort, "observer": observer, "last": last, "limit": batch_limit},
                api="bridge",
            )
            community_cnt += batch_limit
            last = communities[-1]["name"]

        super(Communities, self).__init__(
            [
                Community(x, lazy=lazy, full=full, blockchain_instance=self.blockchain)
                for x in communities
            ]
        )

    def search_title(self, title):
        """Returns all communites which have a title similar to title"""
        ret = CommunityObject()
        for community in self:
            if title.lower() in community["title"].lower():
                ret.append(community)
        return ret
