import collections
import csv
import datetime
import StringIO

from django.conf import settings
from django.contrib.auth.decorators import user_passes_test, login_required, permission_required
from django.core.exceptions import PermissionDenied
from django.views.generic import list_detail, ListView, DetailView
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from django.template import Context, Template
from django.template.loader import get_template
from django.http import Http404, HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from django.core.mail import EmailMessage, mail_admins
from django.forms import FileField
from django.forms import Form
from django.forms import ModelForm
from django.forms import ModelChoiceField, ModelMultipleChoiceField
from django.forms import ValidationError
from django.db import connection
from django.db.models import Q, Count

import django_filters

import forms.models
import groups.models
import groups.views
import util.emails

#################
# GENERIC VIEWS #
#################

class SelectGroupForm(Form):
    group = ModelChoiceField(queryset=groups.models.Group.objects.all())
    def __init__(self, *args, **kwargs):
        queryset = None
        if 'queryset' in kwargs:
            queryset = kwargs['queryset']
            del kwargs['queryset']
        super(SelectGroupForm, self).__init__(*args, **kwargs)
        if queryset is not None:
            self.fields["group"].queryset = queryset

def select_group(request, url_name_after, url_args=[], pagename='homepage', queryset=None, title="", msg=""):
    if request.method == 'POST': # If the form has been submitted...
        # A form bound to the POST data
        form = SelectGroupForm(request.POST, queryset=queryset, )
        if form.is_valid(): # All validation rules pass
            group = form.cleaned_data['group'].id
            return HttpResponseRedirect(reverse(url_name_after, args=url_args+[group],)) # Redirect after POST
    else:
        form = SelectGroupForm(queryset=queryset, ) # An unbound form

    if not title: title = "Select group"
    context = {
        'form':form,
        'title':title,
        'msg':msg,
        'pagename':pagename,
    }
    return render_to_response('forms/select.html', context, context_instance=RequestContext(request), )

#############################
# FIRST-YEAR SUMMER MAILING #
#############################

@login_required
def fysm_by_years(request, year, category, ):
    if year is None: year = datetime.date.today().year
    queryset = forms.models.FYSM.objects.filter(year=year).order_by('group__name')
    category_obj = None
    category_name = 'main'
    if category != None:
        category_obj = get_object_or_404(forms.models.FYSMCategory, slug=category)
        category_name = category_obj.name
        queryset = queryset.filter(categories=category_obj)
    forms.models.FYSMView.record_metric(request=request, fysm=None, year=year, page=category_name, )
    categories = forms.models.FYSMCategory.objects.all()
    return list_detail.object_list(
        request,
        queryset=queryset,
        template_name="fysm/fysm_listing.html",
        template_object_name="fysm",
        extra_context={
            "year": year,
            "pagename": "fysm",
            "category": category_obj,
            "categories": categories,
        }
    )

@login_required
def fysm_view(request, year, submission, ):
    submit_obj = get_object_or_404(forms.models.FYSM, pk=submission,)
    all = forms.models.FYSM.objects.only("id", "display_name", )
    try:
        prev = all.filter(display_name__lt=submit_obj.display_name).order_by("-display_name")[0]
    except IndexError:
        prev = None
    try:
        next = all.filter(display_name__gt=submit_obj.display_name).order_by("display_name")[0]
    except IndexError:
        next = None
    forms.models.FYSMView.record_metric(request=request, fysm=submit_obj, year=year, page="detail", )
    return list_detail.object_detail(
        request,
        forms.models.FYSM.objects,
        object_id=submission,
        template_name="fysm/fysm_detail.html",
        template_object_name="fysm",
        extra_context={
            "year": year,
            "pagename": "fysm",
            "prev": prev,
            "next": next,
        },
    )

def fysm_link(request, year, link_type, submission, ):
    submit_obj = get_object_or_404(forms.models.FYSM, pk=submission,)
    if submit_obj.year != int(year):
        raise Http404("Year mismatch: fysm.year='%s', request's year='%s'" % (submit_obj.year, year, ))
    if link_type == 'join':
        url = submit_obj.join_url
    elif link_type == 'website':
        url = submit_obj.website
    else:
        raise Http404("Unknown link type")
    forms.models.FYSMView.record_metric(request=request, fysm=submit_obj, year=year, page=link_type, )
    return HttpResponseRedirect(url)

