import random
import zipfile
from datetime import timedelta
from io import BytesIO

from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.core.mail import send_mail
from django.db.models import Q
from django.http import HttpResponse
from django.shortcuts import redirect
from django.template.loader import render_to_string
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, CreateView, UpdateView
from django.views.generic.edit import BaseFormView
from django_tables2.views import SingleTableView
from member.models import TFJMUser, Solution, Synthesis

from .forms import TournamentForm, OrganizerForm, SolutionForm, SynthesisForm, TeamForm, PoolForm
from .models import Tournament, Team, Pool
from .tables import TournamentTable, TeamTable, SolutionTable, SynthesisTable, PoolTable


class AdminMixin(LoginRequiredMixin):
    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated or not request.user.admin:
            raise PermissionDenied
        return super().dispatch(request, *args, **kwargs)


class OrgaMixin(LoginRequiredMixin):
    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated or not request.user.organizes:
            raise PermissionDenied
        return super().dispatch(request, *args, **kwargs)


class TeamMixin(LoginRequiredMixin):
    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated or not request.user.team:
            raise PermissionDenied
        return super().dispatch(request, *args, **kwargs)


class TournamentListView(SingleTableView):
    model = Tournament
    table_class = TournamentTable
    extra_context = dict(title=_("Tournaments list"),)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        team_users = TFJMUser.objects.filter(Q(team__isnull=False) | Q(role="admin") | Q(role="organizer"))\
            .order_by('-role')
        valid_team_users = team_users.filter(
            Q(team__validation_status="2valid") | Q(role="admin") | Q(role="organizer"))

        context["team_users_emails"] = [user.email for user in team_users]
        context["valid_team_users_emails"] = [user.email for user in valid_team_users]

        return context


class TournamentCreateView(AdminMixin, CreateView):
    model = Tournament
    form_class = TournamentForm
    extra_context = dict(title=_("Add tournament"),)

    def get_success_url(self):
        return reverse_lazy('tournament:detail', args=(self.object.pk,))


class TournamentDetailView(DetailView):
    model = Tournament

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        context["title"] = _("Tournament of {name}").format(name=self.object.name)

        team_users = TFJMUser.objects.filter(
            Q(team__tournament=self.object)
            | Q(organized_tournaments=self.object)).order_by('role')
        valid_team_users = team_users.filter(
            Q(team__validation_status="2valid")
            | Q(role="admin")
            | Q(organized_tournaments=self.object))

        context["team_users_emails"] = [user.email for user in team_users]
        context["valid_team_users_emails"] = [user.email for user in valid_team_users]

        context["teams"] = TeamTable(self.object.teams.all())

        return context


class TournamentUpdateView(AdminMixin, UpdateView):
    model = Tournament
    form_class = TournamentForm
    extra_context = dict(title=_("Update tournament"),)

    def get_success_url(self):
        return reverse_lazy('tournament:detail', args=(self.object.pk,))


