from django.core.mail import send_mail
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.urls import reverse, reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.views.generic import DeleteView, FormView, TemplateView
from django.contrib import messages
from django.db.models import Q, Count, Sum
from django.views.generic import CreateView, DetailView, ListView, UpdateView
from django.http import HttpResponse, Http404, HttpResponseServerError
from django.utils import timezone
from django.core.exceptions import PermissionDenied
from django.core.mail import send_mail
from django.forms import modelform_factory
from django import forms
from django.views.decorators.http import require_http_methods
from django.core.cache import cache

from django_select2.views import AutoResponseView

from functools import reduce
import csv

from mailing.forms import MessageForm
from mailing.utils import send_message
from .planning import Program
from .decorators import speaker_required, volunteer_required, staff_required
from .mixins import StaffRequiredMixin, OnSiteMixin, OnSiteFormMixin
from .utils import is_staff
from .models import Participant, Talk, TalkCategory, Vote, Track, Tag, Room, Volunteer, Activity
from .emails import talk_email_send, talk_email_render_preview, \
                    speaker_email_send, speaker_email_render_preview, \
                    volunteer_email_send, volunteer_email_render_preview
from .forms import TalkForm, TalkStaffForm, TalkFilterForm, get_talk_speaker_form_class, \
                   TalkActionForm, SpeakerActionForm, VolunteerActionForm, \
                   ParticipantForm, ParticipantFilterForm, NotifyForm, \
                   ConferenceForm, HomepageForm, CreateUserForm, TrackForm, RoomForm, \
                   VolunteerForm, VolunteerFilterForm, EmailForm, \
                   PreviewTalkMailForm, PreviewSpeakerMailForm, PreviewVolunteerMailForm, \
                   SendTalkMailForm, SendSpeakerMailForm, SendVolunteerMailForm, \
                   TagForm, TalkCategoryForm, ActivityForm, \
                   ACCEPTATION_VALUES, CONFIRMATION_VALUES


def home(request):
    if request.conference.home:
        return render(request, 'cfp/home.html')
    else:
        return redirect(reverse('proposal-home'))


def volunteer_enrole(request):
    if request.user.is_authenticated and Volunteer.objects.filter(site=request.conference.site, email=request.user.email).exists():
        return redirect(reverse('volunteer-dashboard'))
    if not request.conference.volunteers_enrollment_is_open():
        raise PermissionDenied
    initial = {}
    if request.user.is_authenticated and not request.POST:
        initial.update({
            'name': request.user.get_full_name(),
            'phone_number': request.user.profile.phone_number,
            'sms_prefered': request.user.profile.sms_prefered,
        })
    form = VolunteerForm(request.POST or None, initial=initial, conference=request.conference)
    if request.user.is_authenticated:
        form.fields.pop('email')
    if request.method == 'POST' and form.is_valid():
        volunteer = form.save(commit=False)
        volunteer.language = request.LANGUAGE_CODE
        if request.user.is_authenticated:
            volunteer.email = request.user.email
        volunteer.save()
        form.save_m2m()
        body = _("""Hi {},

Thank your for your help in the organization of the conference {}!

You can update your availability at anytime:

    {}

Thanks!

{}

""").format(volunteer.name, request.conference.name, volunteer.get_secret_url(full=True), request.conference.name)
        send_message(
            thread=volunteer.conversation,
            author=request.conference,
            subject=_('[%(conference)s] Thank you for your help!') % {'conference': request.conference},
            content=body,
        )
        messages.success(request, _('Thank you for your participation! You can now subscribe to some activities.'))
        return redirect(reverse('volunteer-dashboard', kwargs={'volunteer_token': volunteer.token}))
    return render(request, 'cfp/volunteer_enrole.html', {
        'activities': Activity.objects.filter(site=request.conference.site),
        'form': form,
    })


def volunteer_mail_token(request):
    form = EmailForm(request.POST or None)
    if request.method == 'POST' and form.is_valid():
        try:
            volunteer = Volunteer.objects.get(site=request.conference.site, email=form.cleaned_data['email'])
        except Volunteer.DoesNotExist:
            messages.error(request, _('Sorry, we do not know this email.'))
        else:

            base_url = ('https' if request.is_secure() else 'http') + '://' + request.conference.site.domain
            url = base_url + reverse('volunteer-dashboard', kwargs=dict(volunteer_token=volunteer.token))
            body = render_to_string('cfp/mails/volunteer_send_token.txt', {
                'volunteer': volunteer,
                'url': url,
                'conf': request.conference
            })
            send_message(
                thread=volunteer.conversation,
                author=request.conference,
                subject=_("[%(conference)s] Someone asked to access your profil") % {'conference': request.conference},
                content=body,
            )
            messages.success(request, _('A email have been sent with a link to access to your profil.'))
            return redirect(reverse('volunteer-mail-token'))
    return render(request, 'cfp/volunteer_mail_token.html', {
        'form': form,
    })


@volunteer_required
def volunteer_dashboard(request, volunteer):
    return render(request, 'cfp/volunteer_dashboard.html', {
        'activities': Activity.objects.filter(site=request.conference.site),
        'volunteer': volunteer,
    })


@volunteer_required
def volunteer_profile(request, volunteer):
    form = VolunteerForm(request.POST or None, instance=volunteer, conference=request.conference)
    if request.method == 'POST' and form.is_valid():
        form.save()
        messages.success(request, _('Changes saved.'))
        return redirect(reverse('volunteer-dashboard', kwargs={'volunteer_token': volunteer.token}))
    return render(request, 'cfp/volunteer_profile.html', {
        'volunteer': volunteer,
        'form': form,
    })