def select_group_fysm(request, ):
    qobj = Q(activity_category__isnull = True) | ~(Q(activity_category__name='Dorm') | Q(activity_category__name='FSILG'))
    queryset = groups.models.Group.active_groups.filter(qobj)
    return select_group(
        request,
        url_name_after='fysm-manage',
        pagename='fysm',
        queryset=queryset,
    )

class FYSMRequestForm(ModelForm):
    class Meta:
        model = forms.models.FYSM
        fields = (
            'display_name',
            'website',
            'join_url',
            'contact_email',
            'description',
            'logo',
            'tags',
            'categories',
        )

    def clean_display_name(self, ):
        name = self.cleaned_data['display_name']
        if ',' in name:
            raise ValidationError("""In general, commas in a display name are a mistake and will look bad (group names like "Punctuation Society, MIT" should probably be "Punctuation Society"). If you do want a comma, contact asa-fysm@mit.edu and we'll put it in for you.""")
        return name

    def clean_description(self, ):
        description = self.cleaned_data['description']
        length = len(description)
        if length > 400:
            raise ValidationError("Descriptions are capped at 400 characters, and this one is %d characters." % (length, ))
        return description

@login_required
def fysm_manage(request, group, ):
    year = datetime.date.today().year
    group_obj = get_object_or_404(groups.models.Group, pk=group)

    initial = {}
    try:
        fysm_obj = forms.models.FYSM.objects.get(group=group_obj, year=year, )
    except forms.models.FYSM.DoesNotExist:
        fysm_obj = forms.models.FYSM()
        fysm_obj.group = group_obj
        fysm_obj.year = year
        initial['display_name'] = group_obj.name
        initial['year'] = year
        initial['website'] = group_obj.website_url
        initial['join_url'] = group_obj.website_url
        initial['contact_email'] = group_obj.officer_email

    if request.method == 'POST': # If the form has been submitted...
        form = FYSMRequestForm(request.POST, request.FILES, instance=fysm_obj, ) # A form bound to the POST data

        if form.is_valid(): # All validation rules pass
            request_obj = form.save()

            view_uri = request.build_absolute_uri(reverse('fysm-view', args=[year, request_obj.pk, ]))

            # Send email
            email = util.emails.email_from_template(
                tmpl='fysm/update_email.txt',
                context = Context({
                    'group': group_obj,
                    'fysm': fysm_obj,
                    'view_uri': view_uri,
                    'submitter': request.user,
                    'request': request,
                    'sender': "ASA FYSM team",
                }),
                subject='FYSM entry for "%s" updated by "%s"' % (
                    group_obj.name,
                    request.user,
                ),
                to=[group_obj.officer_email, request.user.email, ],
                from_email='asa-fysm@mit.edu',
            )
            email.bcc = ['asa-fysm-submissions@mit.edu']
            email.send()
            return HttpResponseRedirect(reverse('fysm-thanks', args=[fysm_obj.pk],)) # Redirect after POST

    else:
        form = FYSMRequestForm(instance=fysm_obj, initial=initial, ) # An unbound form

    context = {
        'group':group_obj,
        'fysm':fysm_obj,
        'form':form,
        'categories':forms.models.FYSMCategory.objects.all(),
        'pagename':'fysm',
    }
    return render_to_response('fysm/submit.html', context, context_instance=RequestContext(request), )


def fysm_thanks(request, fysm, ):
    year = datetime.date.today().year
    fysm_obj = get_object_or_404(forms.models.FYSM, pk=fysm)

    context = {
        'group':fysm_obj.group,
        'fysm':fysm_obj,
        'pagename':'fysm',
    }
    return render_to_response('fysm/thanks.html', context, context_instance=RequestContext(request), )

#####################
# Membership update #
#####################

