import datetime
import json
import os
import random
import re

from itertools import chain

from pylons import app_globals as g

from r2.lib.amqp import worker
from r2.lib.db import queries
from r2.lib.utils import in_chunks
from r2.models import Thing, Account, Subreddit, Link, Comment
from r2.models.admintools import send_system_message
from r2.models.gold import (
    gold_goal_on,
    gold_revenue_multi,
    GoldRevenueGoalByDate,
    TIMEZONE,
)
from r2.models.comment_tree import CommentTree
from r2.models.wiki import WikiPage
from r2.lib.db import tdb_cassandra

from reddit_gold.server_naming import gold_buyers_on


SERVERNAME_SR = Subreddit._by_name(g.gold_servername_sr)
SYSTEM_ACCOUNT = Account._by_name(g.system_user)


def get_recent_name_submissions():
    link_fullnames = list(queries.get_links(SERVERNAME_SR, "new", "all"))
    links = chain.from_iterable(Thing._by_fullname(chunk, return_dict=False)
                                for chunk in in_chunks(link_fullnames))

    for link in links:
        if link._deleted or link._spam:
            continue

        # OH GOD WHAT HAVE YOU POSTED IN MY LOVELY AUTOMATED SUBREDDIT!?
        if (not hasattr(link, "revenue_date") or
            not hasattr(link, "revenue_bucket") or
            not hasattr(link, "server_names")):
            continue

        yield link


def post_if_goal_reached(date):
    # bail out if this day's already been submitted
    for link in get_recent_name_submissions():
        if link.revenue_date == date:
            return

    revenue = gold_revenue_multi([date]).get(date, 0)
    goal = gold_goal_on(date)
    percent = revenue / float(goal)
    bucket = int(percent)
    if bucket == 0:
        return

    buyer_count = len(gold_buyers_on(date))
    template_wp = WikiPage.get(SERVERNAME_SR, "templates/selftext")
    template = random.choice(template_wp._get("content").split("\r\n---\r\n"))
    boilerplate = WikiPage.get(SERVERNAME_SR, "templates/boilerplate")._get("content")
    selftext_template = template + "\n\n---\n\n" + boilerplate

    link = Link._submit(
        is_self=True,
        title=date.strftime("%a %Y-%m-%d"),
        content=selftext_template % {
            "percent": int(percent * 100),
            "buyers": buyer_count,
        },
        author=SYSTEM_ACCOUNT,
        sr=SERVERNAME_SR,
        ip="127.0.0.1",
        sendreplies=False,
    )

    link.flair_text = "Name pending..."
    link.flair_css_class = "goal-bucket-%d-active" % bucket
    link.revenue_date = date
    link.revenue_bucket = bucket
    link.server_names = []
    link._commit()

    queries.new_link(link)
    link.update_search_index()

    template = WikiPage.get(SERVERNAME_SR, "templates/notification-message")._get("content")
    subject_template, sep, body_template = template.partition("\r\n")
    for id in gold_buyers_on(date):
        recipient = Account._byID(id, data=True)
        send_system_message(
            recipient,
            subject_template,
            body_template % {
                "percent": int(percent * 100),
                "buyers": buyer_count,
                "user": recipient.name,
                "link": link.url,
            },
        )


def activate_requested_names(but_not):
    date_to_exclude = but_not

    for link in get_recent_name_submissions():
        if link.server_names or link.revenue_date == date_to_exclude:
            continue

        activate_names_requested_in(link)


valid_name_re = re.compile(r"(?:^|\*)([A-Za-z0-9-]{1,25})(?:$|\*)")
def activate_names_requested_in(link):
    comment_tree = CommentTree.by_link(link)
    acceptable_names = []
    if comment_tree.tree:
        top_level_cids = comment_tree.tree[None]
        comments = chain.from_iterable(Comment._byID(chunk, return_dict=False,
                                                     data=True)
                                       for chunk in in_chunks(top_level_cids))

        for comment in sorted(comments, key=lambda c: c._ups, reverse=True):
            if comment._spam or comment._deleted:
                continue

            sanitized = comment.body.strip()
            match = valid_name_re.search(sanitized)
            if match:
                acceptable_names.append((comment, match.group(1)))

    # we activate one name for each 100% of rev goal met
    names = acceptable_names[:link.revenue_bucket]
    activate_names(link, names)

    activated_names = [name for comment, name in names]
    link.server_names = activated_names
    link.flair_text = ", ".join(activated_names) if names else "/dev/null"
    link.flair_css_class = "goal-bucket-%d" % link.revenue_bucket
    link._commit()