class TeamDetailView(LoginRequiredMixin, DetailView):
    model = Team

    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated or \
                (not request.user.admin and self.request.user not in self.get_object().tournament.organizers.all()
                 and self.get_object() != request.user.team):
            raise PermissionDenied
        return super().dispatch(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        print(request.POST)
        team = self.get_object()
        if "zip" in request.POST:
            solutions = team.solutions.all()

            out = BytesIO()
            zf = zipfile.ZipFile(out, "w")

            for solution in solutions:
                zf.write(solution.file.path, str(solution) + ".pdf")

            zf.close()

            resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
            resp['Content-Disposition'] = 'attachment; filename={}'\
                .format(_("Solutions for team {team}.zip")
                        .format(team=str(team)).replace(" ", "%20"))
            return resp
        elif "leave" in request.POST and request.user.participates:
            request.user.team = None
            request.user.save()
            if not team.users.exists():
                team.delete()
            return redirect('tournament:detail', pk=team.tournament.pk)
        elif "request_validation" in request.POST and request.user.participates:
            team.validation_status = "1waiting"
            team.save()
            team.tournament.send_mail_to_organizers("request_validation", "Demande de validation TFJM²", team=team)
            return redirect('tournament:team_detail', pk=team.pk)
        elif "validate" in request.POST and request.user.organizes:
            team.validation_status = "2valid"
            team.save()
            team.send_mail("validate_team", "Équipe validée TFJM²")
            return redirect('tournament:team_detail', pk=team.pk)
        elif "invalidate" in request.POST and request.user.organizes:
            team.validation_status = "0invalid"
            team.save()
            team.send_mail("unvalidate_team", "Équipe non validée TFJM²")
            return redirect('tournament:team_detail', pk=team.pk)
        elif "delete" in request.POST and request.user.organizes:
            team.delete()
            return redirect('tournament:detail', pk=team.tournament.pk)
        elif "select_final" in request.POST and request.user.admin and not team.selected_for_final and team.pools:
            for solution in team.solutions.all():
                alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
                id = ""
                for i in range(64):
                    id += random.choice(alphabet)
                with solution.file.open("rb") as source:
                    with open("/code/media/" + id, "wb") as dest:
                        for chunk in source.chunks():
                            dest.write(chunk)
                new_sol = Solution(
                    file=id,
                    team=team,
                    problem=solution.problem,
                    final=True,
                )
                new_sol.save()
            team.selected_for_final = True
            team.save()
            team.send_mail("select_for_final", "Sélection pour la finale, félicitations ! - TFJM²",
                           final=Tournament.get_final())
            return redirect('tournament:team_detail', pk=team.pk)

        return self.get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        context["title"] = _("Information about team")
        context["ordered_solutions"] = self.object.solutions.order_by('problem').all()
        context["team_users_emails"] = [user.email for user in self.object.users.all()]

        return context


class TeamUpdateView(LoginRequiredMixin, UpdateView):
    model = Team
    form_class = TeamForm
    extra_context = dict(title=_("Update team"),)

    def dispatch(self, request, *args, **kwargs):
        if not request.user.admin and self.request.user not in self.get_object().tournament.organizers.all() \
                and self.get_object() != self.request.user.team:
            raise PermissionDenied
        return super().dispatch(request, *args, **kwargs)


class AddOrganizerView(AdminMixin, CreateView):
    model = TFJMUser
    form_class = OrganizerForm
    extra_context = dict(title=_("Add organizer"),)
    template_name = "tournament/add_organizer.html"

    def form_valid(self, form):
        user = form.instance
        msg = render_to_string("mail_templates/add_organizer.txt", context=dict(user=user))
        msg_html = render_to_string("mail_templates/add_organizer.html", context=dict(user=user))
        send_mail('Organisateur du TFJM² 2020', msg, 'contact@tfjm.org', [user.email], html_message=msg_html)
        return super().form_valid(form)

    def get_success_url(self):
        return reverse_lazy('index')


class SolutionsView(TeamMixin, BaseFormView, SingleTableView):
    model = Solution
    table_class = SolutionTable
    form_class = SolutionForm
    template_name = "tournament/solutions_list.html"
    extra_context = dict(title=_("Solutions"))

    def post(self, request, *args, **kwargs):
        if "zip" in request.POST:
            solutions = request.user.team.solutions

            out = BytesIO()
            zf = zipfile.ZipFile(out, "w")

            for solution in solutions:
                zf.write(solution.file.path, str(solution) + ".pdf")

            zf.close()

            resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
            resp['Content-Disposition'] = 'attachment; filename={}'\
                .format(_("Solutions for team {team}.zip")
                        .format(team=str(request.user.team)).replace(" ", "%20"))
            return resp

        return super().post(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        self.object_list = self.get_queryset()
        context = super().get_context_data(**kwargs)
        context["now"] = timezone.now()
        context["real_deadline"] = self.request.user.team.tournament.date_solutions + timedelta(minutes=30)
        return context

    def get_queryset(self):
        qs = super().get_queryset().filter(team=self.request.user.team)
        return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram',
                           'problem',)

    def form_valid(self, form):
        solution = form.instance
        solution.team = self.request.user.team
        solution.final = solution.team.selected_for_final

        if timezone.now() > solution.tournament.date_solutions + timedelta(minutes=30):
            form.add_error('file', _("You can't publish your solution anymore. Deadline: {date:%m-%d-%Y %H:%M}.")
                           .format(date=timezone.localtime(solution.tournament.date_solutions)))
            return super().form_invalid(form)

        prev_sol = Solution.objects.filter(problem=solution.problem, team=solution.team, final=solution.final)
        for sol in prev_sol.all():
            sol.delete()
        alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
        id = ""
        for i in range(64):
            id += random.choice(alphabet)
        solution.file.name = id
        solution.save()

        return super().form_valid(form)

    def get_success_url(self):
        return reverse_lazy("tournament:solutions")


class SolutionsOrgaListView(OrgaMixin, SingleTableView):
    model = Solution
    table_class = SolutionTable
    template_name = "tournament/solutions_orga_list.html"
    extra_context = dict(title=_("All solutions"))

    def post(self, request, *args, **kwargs):
        if "tournament_zip" in request.POST:
            tournament = Tournament.objects.get(pk=int(request.POST["tournament_zip"]))
            solutions = tournament.solutions
            if not request.user.admin and request.user not in tournament.organizers.all():
                raise PermissionDenied

            out = BytesIO()
            zf = zipfile.ZipFile(out, "w")

            for solution in solutions:
                zf.write(solution.file.path, str(solution) + ".pdf")

            zf.close()

            resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
            resp['Content-Disposition'] = 'attachment; filename={}'\
                .format(_("Solutions for tournament {tournament}.zip")
                        .format(tournament=str(tournament)).replace(" ", "%20"))
            return resp

        return self.get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        context["tournaments"] = \
            Tournament.objects if self.request.user.admin else self.request.user.organized_tournaments

        return context

    def get_queryset(self):
        qs = super().get_queryset()
        if not self.request.user.admin:
            qs = qs.filter(Q(team__tournament__organizers=self.request.user) | Q(pools__juries=self.request.user))
        return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram',
                           'problem',).distinct()


