import argparse
from random import choice, sample
from string import ascii_lowercase as lowercase, digits

import re
import time
import json
import requests
import time
from collections import defaultdict

import zmq

DIBS_FILE = 'dibs'
DISCOURSE_ROOT_URL = 'https://forum.groklearning.com'

URL_RE = re.compile(r'^https://forum.groklearning.com/t/[^/]*/([0-9]*).*$')

parser = argparse.ArgumentParser()
parser.add_argument('-i', '--incoming-endpoint', required=True)
parser.add_argument('-o', '--outgoing-endpoint', required=True)
parser.add_argument('-c', '--cookies', required=True)
parser.add_argument('-x', '--x-csrf-token', required=True)

args = parser.parse_args()

ctx = zmq.Context()

incoming = ctx.socket(zmq.SUB)
incoming.setsockopt(zmq.SUBSCRIBE, '')
incoming.connect(args.incoming_endpoint)

outgoing = ctx.socket(zmq.PUSH)
outgoing.connect(args.outgoing_endpoint)

def sanitise(msg, allowed=lowercase + digits + '<'):
    msg = msg.replace("'", '')
    msg = ''.join(
        c if c in allowed else ' '
        for c in msg.lower()
    )
    while '  ' in msg:
        msg = msg.replace('  ', ' ')
    return msg.strip()

def is_valid_url(url):
    if url.isdigit():
        return True
    return re.match(URL_RE, url) is not None

def url_to_id(url):
    if url.isdigit():
        return url
    return re.match(URL_RE, url).group(1)

current_dibs = {}
not_dibsed = {}
#past_dibs = {}
fbb_last_thread = ''

def save_dibs():
    global current_dibs
    #global past_dibs

    with open(DIBS_FILE, 'w') as f:
        data = {
            'current_dibs': current_dibs,
            'not_dibsed': not_dibsed,
            #'past_dibs': past_dibs,
            'fbb_last_thread': fbb_last_thread
        }
        f.write(json.dumps(data))

def load_dibs():
    global current_dibs
    #global past_dibs

    try:
        with open(DIBS_FILE, 'r') as f:
            data = json.loads(f.read())
            current_dibs = data['current_dibs']
            not_dibsed = data['not_dibsed']
            #past_dibs = data['past_dibs']
            fbb_last_thread = data['fbb_last_thread']
    except IOError:
        save_dibs()

def format_dibs(thread_id, details, timestamp=False):
    s = '%s, %s (%s)' % (details['title'], details['student'], thread_id)
    if 'permanent' in details and details['permanent']:
        s += ' [permadibsed]'
    if timestamp:
        s += ' '
        diff = time.time() - details['timestamp']
        if diff < 60:
            d = int(diff)
            s += '%d second%s ago' % (d, '' if d == 1 else 's')
        elif diff < 60*60:
            d = int(diff/60.)
            s += '%d minute%s ago' % (d, '' if d == 1 else 's')
        elif diff < 60*60*24:
            d = int(diff/60./60.)
            s += '%d hour%s ago' % (d, '' if d == 1 else 's')
        else:
            d = int(diff/60./60./24.)
            s += '%d day%s ago' % (d, '' if d == 1 else 's')
    return s

def dibs(cmd, nick, words, permanent=False):
    response = None
    if len(words) == 2:
        url = words[1]
        if is_valid_url(url):
            thread_id = url_to_id(url)
            if thread_id not in current_dibs or (current_dibs[thread_id]['owner'] == nick and not current_dibs[thread_id]['permanent'] and permanent):
                details = fetch_thread_details(thread_id)
                if details is not None:
                    details['owner'] = nick
                    details['permanent'] = permanent
                    details['timestamp'] = int(time.time())
                    current_dibs[thread_id] = details
                    if thread_id in not_dibsed:
                        del not_dibsed[thread_id]
                    save_dibs()
                    response = '%s has %sed %s' % (nick, cmd, format_dibs(thread_id, details))
                else:
                    response = "%s: that thread doesn't seem to exist." % nick
            else:
                obj = current_dibs[thread_id]
                if obj['owner'] == nick:
                    response = '%s: You\'ve already %sed them, silly!' % (nick, cmd)
                else:
                    response = '%s: %s has already %sed them.' % (nick, obj['owner'], cmd.replace('perma', ''))
        elif url == 'list':
            if len(current_dibs):
                for thread_id, obj in sorted(current_dibs.items(), key=lambda t: t[1]['timestamp'], reverse=True):
                    outgoing.send_json({
                        'kind': data['kind'],
                        'target': data.get('channel', '##ncss_tutors'),
                        'message': '%s %sed %s' % (obj['owner'], cmd, format_dibs(thread_id, obj, timestamp=True))
                    })
                    time.sleep(0.1)
            else:
                response = 'No %ses yet!' % cmd
        elif url == 'that':
            thread_id = fbb_last_thread
            if thread_id not in current_dibs or (current_dibs[thread_id]['owner'] == nick and not current_dibs[thread_id]['permanent'] and permanent):
                details = fetch_thread_details(thread_id)
                if details is not None:
                    details['owner'] = nick
                    details['permanent'] = permanent
                    details['timestamp'] = int(time.time())
                    current_dibs[thread_id] = details
                    if thread_id in not_dibsed:
                        del not_dibsed[thread_id]
                    save_dibs()
                    response = '%s has %sed %s' % (nick, cmd, format_dibs(thread_id, details))
                else:
                    response = "%s: that thread doesn't seem to exist. flowbot, wat r u doin" % nick
            else:
                obj = current_dibs[thread_id]
                if obj['owner'] == nick:
                    response = "%s: You've already %sed them, silly!" % (nick, cmd)
                else:
                    response = '%s: %s has already %sed them.' % (nick, obj['owner'], cmd.replace('perma', ''))
        else:
            response = "%s: That URL doesn't seem valid." % nick
    return response