def activate_names(link, names):
    for comment, name in names:
        # find a slot to assign a name to. we'll prefer nodes that are
        # currently empty, and failing that find the least-recently-modified
        # node.
        ROOT = "/gold/server-names"
        slot_names = g.zookeeper.get_children(ROOT)
        slots = [(slot_name, g.zookeeper.get(os.path.join(ROOT, slot_name)))
                 for slot_name in slot_names]
        slots.sort(key=lambda (path, (data, stat)): (bool(data), stat.mtime))
        slot_path = os.path.join(ROOT, slots[0][0])

        comment_data = {'name': str(name),
                        'permalink': comment.make_permalink_slow()}
        g.zookeeper.set(slot_path, json.dumps(comment_data))

        lock = g.zookeeper.Lock(slot_path)
        lock_contenders = lock.contenders()
        old_name = lock_contenders[0] if lock_contenders else ""
        old_name = old_name or "one of our servers"

        # reply to the user
        wp = WikiPage.get(SERVERNAME_SR, "templates/success-reply")
        template = random.choice(wp._get("content").split("\r\n---\r\n"))
        comment, inbox_rel = Comment._new(
            author=SYSTEM_ACCOUNT,
            link=link,
            parent=comment,
            body=template % {
                "old-name": old_name,
                "new-name": name,
            },
            ip="127.0.0.1",
        )
        queries.new_comment(comment, inbox_rel)

        # update the link's text
        wp = WikiPage.get(SERVERNAME_SR, "templates/goldisms")
        goldism = random.choice(wp._get("content").split("\r\n---\r\n"))
        wp = WikiPage.get(SERVERNAME_SR, "templates/selftext-success")
        template = wp._get("content")
        link.selftext = template % {
            "old-name": old_name,
            "new-name": name,
            "goldism": goldism,
        }
        link._commit()


def update_sidebar():
    MAX_HEADING = 6
    LOG_ENTRIES = 30

    links = sorted(
        get_recent_name_submissions(),
        key=lambda L: L.revenue_date,
        reverse=True,
    )[:LOG_ENTRIES]

    lines = []
    for link in links:
        date_text = link.revenue_date.strftime("%b-%d")

        # active threads won't have any server names yet
        if not link.server_names:
            server_names = "<<in progress>>"
        else:
            server_names = ", ".join(link.server_names)

        lines.append("%s [%s: **%s**](%s)\n\n" % (
            "#" * max(MAX_HEADING - link.revenue_bucket + 1, 1)
                if link.revenue_bucket > 0 else ">",
            date_text,
            server_names,
            link.make_permalink(SERVERNAME_SR),
        ))

    # preserve human-edited content before the first <hr>
    human_content, hr, junk = SERVERNAME_SR.description.partition("---")
    SERVERNAME_SR.description = human_content + "---\n\n" + "".join(lines)
    SERVERNAME_SR._commit()


def determine_gold_goal(date):
    # fetch the revenues from the previous 7 days
    date_list = [date - datetime.timedelta(days=i)
                 for i in range(1, 8)]
    previous_revenues = gold_revenue_multi(date_list).values()

    # throw out highest value and set goal to 105% of average
    previous_revenues = sorted(previous_revenues)[:-1]
    average_revenue = sum(previous_revenues) / float(len(previous_revenues))
    goal = average_revenue * 1.05

    # don't let this be more than 20% different from the previous goal
    previous_goal = gold_goal_on(date - datetime.timedelta(days=1))
    goal = min(previous_goal * 1.2, goal)
    goal = max(previous_goal * 0.8, goal)

    goal = round(goal, 0)
    GoldRevenueGoalByDate.set(date, goal)
    # force update to memoized value
    gold_goal_on(date, _update=True)


def main():
    now = datetime.datetime.now(TIMEZONE)

    # calculate and store the new day's gold goal
    determine_gold_goal(now.date())

    # post a new thread if we met our revenue goal
    yesterday = (now - datetime.timedelta(days=1)).date()
    post_if_goal_reached(yesterday)

    # look at old (now complete) threads if any
    activate_requested_names(but_not=yesterday)

    # wait until all our amqp / permacache changes are flushed from the
    # in-process queue.
    worker.join()
    g.reset_caches()

    # update the sidebar with a list of names
    update_sidebar()


if g.running_as_script:
    main()