membership_update_qs = groups.models.Group.objects.filter(group_status__slug__in=['active', 'suspended', ])

@login_required
def group_membership_update_select_group(request, ):
    cycle = forms.models.GroupConfirmationCycle.latest()

    users_groups = groups.models.Group.admin_groups(request.user.username)
    qs = membership_update_qs.filter(pk__in=users_groups)

    return select_group(request=request,
        url_name_after='membership-update-group',
        url_args=[cycle.slug],
        pagename='groups',
        queryset=qs,
        title="Submit membership update for...",
    )

class Form_GroupMembershipUpdate(ModelForm):
    def __init__(self, *args, **kwargs):
        super(Form_GroupMembershipUpdate, self).__init__(*args, **kwargs)
        self.fields['no_hazing'].required = True
        help_text = "If you have a membership list, you can get help turning it into membership breakdown using our <a href='%s'>people lookup</a> tool. You'll need to copy the numbers back over, though." % (reverse('membership-people-lookup'), )
        self.fields['num_undergrads'].help_text = help_text

    class Meta:
        model = forms.models.GroupMembershipUpdate
        fields = [
            'updater_title',
            'group_email',
            'officer_email',
            'email_preface',
            'no_hazing',
            'no_discrimination',
            'membership_definition',
            'num_undergrads',
            'num_grads',
            'num_alum',
            'num_other_affiliate',
            'num_other',
            #'membership_list',
        ]

@login_required
def group_membership_update(request, cycle_slug, pk, ):
    cycle = get_object_or_404(forms.models.GroupConfirmationCycle, slug=cycle_slug)
    group_obj = get_object_or_404(groups.models.Group, pk=pk)
    if not request.user.has_perm('groups.admin_group', group_obj):
        raise PermissionDenied

    try:
        update_obj = forms.models.GroupMembershipUpdate.objects.get(group=group_obj, cycle=cycle, )
    except forms.models.GroupMembershipUpdate.DoesNotExist:
        update_obj = None

    confirm_uri = request.build_absolute_uri(reverse('membership-confirm'))
    submitted_uri = request.build_absolute_uri(reverse('membership-submitted'))

    if request.method == 'POST':
        form = Form_GroupMembershipUpdate(request.POST, request.FILES, instance=update_obj) # A form bound to the POST data

        if form.is_valid(): # All validation rules pass
            # Update the updater info
            form.instance.group = group_obj
            form.instance.cycle = cycle
            form.instance.update_time  = datetime.datetime.now()
            form.instance.updater_name = request.user.username
            request_obj = form.save()

            # Send email
            tmpl = get_template('membership/anti-hazing.txt')
            ctx = Context({
                'update': request_obj,
                'group': group_obj,
                'submitter': request.user,
            })
            body = tmpl.render(ctx)
            email = EmailMessage(
                subject='Anti-Hazing and Non-Discrimination Acknowledgement for %s' % (
                    group_obj.name,
                ),
                body=body,
                from_email=request.user.email,
                to=[request_obj.group_email, ],
                cc=[request_obj.officer_email, ],
                bcc=['asa-db-outgoing@mit.edu', ],
            )
            email.send()

            # Send email
            tmpl = get_template('membership/submit-confirm-email.txt')
            ctx = Context({
                'update': request_obj,
                'group': group_obj,
                'submitter': request.user,
                'confirm_uri': confirm_uri,
                'submitted_uri': submitted_uri,
            })
            body = tmpl.render(ctx)
            email = EmailMessage(
                subject='ASA Membership Information for %s' % (
                    group_obj.name,
                ),
                body=body,
                from_email=request.user.email,
                to=[request_obj.officer_email, ],
                bcc=['asa-db-outgoing@mit.edu', ],
            )
            email.send()

            return HttpResponseRedirect(reverse('membership-thanks', )) # Redirect after POST

    else:
        form = Form_GroupMembershipUpdate(instance=update_obj)

    context = {
        'form':form,
        'group':group_obj,
        'cycle':cycle,
        'confirm_uri': confirm_uri,
        'pagename':'groups',
    }
    return render_to_response('membership/update.html', context, context_instance=RequestContext(request), )