def undibsed(cmd, nick, words):
    response = None
    if len(not_dibsed):
        for thread_id, obj in sorted(not_dibsed.items(), key=lambda t: t[1]['timestamp'], reverse=True):
            outgoing.send_json({
                'kind': data['kind'],
                'target': data.get('channel', '##ncss_tutors'),
                'message': format_dibs(thread_id, obj, timestamp=True)
            })
    else:
        response = 'No pending messages.'
    return response

def undibs(cmd, nick, words):
    response = None
    if len(words) == 2:
        url = words[1]
        if is_valid_url(url):
            thread_id = url_to_id(url)
            if thread_id in current_dibs:
                obj = current_dibs[thread_id]
                if obj['owner'] == nick:
                    #past_dibs[thread_id] = obj
                    del current_dibs[thread_id]
                    save_dibs()
                    response = "%s: You've %sed %s" % (nick, cmd, format_dibs(thread_id, obj))
                else:
                    response = "%s: You can't %s that! It belongs to %s" % (nick, cmd, obj['owner'])
            else:
                response = "%s: You can't %s that, no-one's %sed it yet!" % (nick, cmd, cmd[2:])
    return response

def check(cmd, nick, words):
    response = None
    if len(words) == 2:
        url = words[1]
        if is_valid_url(url):
            thread_id = url_to_id(url)
            if thread_id in current_dibs:
                response = '%s: %s has already been dibsed by %s.' % (nick, thread_id, current_dibs[thread_id]['owner'])
            else:
                response = '%s: %s has not yet been dibsed.' % (nick, thread_id)
    return response

def fetch_thread_details(thread_id):
    url = 'https://forum.groklearning.com/t/%s.json' % thread_id

    try:
        response = requests.get(
            url,
            cookies=dict(
                thing.split('=', 1)
                for thing in args.cookies.split()
            ),
            headers={
                'X-CSRF-Token': args.x_csrf_token,
                'X-Requested-With': 'XMLHttpRequest',
            },
        )
        response.raise_for_status()

        json = response.json()
        
        details = {
            'title': json['title'],
            'student': json['details']['created_by']['username'],
            'category': json['category_id']
        }

        return details
    except (requests.exceptions.HTTPError, ValueError):
        return None

load_dibs()

while True:
    data = incoming.recv_json()
    response = None

    if data['kind'] == 'chanmsg':
        nick = data['from']['nick']
        message = data['message_to_me']
        if message is None:
            message = data['message']

        if message is not None:
            words = message.split(' ')
            if len(words) == 0:
                continue

            cmd = sanitise(words[0], allowed=lowercase)
            response = None

            if nick.lower() == 'flowbot':
                new = '*new*' in words
                if '*new*' in words:
                    words.remove('*new*')
                if len(words) == 4 and words[0] == 'undibs' and words[2] == 'for':
                    url = words[1]
                    if is_valid_url(url):
                        thread_id = url_to_id(url)
                        for_nick = words[3]

                        if thread_id in current_dibs:
                            obj = current_dibs[thread_id]
                            if obj['owner'] == for_nick:
                                if 'permanent' in obj and obj['permanent']:
                                    continue
                                else:
                                    response = '%s: ok' % nick
                            else:
                                response = "%s: Looks like you got ninja'd by %s :(" % (obj['owner'], for_nick)
                            #past_dibs[thread_id] = obj
                            del current_dibs[thread_id]
                            save_dibs()
                        else:
                            # special case for phoenix_
                            if for_nick == 'phoenix_':
                                response = "%s: Don't be silly, %s wouldn't have answered an undibsed thread!" % (nick, for_nick)
                            else:
                                response = "%s: You didn't dibs that! (You might want to permadibs it.)" % for_nick
                            if thread_id in not_dibsed:
                                del not_dibsed[thread_id]
                                save_dibs()
                elif len(words) > 3 and words[1] == 'posted' and words[2] == 'in' and is_valid_url(words[-1]):
                    url = words[-1]
                    thread_id = url_to_id(url)
                    fbb_last_thread = thread_id
                    if new and thread_id not in not_dibsed:
                        obj = fetch_thread_details(thread_id)
                        obj['timestamp'] = int(time.time())
                        not_dibsed[thread_id] = obj
                    save_dibs()

            elif cmd == 'dibs' or cmd == 'bags':
                response = dibs(cmd, nick, words)

            elif cmd == 'undibsed':
                response = undibsed(cmd, nick, words)

            elif cmd == 'permadibs' or cmd == 'permabags':
                response = dibs(cmd, nick, words, permanent=True)

            elif cmd == 'undibs' or cmd == 'unbags':
                response = undibs(cmd, nick, words)

            elif cmd == 'check':
                response = check(cmd, nick, words)

    if response is not None:
        outgoing.send_json({
            'kind': data['kind'],
            'target': data.get('channel', '##ncss_tutors'),
            'message': response
        })