@volunteer_required
def volunteer_update_activity(request, volunteer, activity, join):
    activity = get_object_or_404(Activity, slug=activity, site=request.conference.site)
    if join:
        volunteer.activities.add(activity)
        messages.success(request, _('Thank you for your participation!'))
    else:
        volunteer.activities.remove(activity)
        messages.success(request, _('Okay, no problem!'))
    return redirect(reverse('volunteer-dashboard', kwargs=dict(volunteer_token=volunteer.token)))


@staff_required
def volunteer_list(request):
    site = request.conference.site
    filter_form = VolunteerFilterForm(request.GET or None, site=site)
    # Filtering
    show_filters = False
    volunteers = Volunteer.objects.filter(site=site).order_by('pk').distinct().prefetch_related('activities')
    if filter_form.is_valid():
        data = filter_form.cleaned_data
        if len(data['activity']):
            show_filters = True
            q = Q()
            if 'none' in data['activity']:
                data['activity'].remove('none')
                q |= Q(activities__isnull=True)
            if len(data['activity']):
                q |= Q(activities__slug__in=data['activity'])
            volunteers = volunteers.filter(q)
    # Action
    action_form = VolunteerActionForm(request.POST or None, volunteers=volunteers)
    if request.method == 'POST' and action_form.is_valid():
        data = action_form.cleaned_data
        if data['email']:
            request.session['volunteer-email-list'] = data['volunteers']
            return redirect(reverse('volunteer-email'))
        return redirect(request.get_full_path())
    if request.GET.get('format') == 'csv':
        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename="volunteers.csv"'
        writer = csv.writer(response)
        for volunteer in volunteers:
            writer.writerow(volunteer.get_csv_row())
        return response
    else:
        contact_link = 'mailto:' + ','.join([volunteer.email for volunteer in volunteers.all()])
        csv_query_dict = request.GET.copy()
        csv_query_dict['format'] = 'csv'
        csv_link = '?' + csv_query_dict.urlencode()
        return render(request, 'cfp/staff/volunteer_list.html', {
            'volunteer_list': volunteers,
            'filter_form': filter_form,
            'action_form': action_form,
            'show_filters': show_filters,
            'contact_link': contact_link,
            'csv_link': csv_link,
            'pending_email': bool(request.session.get('volunteer-email-list', None)),
        })


@staff_required
def volunteer_details(request, volunteer_id):
    volunteer = get_object_or_404(Volunteer, site=request.conference.site, pk=volunteer_id)
    message_form = MessageForm(request.POST or None)
    if request.method == 'POST' and message_form.is_valid():
        in_reply_to = volunteer.conversation.message_set.last()
        send_message(
            thread=volunteer.conversation,
            author=request.user,
            subject='',
            content=message_form.cleaned_data['content'],
            in_reply_to=in_reply_to,
        )
        messages.success(request, _('Message sent!'))
        return redirect(reverse('volunteer-details', args=[volunteer.pk]))
    return render(request, 'cfp/staff/volunteer_details.html', {
        'volunteer': volunteer,
    })


@staff_required
def volunteer_email(request):
    volunteers = Volunteer.objects.filter(pk__in=request.session.get('volunteer-email-list', []))
    if not volunteers.exists():
        messages.error(request, _('Please select some volunteers.'))
        return redirect('volunteer-list')
    form = SendVolunteerMailForm(request.POST or None, initial=request.session.get('volunteer-email-stored'), volunteers=volunteers)
    if request.method == 'POST' and form.is_valid():
        subject = form.cleaned_data['subject']
        body = form.cleaned_data['body']
        request.session['volunteer-email-stored'] = {'subject': subject, 'body': body}
        if form.cleaned_data['confirm']:
            sent = volunteer_email_send(volunteers, subject, body)
            messages.success(request, _('%(count)d mails have been sent.') % {'count': sent})
            del request.session['volunteer-email-list']
            return redirect('volunteer-list')
        else:
            messages.info(request, _('You are ready to send %(count)d emails.') % {'count': volunteers.count()})
    else:
        form.fields.pop('confirm')
    return render(request, 'cfp/staff/volunteer_email.html', {
        'volunteers': volunteers,
        'form': form,
    })


@require_http_methods(['POST'])
@staff_required
def volunteer_email_preview(request):
    form = PreviewVolunteerMailForm(request.POST or None)
    if not form.is_valid():
        return HttpResponseServerError()
    volunteer = get_object_or_404(Volunteer, site=request.conference.site, pk=form.cleaned_data['volunteer'])
    preview = volunteer_email_render_preview(volunteer, form.cleaned_data['subject'], form.cleaned_data['body'])
    return HttpResponse(preview)