class Form_PersonMembershipUpdate(ModelForm):
    groups = ModelMultipleChoiceField(queryset=membership_update_qs)
    class Meta:
        model = forms.models.PersonMembershipUpdate
        fields = [
            'groups',
        ]

@login_required
def person_membership_update(request, ):
    initial = {
    }
    cycle = forms.models.GroupConfirmationCycle.latest()

    # Initialize/find the PersonMembershipUpdate for this user
    try:
        update_obj = forms.models.PersonMembershipUpdate.objects.get(
            username=request.user.username,
            deleted__isnull=True,
            cycle=cycle,
        )
        selected_groups = update_obj.groups.all()
    except forms.models.PersonMembershipUpdate.DoesNotExist:
        update_obj = forms.models.PersonMembershipUpdate()
        update_obj.update_time  = datetime.datetime.now()
        update_obj.username = request.user.username
        update_obj.cycle = cycle
        selected_groups = []

    # Determine whether the submitter is a student or not
    accounts = groups.models.AthenaMoiraAccount
    try:
        person = accounts.active_accounts.get(username=request.user.username)
        if person.is_student():
            update_obj.valid = forms.models.VALID_AUTOVALIDATED
        else:
            update_obj.valid = forms.models.VALID_AUTOREJECTED
    except accounts.DoesNotExist:
        pass
        update_obj.valid = forms.models.VALID_AUTOREJECTED

    update_obj.save()

    # Find groups that list a role for the user
    office_holders = groups.models.OfficeHolder.current_holders.filter(person=request.user.username)
    role_groups = {}
    for office_holder in office_holders:
        if office_holder.group.pk not in role_groups:
            role_groups[office_holder.group.pk] = (office_holder.group, set())
        role_groups[office_holder.group.pk][1].add(office_holder.role.display_name)

    message = ""
    message_type = "info"

    if update_obj.valid <= forms.models.VALID_UNSET:
        message = "You are not listed as a student. While you're welcome to confirm your membership in groups anyway, you will not count towards the five student member requirement. If you are a student, please contact asa-exec so that we can correct our records."
        message_type = "warn"

    # Handle the single group add/remove forms
    # * removing previously confirmed groups
    # * add/remove groups that list the user in a role
    # * add/remove groups the user searched for
    if request.method == 'POST' and 'add-remove' in request.POST:
        group = groups.models.Group.objects.get(id=request.POST['group'])
        if request.POST['action'] == 'remove':
            if group in update_obj.groups.all():
                update_obj.groups.remove(group)
                message = "You have successfully unconfirmed membership in %s." % (group, )
            else:
                message = "Removal failed because you had not confirmed membership in %s." % (group, )
                message_type = "warn"
        elif request.POST['action'] == 'add':
            if group in update_obj.groups.all():
                message = "Membership in %s already confirmed." % (group, )
                message_type = "warn"
            else:
                update_obj.groups.add(group)
                message = "You have successfully confirmed membership in %s." % (group, )
        else:
            message = "Uh, somehow you tried to do something besides adding and removing..."
            message_type = "alert"

    # Handle the big list of groups
    if request.method == 'POST' and 'list' in request.POST:
        form = Form_PersonMembershipUpdate(request.POST, request.FILES, instance=update_obj)
        if form.is_valid():
            request_obj = form.save()
            message = "Update saved"
    else:
        form = Form_PersonMembershipUpdate(initial=initial, instance=update_obj, )
    form.fields['groups'].widget.attrs['size'] = 20

    # Render the page
    context = {
        'role_groups':role_groups,
        'form':form,
        'member_groups':selected_groups,
        'message': message,
        'message_type': message_type,
        'pagename':'groups',
    }
    return render_to_response('membership/confirm.html', context, context_instance=RequestContext(request), )


