# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function, division
import dataset
import logging
import re
from datetime import datetime
from dateutil.parser import parse as parse_dt
from copy import deepcopy
import os


log = logging.getLogger("usertrack")

if not os.path.exists("databases"):
    os.makedirs("databases")
db = dataset.connect("sqlite:///databases/usertrack.db")


def get_table(bot, channel):
    """Returns table-instance from database.
    Database names are in format "networkalias_channel".
    Network alias is the name assigned to network in config.
    Channel is stripped from the &#!+ prepending the channel name."""

    return db["%s_%s" % (bot.network.alias, re.sub(r"&|#|!|\+", "", channel))]


def upsert_row(bot, channel, data, keys=["nick", "ident", "host"]):
    """Updates row to database.
    Default keys are nick, ident and host,
    which are normally present for data received by get_base_data -function."""

    table = get_table(bot, channel)
    table.upsert(data, keys)


def get_base_data(user):
    """Fetches "base" data according to user.
    Normally this is nick, ident, host and current time to be set to action_time in database.
    Ident and host might be missing when the full mask isn't provided,
    for example in handle_userKicked, where kickee doesn't get anything but name."""

    data = {"nick": getNick(user), "action_time": datetime.now()}

    # For example kickee doesn't get full hostmask -> needs some work...
    try:
        data["ident"] = getIdent(user)
    except IndexError:
        pass
    try:
        data["host"] = getHost(user)
    except IndexError:
        pass

    return data


def handle_privmsg(bot, user, channel, message):
    """Handles all messages bot sees.
    If message is private message to bot, doesn't update.
    Otherwise updates the DB.
    - last_action = 'message'
    - last_message = message
    - message_time = current time"""

    # if user == channel -> this is a query -> don't update
    if user == channel:
        return

    data = get_base_data(user)
    data["last_action"] = "message"
    data["last_message"] = message
    data["message_time"] = datetime.now()

    upsert_row(bot, channel, data)


def handle_userJoined(bot, user, channel):
    """Handles user joining the channel and auto-ops if op == True in database.
    - last_action = 'join'"""

    data = get_base_data(user)
    data["last_action"] = "join"

    upsert_row(bot, channel, data)

    table = get_table(bot, channel)
    if table.find_one(
        nick=getNick(user), ident=getIdent(user), host=getHost(user), op=True
    ):
        log.info("auto-opping %s" % user)
        bot.mode(channel, True, "o", user=getNick(user))


def handle_userLeft(bot, user, channel, message):
    """Handles user leaving the channel (or quitting).
    For leaving, only updates the channel left.
    For quitting, updates all channels in network, which the user was on (as bot knows...)
    - last_action = 'left'"""

    data = get_base_data(user)
    data["last_message"] = message
    if channel is not None:
        data["last_action"] = "left"
        upsert_row(bot, channel, data)
    # QUIT returns the channel as None, loop through all tables, check if user exists and update it
    else:
        data["last_action"] = "quit"
        for t in db.tables:
            if not t.startswith("%s_" % bot.network.alias):
                continue
            table = db.load_table(t)
            res = table.find_one(
                nick=getNick(user), ident=getIdent(user), host=getHost(user)
            )
            if res:
                data["id"] = res["id"]
                table.update(data, ["id"])


def handle_userKicked(bot, kickee, channel, kicker, message):
    """Handles user being kicked.
    As 'kickee' doesn't get full mask, it's only determined by nick.
    For kickee:
        - last_action = kicked by kicker [message]
    For kicker:
        - last_action = kicked kickee [message]"""

    data = get_base_data(kickee)
    data["last_action"] = "kicked by %s [%s]" % (getNick(kicker), message)
    # We don't get full info from kickee, need to update by nick only
    upsert_row(bot, channel, data, ["nick"])

    # Update the kickers action also...
    data = get_base_data(kicker)
    data["last_action"] = "kicked %s [%s]" % (getNick(kickee), message)
    upsert_row(bot, channel, data)


def handle_userRenamed(bot, user, newnick):
    """Handles nick change.
    Updates both data, related to old and new nick, doesn't remove anything from db.
    - last_action = nick change from oldnick to newnick"""

    nick = getNick(user)
    ident = getIdent(user)
    host = getHost(user)

    data = get_base_data(user)
    data["last_action"] = "nick change from %s to %s" % (nick, newnick)

    # loop through all the tables, if user exists, update nick to match
    for t in db.tables:
        if not t.startswith("%s_" % bot.network.alias):
            continue

        table = db.load_table(t)

        # if row is found with new or old nick -> user is on the channel -> update
        if table.find_one(nick=nick, ident=ident, host=host) or table.find_one(
            nick=newnick, ident=ident, host=host
        ):
            # need to create a deep copy of data, as dataset seems to put changed fields back to data...
            # haven't found any documentation on this, so might be a bug?
            tmp_data = deepcopy(data)

            # update the old user
            table.upsert(tmp_data, ["nick", "ident", "host"])

            # update new user
            tmp_data = deepcopy(data)
            tmp_data["nick"] = newnick
            table.upsert(tmp_data, ["nick", "ident", "host"])