def proposal_home(request):
    categories = request.conference.opened_categories
    if not categories.exists():
        return render(request, 'cfp/closed.html')
    initial = {}
    fields = ['name', 'email', 'biography']
    if request.user.is_authenticated:
        if Participant.objects.filter(site=request.conference.site, email=request.user.email).exists():
            return redirect(reverse('proposal-dashboard'))
        elif not request.POST:
            initial.update({
                'name': request.user.get_full_name(),
                'biography': request.user.profile.biography,
            })
        fields.remove('email')
    NewSpeakerForm = modelform_factory(Participant, form=ParticipantForm, fields=fields)
    speaker_form = NewSpeakerForm(request.POST or None, initial=initial, conference=request.conference)
    talk_form = TalkForm(request.POST or None, categories=categories)
    if request.method == 'POST' and all(map(lambda f: f.is_valid(), [speaker_form, talk_form])):
        speaker = speaker_form.save(commit=False)
        speaker.site = request.conference.site
        if request.user.is_authenticated:
            speaker.email = request.user.email
        speaker.save()
        talk = talk_form.save(commit=False)
        talk.site = request.conference.site
        talk.save()
        talk.speakers.add(speaker)
        base_url = ('https' if request.is_secure() else 'http') + '://' + request.conference.site.domain
        url_dashboard = base_url + reverse('proposal-dashboard', kwargs=dict(speaker_token=speaker.token))
        url_talk_details = base_url + reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk))
        url_speaker_add = base_url + reverse('proposal-speaker-add', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk))
        body = _("""Hi {},

Your talk has been submitted for {}.

Here are the details of your talk:
Title: {}
Description: {}

You can at anytime:
- review and edit your profile: {}
- review and edit your talk: {}
- add a new co-speaker: {}

If you have any question, your can answer to this email.

Thanks!

{}

""").format(
            speaker.name, request.conference.name, talk.title, talk.description,
            url_dashboard, url_talk_details, url_speaker_add,
            request.conference.name,
        )
        send_message(
            thread=speaker.conversation,
            author=request.conference,
            subject=_("[%(conference)s] Thank you for your proposition '%(talk)s'") % {
                'conference': request.conference.name,
                'talk': talk,
            },
            content=body,
        )
        messages.success(request, _('Your proposition have been successfully submitted!'))
        return redirect(reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
    return render(request, 'cfp/proposal_home.html', {
        'speaker_form': speaker_form,
        'talk_form': talk_form,
    })


def proposal_mail_token(request):
    form = EmailForm(request.POST or None)
    if request.method == 'POST' and form.is_valid():
        try:
            speaker = Participant.objects.get(site=request.conference.site, email=form.cleaned_data['email'])
        except Participant.DoesNotExist:
            messages.error(request, _('Sorry, we do not know this email.'))
        else:

            base_url = ('https' if request.is_secure() else 'http') + '://' + request.conference.site.domain
            dashboard_url = base_url + reverse('proposal-dashboard', kwargs=dict(speaker_token=speaker.token))
            body = _("""Hi {},

Someone, probably you, asked to access your profile.
You can edit your talks or add new ones following this url:

  {}

If you have any question, your can answer to this email.

Sincerely,

{}

""").format(speaker.name, dashboard_url, request.conference.name)
            send_message(
                thread=speaker.conversation,
                author=request.conference,
                subject=_("[%(conference)s] Someone asked to access your profil") % {
                    'conference': request.conference.name,
                },
                content=body,
            )
            messages.success(request, _('A email have been sent with a link to access to your profil.'))
            return redirect(reverse('proposal-mail-token'))
    return render(request, 'cfp/proposal_mail_token.html', {
        'form': form,
    })


@speaker_required
def proposal_dashboard(request, speaker):
    return render(request, 'cfp/proposal_dashboard.html', {
        'speaker': speaker,
        'talks': speaker.talk_set.all(),
    })


@speaker_required
def proposal_talk_details(request, speaker, talk_id):
    talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
    return render(request, 'cfp/proposal_talk_details.html', {
        'speaker': speaker,
        'talk': talk,
    })


@speaker_required
def proposal_talk_edit(request, speaker, talk_id=None):
    if talk_id:
        talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
    else:
        talk = None
    categories = request.conference.opened_categories
    form = TalkForm(request.POST or None, request.FILES or None, categories=categories, instance=talk)
    if request.method == 'POST' and form.is_valid():
        talk = form.save(commit=False)
        talk.site = request.conference.site
        talk.save()
        talk.speakers.add(speaker)
        if talk_id:
            messages.success(request, _('Changes saved.'))
        else:
            # TODO: it could be great to receive the proposition by mail
            # but this is not crucial as the speaker already have a link in its mailbox
            messages.success(request, _('Your proposition have been successfully submitted!'))
        return redirect(reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
    return render(request, 'cfp/proposal_talk_form.html', {
        'speaker': speaker,
        'talk': talk,
        'form': form,
    })


@speaker_required
def proposal_talk_acknowledgment(request, speaker, talk_id, confirm):
    # TODO: handle multiple speakers case
    talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
    if not request.conference.disclosed_acceptances or not talk.accepted or request.conference.completed:
        raise PermissionDenied
    if talk.confirmed == confirm:
        if confirm:
            messages.warning(request, _('You already confirmed your participation to this talk.'))
        else:
            messages.warning(request, _('You already cancelled your participation to this talk.'))
    else:
        talk.confirmed = confirm
        talk.save()
        if confirm:
            confirmation_message= _('Your participation has been taken into account, thank you!')
            action = _('confirmed')
        else:
            confirmation_message = _('We have noted your unavailability.')
            action = _('cancelled')
        content = _('Speaker %(speaker)s %(action)s his/her participation for %(talk)s.') % {
            'speaker': speaker,
            'action': action,
            'talk': talk,
        }
        send_message(
            thread=talk.conversation,
            author=speaker,
            subject=_('[%(conference)s] %(speaker)s %(action)s his/her participation') % {
                'conference': request.conference,
                'speaker': speaker,
                'action': action,
            },
            content=content,
        )
        messages.success(request, confirmation_message)
    return redirect(reverse('proposal-talk-details', kwargs={'speaker_token': speaker.token, 'talk_id': talk.pk}))


# FIXME his this view really useful?
#@speaker_required
#def proposal_speaker_details(request, speaker, talk_id, co_speaker_id):
#    talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
#    co_speaker = get_object_or_404(Participant, site=request.conference.site, talk_set__pk=talk.pk, pk=co_speaker_id)
#    return render(request, 'cfp/proposal_speaker_details.html', {
#        'speaker': speaker,
#        'talk': talk,
#        'co_speaker': co_speaker,
#    })


@speaker_required
def proposal_speaker_edit(request, speaker, talk_id=None, co_speaker_id=None):
    talk, co_speaker, co_speaker_candidates = None, None, None
    if talk_id:
        talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
        if co_speaker_id:
            co_speaker = get_object_or_404(Participant, site=request.conference.site, talk__pk=talk.pk, pk=co_speaker_id)
        else:
            co_speaker_candidates = speaker.co_speaker_set.exclude(pk__in=talk.speakers.values_list('pk'))
    EditSpeakerForm = modelform_factory(Participant, form=ParticipantForm, fields=['name', 'email', 'biography'] + ParticipantForm.SOCIAL_FIELDS)
    all_forms = []
    speaker_form = EditSpeakerForm(request.POST or None, conference=request.conference, instance=co_speaker if talk else speaker)
    all_forms.append(speaker_form)
    if talk and not co_speaker_id:
        notify_form = NotifyForm(request.POST or None)
        all_forms.append(notify_form)
    else:
        notify_form = None
    if request.method == 'POST' and all(map(lambda f: f.is_valid(), all_forms)):
        edited_speaker = speaker_form.save()
        if talk:
            talk.speakers.add(edited_speaker)
            if co_speaker_id:
                messages.success(request, _('Changes saved.'))
            else:
                if notify_form.cleaned_data['notify']:
                    base_url = ('https' if request.is_secure() else 'http') + '://' + request.conference.site.domain
                    url_dashboard = base_url + reverse('proposal-dashboard', kwargs=dict(speaker_token=edited_speaker.token))
                    url_talk_details = base_url + reverse('proposal-talk-details', kwargs=dict(speaker_token=edited_speaker.token, talk_id=talk.pk))
                    url_speaker_add = base_url + reverse('proposal-speaker-add', kwargs=dict(speaker_token=edited_speaker.token, talk_id=talk.pk))
                    body = _("""Hi {},

{} add you as a co-speaker for the conference {}.

Here is a summary of the talk:
Title: {}
Description: {}

You can at anytime:
- review and edit your profile: {}
- review and edit the talk: {}
- add another co-speaker: {}

If you have any question, your can answer to this email.

Thanks!

{}

""").format(
                        edited_speaker.name, speaker.name, request.conference.name,
                        talk.title, talk.description,
                        url_dashboard, url_talk_details, url_speaker_add,
                        request.conference.name,
                    )
                    send_message(
                        thread=edited_speaker.conversation,
                        author=request.conference,
                        subject=_("[%(conference)s] You have been added as co-speaker to '%(talk)s'") % {
                            'conference': request.conference,
                            'talk': talk,
                        },
                        content=body,
                    )
                messages.success(request, _('Co-speaker successfully added to the talk.'))
            #return redirect(reverse('proposal-speaker-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
            return redirect(reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
        else:
            return redirect(reverse('proposal-dashboard', kwargs=dict(speaker_token=speaker.token)))
    return render(request, 'cfp/proposal_speaker_form.html', {
        'speaker': speaker,
        'talk': talk,
        'co_speaker': co_speaker,
        'co_speaker_candidates': co_speaker_candidates,
        'speaker_form': speaker_form,
        'notify_form': notify_form,
    })


@speaker_required
def proposal_speaker_add(request, speaker, talk_id, speaker_id):
    talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
    co_speaker = get_object_or_404(Participant, pk__in=speaker.co_speaker_set.values_list('pk'), pk=speaker_id)
    talk.speakers.add(co_speaker)
    messages.success(request, _('Co-speaker successfully added to the talk.'))
    return redirect(reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk_id)))


# TODO: ask for confirmation (with POST request needed)
@speaker_required
def proposal_speaker_remove(request, speaker, talk_id, co_speaker_id):
    talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
    co_speaker = get_object_or_404(Participant, site=request.conference.site, talk__pk=talk.pk, pk=co_speaker_id)
    # prevent speaker from removing his/her self
    if co_speaker.pk == speaker.pk:
        raise PermissionDenied
    talk.speakers.remove(co_speaker)
    messages.success(request, _('Co-speaker successfully removed from the talk.'))
    return redirect(reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk_id)))


