import logging
import multiprocessing
import threading
import traceback
from datetime import datetime

from django.contrib import messages
from django.core.cache import cache
from django.db import transaction
from django.db.models import F
from django.shortcuts import HttpResponseRedirect, reverse, get_object_or_404
from django.views.generic import FormView
from django.views.generic import View
from django.views.generic.list import ListView
from django_redis import get_redis_connection

from dispatcher.manage import update_token, list_spj, upload_spj
from dispatcher.models import Server, ServerProblemStatus
from dispatcher.semaphore import Semaphore
from polygon.rejudge import rejudge_submission_set
from problem.models import Problem, SpecialProgram
from problem.tasks import upload_problem_to_judge_server
from submission.models import Submission
from submission.util import SubmissionStatus
from .forms import ServerEditForm, ServerUpdateTokenForm
from ..base_views import BaseCreateView, BaseUpdateView, BaseBackstageMixin

logger = logging.getLogger(__name__)


class ServerCreate(BaseCreateView):
  form_class = ServerEditForm
  template_name = 'backstage/server/server_add.jinja2'

  def get_redirect_url(self, instance):
    return reverse('backstage:server')


class ServerUpdate(BaseUpdateView):
  form_class = ServerEditForm
  template_name = 'backstage/server/server_edit.jinja2'
  queryset = Server.objects.all()

  def get_redirect_url(self, instance):
    return reverse('backstage:server')


class ServerList(BaseBackstageMixin, ListView):
  template_name = 'backstage/server/server.jinja2'
  queryset = Server.objects.all().order_by("-last_seen_time")
  context_object_name = 'server_list'

  def get_context_data(self, **kwargs):  # pylint: disable=arguments-differ
    data = super(ServerList, self).get_context_data(**kwargs)
    redis_server = get_redis_connection("judge")
    sem = Semaphore(redis_server)
    sem.exists_or_init()
    try:
      data['semaphore_available_count'] = sem.available_count
      data['semaphore_available_keys'] = redis_server.lrange(sem.available_key, 0,
                                                             sem.available_count)  # 1 more actually
      data['semaphore_available_keys'] = list(map(lambda s: s.decode(), data['semaphore_available_keys']))
      data['semaphore_grabbed_keys'] = {}
      for key, tt in redis_server.hgetall(sem.grabbed_key).items():
        data['semaphore_grabbed_keys'][key.decode()] = sem.current_time - float(tt.decode())
      data['server_synchronize_status_detail'] = cache.get('server_synchronize_status_detail', '')
      data['server_synchronize_status'] = cache.get('server_synchronize_status', 0)
      data['semaphore_ok'] = True
    except:
      pass

    data['crashed_submission_count'] = Submission.objects.filter(status=SubmissionStatus.SYSTEM_ERROR).count()
    return data


class ServerRefresh(BaseBackstageMixin, View):

  def post(self, request, pk):
    server = Server.objects.get(pk=pk)
    server.serverproblemstatus_set.all().delete()
    messages.success(request, "Server status has been refreshed.")
    return HttpResponseRedirect(reverse('backstage:server'))


class ServerEnableOrDisable(BaseBackstageMixin, View):

  def post(self, request, pk):
    server = Server.objects.get(pk=pk)
    server.enabled = not server.enabled
    server.save(update_fields=['enabled'])
    try:
      Semaphore(get_redis_connection("judge")).reset()
    except:
      pass
    return HttpResponseRedirect(reverse('backstage:server'))


class ServerDelete(BaseBackstageMixin, View):

  def post(self, request, pk):
    # server = Server.objects.get(pk=pk)
    # server.delete()
    # messages.success(request, "Server <strong>%s</strong> is successfully removed." % server.name)
    return HttpResponseRedirect(reverse('backstage:server'))


class ServerUpdateToken(BaseBackstageMixin, FormView):
  form_class = ServerUpdateTokenForm
  template_name = 'backstage/server/server_edit.jinja2'

  def post(self, request, *args, **kwargs):
    form = self.get_form()
    if form.is_valid():
      server = Server.objects.get(pk=kwargs.get('pk'))
      if update_token(server, form.cleaned_data['new_password']):
        messages.success(request, 'Token update succeeded.')
        return HttpResponseRedirect(reverse('backstage:server'))
    messages.error(request, 'Update token failed. Please recheck your server status.')
    return HttpResponseRedirect(reverse('backstage:server'))


def sync_and_update_status(server, problem):
  status, _ = ServerProblemStatus.objects.get_or_create(server=server, problem=problem)
  try:
    upload_problem_to_judge_server(problem, server)
    status.last_status = ''
  except:
    status.last_status = traceback.format_exc()
  status.save()