import main
from modules import chankeep
from twisted.internet.threads import deferToThread
from utils.logging.debug import debug, trace
from utils.logging.log import warn
from utils.parsing import parsen


def getWhoSingle(name, query):
    result = main.r.sscan("live.who." + name, 0, query, count=999999)
    if result[1] == []:
        return None
    return (i.decode() for i in result[1])


def getWho(query):
    result = {}
    for i in main.network.keys():
        f = getWhoSingle(i, query)
        if f:
            result[i] = f
    return result


def getChansSingle(name, nick):
    nick = ("live.chan." + name + "." + i for i in nick)
    result = main.r.sinter(*nick)
    if len(result) == 0:
        return None
    return (i.decode() for i in result)


def getChanList(name, nick):
    chanspace = "live.chan." + name + "." + nick
    result = main.r.smembers(chanspace)
    if len(result) == 0:
        return None
    return (i.decode() for i in result)


def getTotalChanNum(net):
    """
    Get the number of channels a network has.
    """
    chans = main.r.keys(f"live.who.{net}.*")
    return len(chans)


def getUserNum(name, channels):
    """
    Get the number of users on a list of channels.
    """
    chanspace = ("live.who." + name + "." + i for i in channels)
    results = {}
    for channel, space in zip(channels, chanspace):
        results[channel] = main.r.scard(space)
    return results


def getChanNum(name, nicks):
    """
    Get the number of channels a list of users are on.
    """
    nickspace = ("live.chan." + name + "." + i for i in nicks)
    results = {}
    for nick, space in zip(nicks, nickspace):
        results[nick] = main.r.scard(space)
    return results


def getChans(nick):
    result = {}
    for i in main.network.keys():
        f = getChansSingle(i, nick)
        if f:
            result[i] = f
    return result


def getUsersSingle(name, nick):
    nick = ("live.who." + name + "." + i for i in nick)
    result = main.r.sinter(*nick)
    if len(result) == 0:
        return None
    return (i.decode() for i in result)


def getUsers(nick):
    result = {}
    for i in main.network.keys():
        f = getUsersSingle(i, nick)
        if f:
            result[i] = f
    return result


def getNumWhoEntries(name):
    return main.r.scard("live.who." + name)


def getNumTotalWhoEntries():
    total = 0
    for i in main.network.keys():
        total += getNumWhoEntries(i)
    return total


def getNamespace(name, channel, nick):
    gnamespace = "live.who.%s" % name
    namespace = "live.who.%s.%s" % (name, channel)
    chanspace = "live.chan.%s.%s" % (name, nick)
    mapspace = "live.map.%s" % name
    return (gnamespace, namespace, chanspace, mapspace)


def _initialUsers(name, channel, users):
    gnamespace = "live.who.%s" % name
    mapspace = "live.map.%s" % name
    p = main.r.pipeline()
    for i in users:
        user = i[0] + "!" + i[1] + "@" + i[2]
        p.hset(mapspace, i[0], user)
        p.sadd(gnamespace, user)
    p.execute()


def initialUsers(name, channel, users):
    trace("Initialising WHO records for %s on %s" % (channel, name))
    deferToThread(_initialUsers, name, channel, users)
    # d.addCallback(testCallback)


def _initialNames(name, channel, names):
    namespace = "live.who.%s.%s" % (name, channel)
    p = main.r.pipeline()
    for mode, nick in names:
        p.sadd(namespace, nick)
        p.sadd("live.chan." + name + "." + nick, channel)
        if mode:
            p.hset("live.prefix." + name + "." + channel, nick, mode)
    p.execute()


def initialNames(name, channel, names):
    trace("Initialising NAMES records for %s on %s" % (channel, name))
    deferToThread(_initialNames, name, channel, names)
    # d.addCallback(testCallback)


def editUser(name, user):
    gnamespace = "live.who.%s" % name
    mapspace = "live.map.%s" % name
    parsed = parsen(user)
    p = main.r.pipeline()
    p.sadd(gnamespace, user)
    p.hset(mapspace, parsed[0], user)  # add nick -> user mapping
    p.execute()


def addUser(name, channel, nick, user):
    gnamespace, namespace, chanspace, mapspace = getNamespace(name, channel, nick)
    p = main.r.pipeline()
    p.sadd(gnamespace, user)
    p.sadd(namespace, nick)
    p.sadd(chanspace, channel)
    p.hset(mapspace, nick, user)
    p.execute()


def delUser(name, channel, nick, user):
    gnamespace, namespace, chanspace, mapspace = getNamespace(name, channel, nick)
    p = main.r.pipeline()
    channels = main.r.smembers(chanspace)
    p.srem(namespace, nick)
    if channels == {channel.encode()}:  # can we only see them on this channel?
        p.delete(chanspace)  # remove channel tracking entry
        p.hdel(
            "live.prefix." + name + "." + channel, nick
        )  # remove prefix tracking entry
        p.hdel(mapspace, nick)  # remove nick mapping entry
        if user:
            p.srem(gnamespace, user)  # remove global userinfo entry
        else:
            warn("Attempt to delete nonexistent user: %s" % user)
    else:
        p.srem(chanspace, channel)  # keep up - remove the channel from their list
    p.execute()