@staff_required
def talk_acknowledgment(request, talk_id, confirm):
    talk = get_object_or_404(Talk, pk=talk_id, site=request.conference.site)
    if talk.accepted is not True or talk.confirmed == confirm:
        raise PermissionDenied
    # TODO: handle multiple speakers case
    talk.confirmed = confirm
    talk.save()
    if confirm:
        confirmation_message= _('The speaker confirmation have been noted.')
        action = _('confirmed')
        thread_note = _('The talk have been confirmed.')
    else:
        confirmation_message = _('The speaker unavailability have been noted.')
        action = _('cancelled')
    thread_note = _('The talk have been %(action)s.') % {'action': action}
    send_message(
        thread=talk.conversation,
        author=request.user,
        subject=_("[%(conference)s] The talk '%(talk)s' have been %(action)s.") % {
            'conference': request.conference,
            'talk': talk,
            'action': action,
        },
        content=thread_note,
    )
    messages.success(request, confirmation_message)
    return redirect(reverse('talk-details', kwargs=dict(talk_id=talk_id)))


@staff_required
def staff(request):
    return render(request, 'cfp/staff/base.html')


@staff_required
def admin(request):
    return render(request, 'cfp/admin/base.html')


@staff_required
def talk_list(request):
    talks = Talk.objects.filter(site=request.conference.site)
    # Filtering
    show_filters = False
    filter_form = TalkFilterForm(request.GET or None, site=request.conference.site)
    if filter_form.is_valid():
        data = filter_form.cleaned_data
        if len(data['category']):
            show_filters = True
            talks = talks.filter(reduce(lambda x, y: x | y, [Q(category__pk=pk) for pk in data['category']]))
        if len(data['accepted']):
            show_filters = True
            talks = talks.filter(reduce(lambda x, y: x | y, [Q(accepted=dict(ACCEPTATION_VALUES)[status]) for status in data['accepted']]))
        if len(data['confirmed']):
            show_filters = True
            talks = talks.filter(accepted=True)
            talks = talks.filter(reduce(lambda x, y: x | y, [Q(confirmed=dict(CONFIRMATION_VALUES)[status]) for status in data['confirmed']]))
        if data['room'] != None:
            show_filters = True
            talks = talks.filter(room__isnull=not data['room'])
        if data['scheduled'] != None:
            show_filters = True
            talks = talks.filter(start_date__isnull=not data['scheduled'])
        if len(data['tag']):
            show_filters = True
            talks = talks.filter(tags__slug__in=data['tag'])
        if len(data['track']):
            show_filters = True
            q = Q()
            if 'none' in data['track']:
                data['track'].remove('none')
                q |= Q(track__isnull=True)
            if len(data['track']):
                q |= Q(track__slug__in=data['track'])
            talks = talks.filter(q)
        if data['vote'] != None:
            show_filters = True
            if data['vote']:
                talks = talks.filter(vote__user=request.user)
            else:
                talks = talks.exclude(vote__user=request.user)
        if data['materials'] != None:
            show_filters = True
            materials_filter = Q(materials__isnull=False) & ~Q(materials__exact='')
            if data['materials']:
                talks = talks.filter(materials_filter)
            else:
                talks = talks.filter(~materials_filter)
        if data['video'] != None:
            show_filters = True
            if data['video']:
                talks = talks.exclude(video__exact='')
            else:
                talks = talks.filter(video__exact='')
    talks = talks.prefetch_related('category', 'speakers', 'track', 'tags')

    if request.GET.get('format') == 'csv':
        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename="talks.csv"'
        writer = csv.writer(response)
        for talk in talks:
            writer.writerow(talk.get_csv_row())
        return response

    # Action
    action_form = TalkActionForm(request.POST or None, talks=talks, site=request.conference.site)
    if request.method == 'POST' and action_form.is_valid():
        data = action_form.cleaned_data
        for talk_id in data['talks']:
            talk = Talk.objects.get(site=request.conference.site, pk=talk_id)
            if data['decision'] != None and data['decision'] != talk.accepted:
                if data['decision']:
                    action = _('accepted')
                else:
                    action = _('declined')
                note = _('The talk has been %(action)s.') % {'action': action}
                send_message(
                    thread=talk.conversation,
                    author=request.user,
                    subject=_("[%(conference)s] The talk '%(talk)s' have been %(action)s") % {
                        'conference': request.conference,
                        'talk': talk,
                        'action': action,
                    },
                    content=note,
                )
                talk.accepted = data['decision']
            if data['track']:
                talk.track = Track.objects.get(site=request.conference.site, slug=data['track'])
            if data['tag']:
                talk.tags.add(Tag.objects.get(site=request.conference.site, slug=data['tag']))
            if data['room']:
                talk.room = Room.objects.get(site=request.conference.site, slug=data['room'])
            talk.save()
        if data['email']:
            email = int(data['email'])
            if email == TalkActionForm.EMAIL_TALKS:
                request.session['talk-email-list'] = data['talks']
                return redirect(reverse('talk-email'))
            elif email == TalkActionForm.EMAIL_SPEAKERS:
                selected_talks = Talk.objects.filter(pk__in=data['talks'])
                speakers = Participant.objects.filter(pk__in=selected_talks.values('speakers__pk')).distinct()
                request.session['speaker-email-list'] = list(speakers.values_list('pk', flat=True))
                return redirect(reverse('speaker-email'))
        return redirect(request.get_full_path())
    # Sorting
    if request.GET.get('order') == 'desc':
        sort_reverse = True
    else:
        sort_reverse = False
    SORT_MAPPING = {
        'title': 'title',
        'category': 'category',
        'status': 'accepted',
    }
    sort = request.GET.get('sort')
    if sort in SORT_MAPPING.keys():
        if sort_reverse:
            talks = talks.order_by('-' + SORT_MAPPING[sort])
        else:
            talks = talks.order_by(SORT_MAPPING[sort])
    # Sorting URLs
    sort_urls = dict()
    sort_glyphicons = dict()
    for c in SORT_MAPPING.keys():
        url = request.GET.copy()
        url['sort'] = c
        if c == sort:
            if sort_reverse:
                del url['order']
                glyphicon = 'sort-by-attributes-alt'
            else:
                url['order'] = 'desc'
                glyphicon = 'sort-by-attributes'
        else:
            glyphicon = 'sort'
        sort_urls[c] = url.urlencode()
        sort_glyphicons[c] = glyphicon
    csv_query_dict = request.GET.copy()
    csv_query_dict['format'] = 'csv'
    csv_link = '?' + csv_query_dict.urlencode()
    return render(request, 'cfp/staff/talk_list.html', {
        'show_filters': show_filters,
        'talk_list': talks,
        'filter_form': filter_form,
        'action_form': action_form,
        'sort_urls': sort_urls,
        'sort_glyphicons': sort_glyphicons,
        'csv_link': csv_link,
        'pending_email': bool(request.session.get('talk-email-list', None)),
    })