class View_GroupMembershipList(ListView):
    context_object_name = "group_list"
    template_name = "membership/submitted.html"

    def get_queryset(self):
        cycle = forms.models.GroupConfirmationCycle.latest()
        group_updates = forms.models.GroupMembershipUpdate.objects.all()
        group_updates = group_updates.filter(
            cycle=cycle,
            group__personmembershipupdate__cycle=cycle,
            group__personmembershipupdate__deleted__isnull=True,
            group__personmembershipupdate__valid__gt=0,
        )
        group_updates = group_updates.annotate(num_confirms=Count('group__personmembershipupdate'))
        #print len(list(group_updates))
        #for query in connection.queries: print query
        return group_updates


class View_GroupConfirmationCyclesList(ListView):
    context_object_name = "cycle_list"
    template_name = "membership/admin.html"
    model = forms.models.GroupConfirmationCycle

    def get_context_data(self, **kwargs):
        context = super(View_GroupConfirmationCyclesList, self).get_context_data(**kwargs)
        context['pagename'] = 'groups'
        return context


@permission_required('groups.view_group_private_info')
def group_confirmation_issues(request, slug, ):
    account_numbers = ("accounts" in request.GET) and request.GET['accounts'] == "1"

    check_groups = groups.models.Group.objects.filter(group_status__slug__in=('active', 'suspended', ))
    check_groups = check_groups.select_related('group_status')
    group_updates = forms.models.GroupMembershipUpdate.objects.filter(cycle__slug=slug, )
    group_updates = group_updates.select_related('group', 'group__group_status')
    people_confirmations = forms.models.PersonMembershipUpdate.objects.filter(
        deleted__isnull=True,
        valid__gt=0,
        cycle__slug=slug,
    )

    buf = StringIO.StringIO()
    output = csv.writer(buf)
    fields = ['group_id', 'group_name', 'group_status', 'recognition_date', 'issue', 'num_confirm', 'officer_email', ]
    if account_numbers: fields.append("main_account")
    output.writerow(fields)

    def output_issue(group, issue, num_confirms):
        fields = [
            group.id,
            group.name,
            group.group_status.slug,
            group.recognition_date,
            issue,
            num_confirms,
            group.officer_email,
        ]
        if account_numbers: fields.append(group.main_account_id)
        output.writerow(fields)

    q_present = Q(id__in=group_updates.values('group'))
    missing_groups = check_groups.filter(~q_present)
    #print len(list(group_updates))
    for group in missing_groups:
        #num_confirms = len(people_confirmations.filter(groups=group))
        output_issue(group, 'unsubmitted', '')

    for group_update in group_updates:
        group = group_update.group
        num_confirms = people_confirmations.filter(groups=group).count()
        problems = []

        if num_confirms < 5:
            problems.append("confirmations")

        num_students = group_update.num_undergrads + group_update.num_grads
        num_other = group_update.num_alum + group_update.num_other_affiliate + group_update.num_other
        if num_students < num_other:
            problems.append("50%")

        for problem in problems:
            output_issue(group, problem, num_confirms)

    return HttpResponse(buf.getvalue(), mimetype='text/csv', )


class PeopleStatusLookupForm(ModelForm):
    class Meta:
        model = forms.models.PeopleStatusLookup
        fields = ('people', )

def people_status_lookup(request, pk=None, ):
    if pk is None:
        if request.method == 'POST':
            form = PeopleStatusLookupForm(request.POST, request.FILES, )
            if form.is_valid(): # All validation rules pass
                lookup = form.save(commit=False)
                lookup.requestor = request.user
                lookup.referer = request.META['HTTP_REFERER']
                lookup.update_classified_people()
                results = lookup.classifications_with_descriptions()
                lookup.save()
        else:
            form = PeopleStatusLookupForm()
            results = None
    else:
        if request.user.has_perm('forms.view_peoplestatusupdate'):
            lookup = get_object_or_404(forms.models.PeopleStatusLookup, pk=int(pk))
            results = lookup.classifications_with_descriptions()
            form = None
        else:
            raise PermissionDenied("You don't have permission to view old lookup requests.")

    context = {
        'form': form,
        'results': results,
    }

    return render_to_response('membership/people-lookup.html', context, context_instance=RequestContext(request), )

