#!/usr/bin/env python3
import csv
from datetime import datetime
import os
import sys

import click
from mongoengine.queryset.visitor import Q
from mongoengine.errors import DoesNotExist
import requests

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from rsvp.models import Event, User
from rsvp.utils import markdown_to_html


@click.group()
def cli():
    """A CLI to manage users"""


@click.command()
@click.option("--email", required=True)
@click.option("--name", required=True)
@click.option("--gender", required=True, type=click.Choice(['male', 'female']))
def create_approved_user(email, name, gender):
    try:
        user = User.objects.get(email=email)
    except User.DoesNotExist:
        user = User(email=email, name=name, gender=gender)
        user.save()

    user.update(push__roles='.approved-user')


@click.command()
def approve_all_users():
    """Command to approve all unapproved users on the app

    The command is useful when running a demo app, and this command could be
    run as a cron job.

    """
    unapproved_users = User.objects.filter(roles__nin=[".approved-user"])
    count = unapproved_users.count()
    unapproved_users.update(push__roles=".approved-user")
    print(f"Approved {count} users:\n  ")
    print("\n  ".join(unapproved_users.values_list("email")))


@click.command()
@click.option("--format", default="tsv")
@click.option("--display-roles", default=False, is_flag=True)
@click.option("--display-gender", default=False, is_flag=True)
@click.option("--include-roles", multiple=True)
def list_users(
    format="tsv", display_roles=False, include_roles=None, display_gender=False
):
    seperator = ", " if format == "csv" else "\t"
    users = (
        User.objects(roles__in=include_roles)
        if include_roles
        else User.objects.all()
    )
    output_format = (
        '{name}{sep}{email}{sep}"{roles}"'
        if display_roles
        else "{name}{sep}{email}"
    )
    if display_gender:
        output_format = "{{gender}}{{sep}}{}".format(output_format)
    for user in users.order_by("email"):
        click.echo(
            output_format.format(
                name=user.name,
                email=user.email,
                gender=user.gender,
                roles=", ".join(user.roles),
                sep=seperator,
            )
        )


@click.command()
def list_roles():
    roles = {
        role
        for user in User.objects.only("roles")
        for role in (user.roles or [])
    }
    for role in sorted(roles):
        users = User.objects(roles__in=[role]).only("email")
        users_count = users.count()
        if users_count <= 3:
            display_users = ", ".join(users.values_list("email"))
        else:
            display_users = "{} users".format(users_count)
        click.echo("{}\t{}".format(role, display_users))


@click.command()
@click.argument("role", type=str)
@click.option("--all", default=False, is_flag=True)
@click.option("--interactive", default=False, is_flag=True)
@click.argument("users", type=str, nargs=-1)
def add_role(role, users, all, interactive=False):
    """Add a role to the specified users."""
    if all:
        click.echo("Adding role {} to all users".format(role))
        User.objects(roles__nin=[role]).update(push__roles=role)
    elif interactive or not users:
        role_count = User.objects(roles__in=[role]).count()
        if role_count > 0:
            click.echo(
                "{} users already have the role: {}".format(role_count, role)
            )
        users = User.objects(roles__nin=[role]).order_by("email")
        message = "Select users to add the role to"
        page_size = 10
        num_pages = int(users.count() / page_size) + 1

        def split_values(value):
            return (
                []
                if value == "-"
                else map(int, value.replace(",", "").strip().split())
            )

        for page in range(1, num_pages + 1):
            user_list = "\n".join(
                "{}. {}".format(i, user.name)
                for i, user in enumerate(
                    users[(page - 1) * page_size : page * page_size]
                )
            )
            click.echo("Displaying page {} of {}".format(page, num_pages))
            click.echo(
                "{message}:\n\n{user_list}\n\n".format(
                    message=message, user_list=user_list
                )
            )
            values = click.prompt(
                "{} [- to skip page]".format(message), value_proc=split_values
            )
            page_users = [
                users[(page - 1) * page_size + value] for value in values
            ]
            emails = [user.email for user in page_users]
            page_users = User.objects(email__in=emails)
            page_users.update(push__roles=role)
            print(
                "Added role to \n{}\n\n".format(
                    "\n".join(page_users.values_list("email"))
                )
            )
    else:
        users = User.objects(Q(email__in=users) & Q(roles__nin=[role]))
        click.echo("Adding role {} to {} users".format(role, users.count()))
        users.update(push__roles=role)