@staff_required
def talk_details(request, talk_id):
    talk = get_object_or_404(Talk, pk=talk_id, site=request.conference.site)
    try:
        vote = talk.vote_set.get(user=request.user).vote
    except Vote.DoesNotExist:
        vote = None
    message_form = MessageForm(request.POST or None)
    if request.method == 'POST' and message_form.is_valid():
        in_reply_to = talk.conversation.message_set.last()
        subject=_("[%(conference)s] New comment about '%(talk)s'") % {
            'conference': request.conference,
            'talk': talk,
        }
        if in_reply_to:
            # Maybe use in_reply_to.subject?
            subject = 'Re: ' + subject
        send_message(
            thread=talk.conversation,
            author=request.user,
            subject=subject,
            content=message_form.cleaned_data['content'],
            in_reply_to=in_reply_to,
        )
        messages.success(request, _('Message sent!'))
        return redirect(reverse('talk-details', args=[talk.pk]))
    return render(request, 'cfp/staff/talk_details.html', {
        'talk': talk,
        'vote': vote,
    })


@staff_required
def talk_vote(request, talk_id, score):
    if score not in [-2, -1, 0, 1, 2]:
        raise Http404
    talk = get_object_or_404(Talk, pk=talk_id, site=request.conference.site)
    vote, created = Vote.objects.get_or_create(talk=talk, user=request.user)
    vote.vote = score
    vote.save()
    messages.success(request, _('Vote successfully created') if created else _('Vote successfully updated'))
    return redirect(talk.get_absolute_url())