##########
# Midway #
##########


class View_Midways(ListView):
    context_object_name = "midway_list"
    template_name = "midway/midway_list.html"

    def get_queryset(self):
        midways = forms.models.Midway.objects.order_by('date')
        return midways

    def get_context_data(self, **kwargs):
        context = super(View_Midways, self).get_context_data(**kwargs)
        context['pagename'] = 'midway'
        return context

def midway_map_latest(request, ):
    midways = forms.models.Midway.objects.order_by('-date')[:1]
    if len(midways) == 0:
        raise Http404("No midways found.")
    else:
        url = reverse('midway-map', args=(midways[0].slug, ))
        return HttpResponseRedirect(url)


class MidwayAssignmentFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(name='group__name', lookup_type='icontains', label="Name contains")
    abbreviation = django_filters.CharFilter(name='group__abbreviation', lookup_type='iexact', label="Abbreviation is")
    activity_category = django_filters.ModelChoiceFilter(
        label='Activity category',
        name='group__activity_category',
        queryset=groups.models.ActivityCategory.objects,
    )

    class Meta:
        model = forms.models.MidwayAssignment
        fields = [
            'name',
            'abbreviation',
            'activity_category',
        ]
        order_by = (
            ('group__name', 'Name', ),
            ('group__abbreviation', 'Abbreviation', ),
            ('group__activity_category__name', 'Activity category', ),
            ('location', 'Location', ),
        )


class MidwayMapView(DetailView):
    context_object_name = "midway"
    model = forms.models.Midway
    template_name = 'midway/map.html'

    def get_context_data(self, **kwargs):
        # Call the base implementation first to get a context
        context = super(MidwayMapView, self).get_context_data(**kwargs)
        
        filterset = MidwayAssignmentFilter(self.request.GET)
        context['assignments'] = filterset.qs
        context['filter'] = filterset
        context['pagename'] = 'midway'

        return context


class MidwayAssignmentsUploadForm(Form):
    def validate_csv_fields(upload_file):
        reader = csv.reader(upload_file)
        row = reader.next()
        for col in ('Group', 'officers', 'Table', ):
            if col not in row:
                raise ValidationError('Please upload a CSV file with (at least) columns "Group", "officers", and "Table". (Missing at least "%s".)' % (col, ))

    assignments = FileField(validators=[validate_csv_fields])

@permission_required('forms.add_midwayassignment')
def midway_assignment_upload(request, slug, ):
    midway = get_object_or_404(forms.models.Midway, slug=slug, )

    uploaded = False
    found = []
    issues = collections.defaultdict(list)

    if request.method == 'POST': # If the form has been submitted...
        form = MidwayAssignmentsUploadForm(request.POST, request.FILES, ) # A form bound to the POST data

        if form.is_valid(): # All validation rules pass
            uploaded = True
            reader = csv.DictReader(request.FILES['assignments'])
            for row in reader:
                group_name = row['Group']
                group_officers = row['officers']
                table = row['Table']
                issue = False
                try:
                    group = groups.models.Group.objects.get(name=group_name)
                    assignment = forms.models.MidwayAssignment(
                        midway=midway,
                        location=table,
                        group=group,
                    )
                    assignment.save()
                    found.append(assignment)
                    status = group.group_status.slug
                    if status != 'active':
                        issue = 'status=%s (added anyway)' % (status, )
                except groups.models.Group.DoesNotExist:
                    issue = 'unknown group (ignored)'
                except groups.models.Group.MultipleObjectsReturned:
                    issue = 'multiple groups found (ignored)'
                if issue:
                    issues[issue].append((group_name, group_officers, table))
            for issue in issues:
                issues[issue] = sorted(issues[issue], key=lambda x: x[0])

    else:
        form = MidwayAssignmentsUploadForm() # An unbound form

    context = {
        'midway':midway,
        'form':form,
        'uploaded': uploaded,
        'found': found,
        'issues': dict(issues),
        'pagename':'midway',
    }
    return render_to_response('midway/upload.html', context, context_instance=RequestContext(request), )