class SynthesesView(TeamMixin, BaseFormView, SingleTableView):
    model = Synthesis
    table_class = SynthesisTable
    form_class = SynthesisForm
    template_name = "tournament/syntheses_list.html"
    extra_context = dict(title=_("Syntheses"))

    def post(self, request, *args, **kwargs):
        if "zip" in request.POST:
            syntheses = request.user.team.syntheses

            out = BytesIO()
            zf = zipfile.ZipFile(out, "w")

            for synthesis in syntheses:
                zf.write(synthesis.file.path, str(synthesis) + ".pdf")

            zf.close()

            resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
            resp['Content-Disposition'] = 'attachment; filename={}'\
                .format(_("Syntheses for team {team}.zip")
                        .format(team=str(request.user.team)).replace(" ", "%20"))
            return resp

        return super().post(request, *args, **kwargs)

    def get_queryset(self):
        qs = super().get_queryset().filter(team=self.request.user.team)
        return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram',
                           'round', 'source',)

    def get_context_data(self, **kwargs):
        self.object_list = self.get_queryset()
        context = super().get_context_data(**kwargs)
        context["now"] = timezone.now()
        context["real_deadline_1"] = self.request.user.team.future_tournament.date_syntheses + timedelta(minutes=30)
        context["real_deadline_2"] = self.request.user.team.future_tournament.date_syntheses_2 + timedelta(minutes=30)
        return context

    def form_valid(self, form):
        synthesis = form.instance
        synthesis.team = self.request.user.team
        synthesis.final = synthesis.team.selected_for_final

        if synthesis.round == '1' and timezone.now() > (synthesis.tournament.date_syntheses + timedelta(minutes=30)):
            form.add_error('file', _("You can't publish your synthesis anymore for the first round."
                                     " Deadline: {date:%m-%d-%Y %H:%M}.")
                           .format(date=timezone.localtime(synthesis.tournament.date_syntheses)))
            return super().form_invalid(form)

        if synthesis.round == '2' and timezone.now() > synthesis.tournament.date_syntheses_2 + timedelta(minutes=30):
            form.add_error('file', _("You can't publish your synthesis anymore for the second round."
                                     " Deadline: {date:%m-%d-%Y %H:%M}.")
                           .format(date=timezone.localtime(synthesis.tournament.date_syntheses_2)))
            return super().form_invalid(form)

        prev_syn = Synthesis.objects.filter(team=synthesis.team, round=synthesis.round, source=synthesis.source,
                                            final=synthesis.final)
        for syn in prev_syn.all():
            syn.delete()
        alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
        id = ""
        for i in range(64):
            id += random.choice(alphabet)
        synthesis.file.name = id
        synthesis.save()

        return super().form_valid(form)

    def get_success_url(self):
        return reverse_lazy("tournament:syntheses")