@click.command()
@click.argument("info", type=str, nargs=-1)
def create_admins(info):
    """Create admin users with given email ids."""
    for each in info:
        name, email = each.split(":")
        try:
            user = User.objects.get(email=email)
        except DoesNotExist:
            user = User(name=name, email=email)
            user.save()
        finally:
            for role in (".approved-user", "admin"):
                if not user.has_role(role):
                    user.roles.append(role)
            user.save()


@click.command()
@click.argument("role", type=str)
@click.option("--all", default=False, is_flag=True)
@click.argument("users", type=str, nargs=-1)
def remove_role(role, users, all):
    """Remove a role for the specified users."""
    if all:
        click.echo("Removing role {} from all users".format(role))
        User.objects(roles__in=[role]).update(pull__roles=role)
    else:
        click.echo("Removing role {} from {} users".format(role, len(users)))
        users = User.objects(Q(email__in=users) & Q(roles__in=[role]))
        users.update(pull__roles=role)


@click.command()
def list_genders():
    genders = set(User.objects.values_list("gender"))
    for gender in sorted(genders, key=lambda x: str(x)):
        users = User.objects(gender=gender).only("email")
        users_count = users.count()
        if users_count <= 3:
            display_users = ", ".join(users.values_list("email"))
        else:
            display_users = "{} users".format(users_count)
        click.echo("{}\t{}".format(gender, display_users))


@click.command()
@click.argument("gender", type=str)
@click.argument("users", type=str, nargs=-1)
def set_gender(gender, users):
    click.echo("Setting gender {} for {} users".format(gender, len(users)))
    users = User.objects(Q(email__in=users))
    users.update(gender=gender)


@click.command()
@click.argument("user", type=str)
@click.argument("attribute", type=str)
@click.argument("value", type=str)
def set_user_attribute(user, attribute, value):
    user = User.objects.get(email=user)
    if attribute not in user._fields:
        click.echo("{} is not a valid attribute for users".format(attribute))
        return

    click.echo("Setting {} to {} for {} users".format(attribute, value, user))
    setattr(user, attribute, markdown_to_html(value))
    user.save()


@click.command()
@click.argument("csv_url", type=str)
def set_birthdays_from_csv(csv_url):
    reader = csv.DictReader(requests.get(csv_url).text.splitlines())
    csv_data = {
        row["Email address"].lower(): row["Date of Birth"] for row in reader
    }
    users = User.objects.filter(dob=None)
    for user in users:
        email = user.email
        if email not in csv_data:
            continue
        birthday = datetime.strptime(csv_data[user.email], "%m/%d/%Y")
        click.echo("Setting {}'s birthday to {}".format(email, birthday))
        user.dob = birthday
        user.save()


@click.command()
@click.argument("emails", type=str, nargs=-1)
def canonicalize_users(emails):
    """The first email ID will be used as the canonical email.

    All the IDs following the first one, will be replaced by the first one.

    """
    if len(emails) < 2:
        click.echo("Need at least 2 emails to canonicalize users")
        return

    canonical_email = emails[0]
    canonical_user = User.objects.get(email=canonical_email)
    for email in emails[1:]:
        user = User.objects.get(email=email)
        for event in Event.objects():
            try:
                rsvp = event.rsvps.get(user=user)
            except DoesNotExist:
                pass
            else:
                click.echo("Changing rsvp {}".format(rsvp.id))
                rsvp.user = canonical_user
                rsvp.save()
        user.delete()


@click.command()
@click.argument("emails", type=str, nargs=-1)
def notify_santas(emails):
    """Manually notify santas missed out when the automated script was run.

    Each argument is a pair of email addresses separated by a comma. For
    instance, santa@example.com,kiddo@email.com. Each such santa,kiddo pair is
    one argument to the command.

    """

    def process_pair(email):
        pair = [e.strip() for e in email.split(",")]
        assert len(pair) == 2
        assert "@" in pair[0]
        assert "@" in pair[1]
        return pair

    pairs = [process_pair(email) for email in emails]
    if not pairs:
        click.echo("Need at least one santa,kiddo pair!")
        return

    from rsvp.app import app
    from rsvp.rudolph import notify_santas

    click.echo("Notifying pairs: {}".format(dict(pairs)))
    with app.app_context():
        notify_santas(pairs)


cli.add_command(approve_all_users)
cli.add_command(create_approved_user)
cli.add_command(create_admins)
cli.add_command(list_users)
cli.add_command(list_roles)
cli.add_command(add_role)
cli.add_command(canonicalize_users)
cli.add_command(remove_role)
cli.add_command(list_genders)
cli.add_command(set_gender)
cli.add_command(set_user_attribute)
cli.add_command(set_birthdays_from_csv)
cli.add_command(notify_santas)
if __name__ == "__main__":
    cli()