@staff_required
def talk_decide(request, talk_id, accept):
    talk = get_object_or_404(Talk, pk=talk_id, site=request.conference.site)
    if request.method == 'POST':
        talk.accepted = accept
        talk.save()
        if accept:
            action = _('accepted')
        else:
            action = _('declined')
        # Does we need to send a notification to the proposer?
        m = request.POST.get('message', '').strip()
        if m:
            for participant in talk.speakers.all():
                send_message(
                    thread=participant.conversation,
                    author=request.conference,
                    subject=_("[%(conference)s] Your talk '%(talk)s' have been %(action)s") % {
                        'conference': request.conference,
                        'talk': talk,
                        'action': action,
                    },
                    content=m,
                )
        # Save the decision in the talk's conversation
        send_message(
            thread=talk.conversation,
            author=request.user,
            subject=_("[%(conference)s] The talk '%(talk)s' have been %(action)s") % {
                'conference': request.conference,
                'talk': talk,
                'action': action,
            },
            content=_('The talk has been %(action)s.') % {'action': action},
        )
        messages.success(request, _('Decision taken in account'))
        return redirect(talk.get_absolute_url())
    return render(request, 'cfp/staff/talk_decide.html', {
        'talk': talk,
        'accept': accept,
    })


@staff_required
def talk_email(request):
    talks = Talk.objects.filter(pk__in=request.session.get('talk-email-list', []))
    count = talks.annotate(speakers_count=Count('speakers', distinct=True)).aggregate(Sum('speakers_count'))['speakers_count__sum']
    if not talks.exists():
        messages.error(request, _('Please select some talks.'))
        return redirect('talk-list')
    form = SendTalkMailForm(request.POST or None, initial=request.session.get('talk-email-stored'), talks=talks)
    if request.method == 'POST' and form.is_valid():
        subject = form.cleaned_data['subject']
        body = form.cleaned_data['body']
        request.session['talk-email-stored'] = {'subject': subject, 'body': body}
        if form.cleaned_data['confirm']:
            sent = talk_email_send(talks, subject, body)
            messages.success(request, _('%(count)d mails have been sent.') % {'count': sent})
            del request.session['talk-email-list']
            return redirect('talk-list')
        else:
            messages.info(request, _('You are ready to send %(count)d emails.') % {'count': count})
    else:
        form.fields.pop('confirm')
    return render(request, 'cfp/staff/talk_email.html', {
        'talks': talks,
        'form': form,
    })


@require_http_methods(['POST'])
@staff_required
def talk_email_preview(request):
    form = PreviewTalkMailForm(request.POST or None)
    if not form.is_valid():
        return HttpResponseServerError()
    speaker = get_object_or_404(Participant, site=request.conference.site, pk=form.cleaned_data['speaker'])
    talk = get_object_or_404(Talk, site=request.conference.site, pk=form.cleaned_data['talk'])
    preview = talk_email_render_preview(talk, speaker, form.cleaned_data['subject'], form.cleaned_data['body'])
    return HttpResponse(preview)


@staff_required
def participant_list(request):
    participants = Participant.objects.filter(site=request.conference.site) \
                                      .extra(select={'lower_name': 'lower(name)'}) \
                                      .order_by('lower_name')
    # Filtering
    show_filters = False
    filter_form = ParticipantFilterForm(request.GET or None, site=request.conference.site)
    if filter_form.is_valid():
        data = filter_form.cleaned_data
        talks = Talk.objects.filter(site=request.conference.site)
        if len(data['category']):
            show_filters = True
            talks = talks.filter(reduce(lambda x, y: x | y, [Q(category__pk=pk) for pk in data['category']]))
        if len(data['accepted']):
            show_filters = True
            talks = talks.filter(reduce(lambda x, y: x | y, [Q(accepted=dict(ACCEPTATION_VALUES)[status]) for status in data['accepted']]))
        if len(data['confirmed']):
            show_filters = True
            talks = talks.filter(accepted=True)
            talks = talks.filter(reduce(lambda x, y: x | y, [Q(confirmed=dict(CONFIRMATION_VALUES)[status]) for status in data['confirmed']]))
        if len(data['track']):
            show_filters = True
            q = Q()
            if 'none' in data['track']:
                data['track'].remove('none')
                q |= Q(track__isnull=True)
            if len(data['track']):
                q |= Q(track__slug__in=data['track'])
            talks = talks.filter(q)
        participants = participants.filter(talk__in=talks)
    # Action
    action_form = SpeakerActionForm(request.POST or None, speakers=participants)
    if request.method == 'POST' and action_form.is_valid():
        data = action_form.cleaned_data
        if data['email']:
            request.session['speaker-email-list'] = data['speakers']
            return redirect(reverse('speaker-email'))
        return redirect(request.get_full_path())
    if request.GET.get('format') == 'csv':
        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename="participants.csv"'
        writer = csv.writer(response)
        for participant in participants:
            writer.writerow(participant.get_csv_row())
        return response
    else:
        contact_link = 'mailto:' + ','.join([participant.email for participant in participants.all()])
        csv_query_dict = request.GET.copy()
        csv_query_dict['format'] = 'csv'
        csv_link = '?' + csv_query_dict.urlencode()
        return render(request, 'cfp/staff/participant_list.html', {
            'filter_form': filter_form,
            'action_form': action_form,
            'participant_list': participants,
            'show_filters': show_filters,
            'contact_link': contact_link,
            'csv_link': csv_link,
            'pending_email': bool(request.session.get('speaker-email-list', None)),
        })