def escape(text):
    chars = ["[", "]", "^", "-", "*", "?"]
    text = text.replace("\\", "\\\\")
    for i in chars:
        text = text.replace(i, "\\" + i)
    return text


def getUserByNick(name, nick):
    gnamespace = "live.who.%s" % name  # "nick": "nick!ident@host"
    mapspace = "live.map.%s" % name
    if main.r.hexists(mapspace, nick):
        return main.r.hget(mapspace, nick)
    else:
        warn(
            "Entry doesn't exist: %s on %s - attempting auxiliary lookup"
            % (nick, mapspace)
        )
        # return False
    # legacy code below - remove when map is reliable
    usermatch = main.r.sscan(gnamespace, match=escape(nick) + "!*", count=999999999)
    if usermatch[1] == []:
        warn("No matches found for user query: %s on %s" % (nick, name))
        return False
    else:
        if len(usermatch[1]) == 1:
            user = usermatch[1][0]
            return user
        else:
            warn("Auxiliary lookup failed: %s on %s" % (nick, gnamespace))
            return False


def renameUser(name, oldnick, olduser, newnick, newuser):
    gnamespace = "live.who.%s" % name
    chanspace = "live.chan.%s.%s" % (name, oldnick)
    mapspace = "live.map.%s" % name
    newchanspace = "live.chan.%s.%s" % (name, newnick)
    p = main.r.pipeline()
    p.srem(gnamespace, olduser)
    p.sadd(gnamespace, newuser)
    for i in main.r.smembers(chanspace):
        i = i.decode()
        p.srem("live.who." + name + "." + i, oldnick)
        p.sadd("live.who." + name + "." + i, newnick)
        p.hdel(mapspace, oldnick)
        p.hset(mapspace, newnick, newuser)
        if main.r.exists(
            "live.prefix." + name + "." + i
        ):  # if there's a prefix entry for the channel
            if main.r.hexists(
                "live.prefix." + name + "." + i, oldnick
            ):  # if the old nick is in it
                mode = main.r.hget(
                    "live.prefix." + name + "." + i, oldnick
                )  # retrieve old modes
                p.hset(
                    "live.prefix." + name + "." + i, newnick, mode
                )  # set old modes to new nickname
    if main.r.exists(chanspace):
        p.rename(chanspace, newchanspace)
    else:
        warn("Key doesn't exist: %s" % chanspace)
    p.execute()


def delUserByNick(name, channel, nick):  # kick
    user = getUserByNick(name, nick)
    if not user:
        return
    delUser(name, channel, nick, user)


def delUserByNetwork(name, nick, user):  # quit
    gnamespace = "live.who.%s" % name
    chanspace = "live.chan.%s.%s" % (name, nick)
    mapspace = "live.chan.%s" % name
    p = main.r.pipeline()
    p.srem(gnamespace, user)
    for i in main.r.smembers(chanspace):
        p.srem("live.who." + name + "." + i.decode(), nick)
        p.hdel("live.prefix." + name + "." + i.decode(), nick)

    p.delete(chanspace)
    p.hdel(mapspace, nick)
    p.execute()


def _delChannels(net, channels):
    gnamespace = "live.who.%s" % net
    mapspace = "live.map.%s" % net
    p = main.r.pipeline()
    for channel in channels:
        namespace = "live.who.%s.%s" % (net, channel)
        for i in main.r.smembers(namespace):
            nick = i.decode()
            # user = getUserByNick(net, nick) -- far too many function calls
            user = main.r.hget(mapspace, nick)
            if not user:
                warn("User lookup failed: %s on %s" % (nick, net))
            if main.r.smembers("live.chan." + net + "." + nick) == {channel.encode()}:
                if user:
                    p.srem(gnamespace, user)

                p.delete("live.chan." + net + "." + nick)
                p.hdel(mapspace, nick)  # remove map entry
            else:
                p.srem("live.chan." + net + "." + nick, channel)
        p.delete(namespace)
        p.delete("live.prefix." + net + "." + channel)
    p.execute()


def delChannels(net, channels):  # we have left a channel
    trace("Purging channel %s for %s" % (", ".join(channels), net))
    dupes = chankeep.getDuplicateChannels(net, total=True)
    print("dupes: %s" % dupes)
    if not dupes:
        deferToThread(_delChannels, net, channels)
    else:
        for channel in channels:
            if channel in dupes[net]:
                if dupes[net][channel] != 0:
                    channels.remove(channel)
                    debug(
                        f"Not removing channel {channel} as {net} has {dupes[net][channel]} other relays covering it"
                    )
        deferToThread(_delChannels, net, channels)
    # d.addCallback(testCallback)