class SynthesesOrgaListView(OrgaMixin, SingleTableView):
    model = Synthesis
    table_class = SynthesisTable
    template_name = "tournament/syntheses_orga_list.html"
    extra_context = dict(title=_("All syntheses"))

    def post(self, request, *args, **kwargs):
        if "tournament_zip" in request.POST:
            tournament = Tournament.objects.get(pk=request.POST["tournament_zip"])
            syntheses = tournament.syntheses
            if not request.user.admin and request.user not in tournament.organizers.all():
                raise PermissionDenied

            out = BytesIO()
            zf = zipfile.ZipFile(out, "w")

            for synthesis in syntheses:
                zf.write(synthesis.file.path, str(synthesis) + ".pdf")

            zf.close()

            resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
            resp['Content-Disposition'] = 'attachment; filename={}'\
                .format(_("Syntheses for tournament {tournament}.zip")
                        .format(tournament=str(tournament)).replace(" ", "%20"))
            return resp

        return self.get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        context["tournaments"] = \
            Tournament.objects if self.request.user.admin else self.request.user.organized_tournaments

        return context

    def get_queryset(self):
        qs = super().get_queryset()
        if not self.request.user.admin:
            qs = qs.filter(Q(team__tournament__organizers=self.request.user) | Q(team__pools__juries=self.request.user))
        return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram',
                           'round', 'source',)


class PoolListView(LoginRequiredMixin, SingleTableView):
    model = Pool
    table_class = PoolTable
    extra_context = dict(title=_("Pools"))

    def get_queryset(self):
        qs = super().get_queryset()
        user = self.request.user
        if not user.admin and user.organizes:
            qs = qs.filter(Q(juries=user) | Q(teams__tournament__organizers=user))
        elif user.participates:
            qs = qs.filter(teams=user.team)
        qs = qs.distinct().order_by('solutions__final', 'teams__tournament__date_start', 'teams__tournament__name',
                                    'round',)
        return qs


class PoolCreateView(AdminMixin, CreateView):
    model = Pool
    form_class = PoolForm
    extra_context = dict(title=_("Create pool"))

    def get_success_url(self):
        return reverse_lazy("tournament:pools")


class PoolDetailView(LoginRequiredMixin, DetailView):
    model = Pool
    extra_context = dict(title=_("Pool detail"))

    def get_queryset(self):
        qs = super().get_queryset()
        user = self.request.user
        if not user.admin and user.organizes:
            qs = qs.filter(Q(juries=user) | Q(teams__tournament__organizers=user))
        elif user.participates:
            qs = qs.filter(teams=user.team)
        return qs.distinct()

    def post(self, request, *args, **kwargs):
        user = request.user
        pool = self.get_object()

        if "solutions_zip" in request.POST:
            if user.participates and pool.round == 2 and pool.tournament.date_solutions_2 > timezone.now():
                raise PermissionDenied

            out = BytesIO()
            zf = zipfile.ZipFile(out, "w")

            for solution in pool.solutions.all():
                zf.write(solution.file.path, str(solution) + ".pdf")

            zf.close()

            resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
            resp['Content-Disposition'] = 'attachment; filename={}' \
                .format(_("Solutions of a pool.zip").replace(" ", "%20"))
            return resp
        elif "syntheses_zip" in request.POST and user.organizes:
            if user.participates and pool.round == 2 and pool.tournament.date_solutions_2 > timezone.now():
                raise PermissionDenied

            out = BytesIO()
            zf = zipfile.ZipFile(out, "w")

            for synthesis in pool.syntheses.all():
                zf.write(synthesis.file.path, str(synthesis) + ".pdf")

            zf.close()

            resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
            resp['Content-Disposition'] = 'attachment; filename={}' \
                .format(_("Syntheses of a pool.zip").replace(" ", "%20"))
            return resp

        return self.get(request, *args, **kwargs)