@staff_required
def participant_details(request, participant_id):
    participant = get_object_or_404(Participant, pk=participant_id, site=request.conference.site)
    message_form = MessageForm(request.POST or None)
    if request.method == 'POST' and message_form.is_valid():
        in_reply_to = participant.conversation.message_set.last()
        send_message(
            thread=participant.conversation,
            author=request.user,
            subject='',
            content=message_form.cleaned_data['content'],
            in_reply_to=in_reply_to,
        )
        messages.success(request, _('Message sent!'))
        return redirect(reverse('participant-details', args=[participant.pk]))
    return render(request, 'cfp/staff/participant_details.html', {
        'participant': participant,
    })


class ParticipantCreate(StaffRequiredMixin, OnSiteFormMixin, CreateView):
    model = Participant
    template_name = 'cfp/staff/participant_form.html'

    def get_form_class(self):
        return modelform_factory(
                    self.model,
                    form=ParticipantForm,
                    fields=['name', 'vip', 'email', 'phone_number', 'biography', 'notes'] + ParticipantForm.SOCIAL_FIELDS,
        )


class ParticipantUpdate(StaffRequiredMixin, OnSiteFormMixin, UpdateView):
    model = Participant
    template_name = 'cfp/staff/participant_form.html'
    slug_field = 'pk'
    slug_url_kwarg = 'participant_id'

    def get_form_class(self):
        return modelform_factory(
                    self.model,
                    form=ParticipantForm,
                    fields=['name', 'vip', 'email', 'phone_number', 'biography', 'notes'] + ParticipantForm.SOCIAL_FIELDS,
        )


class ParticipantRemove(StaffRequiredMixin, OnSiteFormMixin, DeleteView):
    slug_field = 'pk'
    slug_url_kwarg = 'participant_id'
    success_url = reverse_lazy('participant-list')

    def get_queryset(self):
        return Participant.objects.filter(talk__isnull=True)


@staff_required
def participant_add_talk(request, participant_id):
    participant = get_object_or_404(Participant, site=request.conference.site, pk=participant_id)
    form = TalkForm(request.POST or None, categories=TalkCategory.objects.filter(site=request.conference.site))
    if request.method == 'POST' and form.is_valid():
        talk = form.save(commit=False)
        talk.site = request.conference.site
        talk.save()
        talk.speakers.add(participant)
        return redirect(reverse('talk-details', kwargs={'talk_id': talk.pk}))
    return render(request, 'cfp/staff/talk_form.html', {
        'form': form,
        'participant': participant,
    })


@staff_required
def speaker_email(request):
    speakers = Participant.objects.filter(pk__in=request.session.get('speaker-email-list', []))
    if not speakers.exists():
        messages.error(request, _('Please select some speakers.'))
        return redirect('participant-list')
    form = SendSpeakerMailForm(request.POST or None, initial=request.session.get('speaker-email-stored'), speakers=speakers)
    if request.method == 'POST' and form.is_valid():
        subject = form.cleaned_data['subject']
        body = form.cleaned_data['body']
        request.session['speaker-email-stored'] = {'subject': subject, 'body': body}
        if form.cleaned_data['confirm']:
            sent = speaker_email_send(speakers, subject, body)
            messages.success(request, _('%(count)d mails have been sent.') % {'count': sent})
            del request.session['speaker-email-list']
            return redirect('participant-list')
        else:
            messages.info(request, _('You are ready to send %(count)d emails.') % {'count': speakers.count()})
    else:
        form.fields.pop('confirm')
    return render(request, 'cfp/staff/speaker_email.html', {
        'speakers': speakers,
        'form': form,
    })


@require_http_methods(['POST'])
@staff_required
def speaker_email_preview(request):
    form = PreviewSpeakerMailForm(request.POST or None)
    if not form.is_valid():
        return HttpResponseServerError()
    speaker = get_object_or_404(Participant, site=request.conference.site, pk=form.cleaned_data['speaker'])
    preview = speaker_email_render_preview(speaker, form.cleaned_data['subject'], form.cleaned_data['body'])
    return HttpResponse(preview)


@staff_required
def conference_edit(request):
    form = ConferenceForm(request.POST or None, instance=request.conference)
    if request.method == 'POST' and form.is_valid():
        old_staff = set(request.conference.staff.all())
        new_conference = form.save()
        new_staff = set(new_conference.staff.all())
        added_staff = new_staff - old_staff
        protocol = 'https' if request.is_secure() else 'http'
        base_url = protocol+'://'+request.conference.site.domain
        url_login = base_url + reverse('login')
        url_password_reset = base_url + reverse('password_reset')
        msg_title = _('[{}] You have been added to the staff team').format(request.conference.name)
        msg_body_template = _("""Hi {},

You have been added to the staff team.

You can now:
- login: {}
- reset your password: {}

{}

""")
        # TODO: send bulk emails
        for user in added_staff:
            msg_body = msg_body_template.format(user.get_full_name(), url_login, url_password_reset, request.conference.name)
            send_mail(
                msg_title,
                msg_body,
                request.conference.from_email(),
                [user.email],
                fail_silently=False,
            )
        messages.success(request, _('Modifications successfully saved.'))
        return redirect(reverse('conference-edit'))
    return render(request, 'cfp/admin/conference.html', {
        'form': form,
    })