def handle_action(bot, user, channel, message):
    """Handles action (/me etc). Ignores stuff directed to bot (/describe botnick etc).
    - last_action = action
    - last_message = message
    - message_time = current time"""

    # if action is directed to bot instead of channel -> don't log
    if channel == bot.nickname:
        return

    data = get_base_data(user)
    data["last_action"] = "action"
    data["last_message"] = message
    data["message_time"] = datetime.now()

    upsert_row(bot, channel, data)


def command_add_op(bot, user, channel, args):
    """Adds op-status according to nickname or full hostmask. Only for admins.
    If user is found from database, set op = True and return info with full hostmask.
    Else returns user not found."""

    if not isAdmin(user) or user == channel or not args:
        return

    table = get_table(bot, channel)

    # Get basedata for user to be opped
    u = get_base_data(args)

    # If we got full mask, use it..
    if "nick" in u and "ident" in u and "host" in u:
        res = table.find_one(nick=u["nick"], ident=u["ident"], host=u["host"])
    # else use just nickname
    else:
        res = table.find_one(nick=u["nick"])

    if not res:
        return bot.say(channel, "user not found")

    data = {"id": res["id"], "op": True}
    table.upsert(data, ["id"])
    return bot.say(
        channel, "auto-opping %s!%s@%s" % (res["nick"], res["ident"], res["host"])
    )


def command_remove_op(bot, user, channel, args):
    """Removes op-status from nick. Logic same as command_add_op. Only for admins."""

    if not isAdmin(user) or user == channel or not args:
        return

    table = get_table(bot, channel)

    # Get basedata for user to be opped
    u = get_base_data(args)

    # If we got full mask, use it..
    if "nick" in u and "ident" in u and "host" in u:
        res = table.find_one(nick=u["nick"], ident=u["ident"], host=u["host"])
    # else use just nickname
    else:
        res = table.find_one(nick=u["nick"])

    if not res:
        return bot.say(channel, "user not found")

    data = {"id": res["id"], "op": False}
    table.upsert(data, ["id"])
    return bot.say(
        channel,
        "removed auto-op from %s!%s@%s" % (res["nick"], res["ident"], res["host"]),
    )


def command_op(bot, user, channel, args):
    """Ops user if op = True for user or isAdmin."""

    table = get_table(bot, channel)
    if table.find_one(
        nick=getNick(user), ident=getIdent(user), host=getHost(user), op=True
    ) or isAdmin(user):
        log.info("opping %s on %s by request" % (user, channel))
        bot.mode(channel, True, "o", user=getNick(user))


def command_list_ops(bot, user, channel, args):
    """Lists ops in current channel. Only for admins.
    By default lists nicks, if args == 'full', lists full hostmask."""

    if not isAdmin(user) or user == channel:
        return

    table = get_table(bot, channel)
    if args.strip() == "full":
        ops = ", ".join(
            [
                "%s!%s@%s" % (r["nick"], r["ident"], r["host"])
                for r in table.find(op=True)
            ]
        )
    else:
        ops = ", ".join(["%s" % r["nick"] for r in table.find(op=True)])
    return bot.say(channel, "ops: %s" % ops)


def __get_length_str(secs):
    days, hours, minutes, seconds = (
        secs // 86400,
        secs // 3600,
        secs // 60 % 60,
        secs % 60,
    )

    if days > 0:
        return "%dd" % days
    if hours > 0:
        return "%dh" % hours
    if minutes > 0:
        return "%dm" % minutes
    if seconds > 0:
        return "%ds" % seconds
    return "0s"


def command_seen(bot, user, channel, args):
    """Displays the last action by the given user"""
    if not args:
        return bot.say(channel, "Please provide a nick to search...")

    table = get_table(bot, channel)

    # Return the first match, there shouldn't be multiples anyway
    user = table.find_one(nick=args)
    if not user:
        return bot.say(channel, "I haven't seen %s on %s" % (args, channel))

    if not isinstance(user["action_time"], datetime):
        user["action_time"] = parse_dt(user["action_time"])

    # Calculate last seen in seconds
    last_seen = datetime.now() - user["action_time"]
    # Get string for last seen
    last_seen = __get_length_str(last_seen.days * 86400 + last_seen.seconds)

    # If the last action was part or quit, show also the message
    if user["last_action"] in ["left", "quit"]:
        return bot.say(
            channel,
            "%s was last seen at %s (%s ago) [%s, %s]"
            % (
                user["nick"],
                "{0:%Y-%m-%d %H:%M:%S}".format(user["action_time"]),
                last_seen,
                user["last_action"],
                user["last_message"],
            ),
        )

    # Otherwise just show the time and action
    return bot.say(
        channel,
        "%s was last seen at %s (%s ago) [%s]"
        % (
            user["nick"],
            "{0:%Y-%m-%d %H:%M:%S}".format(user["action_time"]),
            last_seen,
            user["last_action"],
        ),
    )