@staff_required
def homepage_edit(request):
    form = HomepageForm(request.POST or None, instance=request.conference)
    if request.method == 'POST' and form.is_valid():
        form.save()
        messages.success(request, _('Modifications successfully saved.'))
        return redirect(reverse('homepage-edit'))
    return render(request, 'cfp/admin/homepage.html', {
        'form': form,
    })


class TalkUpdate(StaffRequiredMixin, OnSiteMixin, OnSiteFormMixin, UpdateView):
    model = Talk
    template_name = 'cfp/staff/talk_form.html'
    pk_url_kwarg = 'talk_id'

    def get_form_class(self):
        return get_talk_speaker_form_class(self.object.site)


class TrackMixin(OnSiteMixin):
    model = Track


class TrackList(StaffRequiredMixin, TrackMixin, ListView):
    template_name = 'cfp/staff/track_list.html'


class TrackFormMixin(OnSiteFormMixin, TrackMixin):
    template_name = 'cfp/staff/track_form.html'
    form_class = TrackForm
    success_url = reverse_lazy('track-list')


class TrackCreate(StaffRequiredMixin, TrackFormMixin, CreateView):
    pass


class TrackUpdate(StaffRequiredMixin, TrackFormMixin, UpdateView):
    pass


class RoomMixin(OnSiteMixin):
    model = Room


class RoomList(StaffRequiredMixin, RoomMixin, ListView):
    template_name = 'cfp/staff/room_list.html'


class RoomDetail(StaffRequiredMixin, RoomMixin, DetailView):
    template_name = 'cfp/staff/room_details.html'


class RoomFormMixin(RoomMixin):
    template_name = 'cfp/staff/room_form.html'
    form_class = RoomForm
    success_url = reverse_lazy('room-list')

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs.update({
            'conference': self.request.conference,
        })
        return kwargs


class RoomCreate(StaffRequiredMixin, RoomFormMixin, CreateView):
    pass


class RoomUpdate(StaffRequiredMixin, RoomFormMixin, UpdateView):
    pass


class TalkCategoryMixin(OnSiteMixin):
    model = TalkCategory


class TalkCategoryList(StaffRequiredMixin, TalkCategoryMixin, ListView):
    template_name = 'cfp/admin/category_list.html'


class TalkCategoryFormMixin(TalkCategoryMixin):
    template_name = 'cfp/admin/category_form.html'
    form_class = TalkCategoryForm
    success_url = reverse_lazy('category-list')

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs.update({
            'conference': self.request.conference,
        })
        return kwargs


class TalkCategoryCreate(StaffRequiredMixin, TalkCategoryFormMixin, CreateView):
    pass


class TalkCategoryUpdate(StaffRequiredMixin, TalkCategoryFormMixin, UpdateView):
    pass


class TagMixin(OnSiteMixin):
    model = Tag


class TagList(StaffRequiredMixin, TagMixin, ListView):
    template_name = 'cfp/admin/tag_list.html'


class TagFormMixin(TagMixin):
    template_name = 'cfp/admin/tag_form.html'
    form_class = TagForm
    success_url = reverse_lazy('tag-list')

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs.update({
            'conference': self.request.conference,
        })
        return kwargs


class TagCreate(StaffRequiredMixin, TagFormMixin, CreateView):
    pass


class TagUpdate(StaffRequiredMixin, TagFormMixin, UpdateView):
    pass


class ActivityMixin(OnSiteMixin):
    model = Activity


class ActivityList(StaffRequiredMixin, ActivityMixin, ListView):
    template_name = 'cfp/admin/activity_list.html'


class ActivityFormMixin(ActivityMixin):
    template_name = 'cfp/admin/activity_form.html'
    form_class = ActivityForm
    success_url = reverse_lazy('activity-list')

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs.update({
            'conference': self.request.conference,
        })
        return kwargs


class ActivityCreate(StaffRequiredMixin, ActivityFormMixin, CreateView):
    pass


class ActivityUpdate(StaffRequiredMixin, ActivityFormMixin, UpdateView):
    pass


@staff_required
def create_user(request):
    form = CreateUserForm(request.POST or None)

    if request.method == 'POST' and form.is_valid():
        form.save()
        messages.success(request, _('User created successfully.'))
        return redirect(reverse('create-user'))

    return render(request, 'cfp/admin/create_user.html', {
        'form': form,
    })


def schedule(request, program_format, pending, template, staff, cache=None):
    program = Program(site=request.conference.site, pending=pending, staff=staff, cache=cache)
    if program_format is None:
        return render(request, template, {'program': program.render('html')})
    elif program_format == 'html':
        return HttpResponse(program.render('html'))
    elif program_format == 'xml':
        return HttpResponse(program.render('xml'), content_type="application/xml")
    elif program_format in ['ics', 'citymeo']:
        response = HttpResponse(program.render('ics', citymeo=bool(program_format == 'citymeo')), content_type='text/calendar')
        response['Content-Disposition'] = 'attachment; filename="planning.ics"'
        return response
    else:
        raise Http404(_("Format '%s' not available" % program_format))


def public_schedule(request, program_format):
    if not request.conference.schedule_available and not is_staff(request, request.user):
        raise PermissionDenied
    if request.conference.schedule_redirection_url and program_format is None:
        return redirect(request.conference.schedule_redirection_url)
    else:
        return schedule(request, program_format=program_format, pending=False, template='cfp/schedule.html', staff=False)


@staff_required
def staff_schedule(request, program_format):
    return schedule(request, program_format=program_format, pending=True, template='cfp/staff/schedule.html', staff=True, cache=False)


@staff_required
def schedule_evict(request):
    cache.clear()
    messages.success(request, _('Schedule evicted from cache.'))
    return redirect('/')


class Select2View(StaffRequiredMixin, AutoResponseView):
    pass