import tempfile
import subprocess
import shutil
import http

from django.shortcuts import render
from django.urls import reverse
from django.views import generic
from django.db.models import Q, Count, Min
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic.base import TemplateView
from django.contrib.auth.models import User, Permission, Group, AnonymousUser
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.staticfiles.finders import find
from django.db import transaction
from django.http import HttpResponse
from django.utils import timezone


import personalni.models as m
from soustredeni.models import Soustredeni
from odevzdavatko.models import Hodnoceni
from tvorba.models import Clanek, Uloha, Tema
from various.models import Nastaveni
from .forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm

from datetime import date
import logging
import csv

from various.views.pomocne import formularOKView
from various.autentizace.views import LoginView
from various.autentizace.utils import posli_reset_hesla

from django.forms.models import model_to_dict

from .models import Organizator, Osoba 


def aktivniOrganizatori(datum=timezone.now()):
	return Organizator.objects.exclude(
		organizuje_do__isnull=False,
		organizuje_do__lt=datum
	).order_by('osoba__jmeno')


class CojemamOrganizatoriView(generic.ListView):
	model = Organizator
	template_name = 'personalni/organizatori.html'
	queryset = aktivniOrganizatori()

	def get_context_data(self, **kwargs):
		context = super(CojemamOrganizatoriView, self).get_context_data(**kwargs)
		context['aktivni'] = True
		return context


class CojemamOrganizatoriStariView(generic.ListView):
	model = Organizator
	template_name = 'personalni/organizatori.html'
	queryset = Organizator.objects.exclude(
		id__in=aktivniOrganizatori()
	).order_by('-organizuje_do')

class JakSeDozvedeliView(generic.ListView):
	model = Osoba
	template_name = 'personalni/jak_se_dozvedeli.html'
	queryset = Osoba.objects.order_by('-datum_registrace')


def obalkyView(request, resitele):
	if len(resitele) == 0:
		return HttpResponse(
			render(request, 'universal.html', {
				'title': 'Není pro koho vyrobit obálky.',
				'text': 'Právě ses pokusil/a vygenerovat obálky pro prázdnou množinu lidí. Můžeš to zkusit změnit, případně se zeptej webařů :-)',
			}),
			status=http.HTTPStatus.NOT_FOUND,
		)

	tex = render(request, 'personalni/obalky.tex', {
		'resitele': resitele
	}).content

	with tempfile.TemporaryDirectory() as tempdir:
		with open(tempdir+"/obalky.tex", "w") as texfile:
			texfile.write(tex.decode())
		shutil.copy(find('personalni/lisak.pdf'), tempdir)
		subprocess.call(["pdflatex", "obalky.tex"], cwd=tempdir)

		with open(tempdir+"/obalky.pdf", "rb") as pdffile:
			response = HttpResponse(pdffile.read(), content_type='application/pdf')
	return response


class OrgoRozcestnikView(TemplateView):
	""" Zobrazí organizátorský rozcestník."""

	template_name = 'personalni/profil/orgorozcestnik.html'

	def get_context_data(self, **kwargs):
		context = super().get_context_data(**kwargs)
		context['posledni_soustredeni'] = Soustredeni.objects.order_by('-datum_konce').first()
		nastaveni = Nastaveni.objects.first()
		aktualni_rocnik = nastaveni.aktualni_rocnik
		context['posledni_cislo_url'] = nastaveni.aktualni_cislo.verejne_url()
		# TODO možná chceme odkazovat na právě rozpracované číslo, a ne to poslední vydané
		# pokud nechceme haluzit kód (= poradi) dalšího čísla, bude asi potřeba jít
		# přes treenody (a dát si přitom pozor na MezicisloNode)

		neobodovana_reseni = Hodnoceni.objects.filter(body__isnull=True)
		reseni_mimo_cislo = Hodnoceni.objects.filter(deadline_body__isnull=True)
		context['pocet_neobodovanych_reseni'] = neobodovana_reseni.count()
		context['pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.count()

		u = self.request.user
		os = m.Osoba.objects.get(user=u)
		organizator = m.Organizator.objects.get(osoba=os)

		context['muj_pocet_neobodovanych_reseni'] = neobodovana_reseni.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).distinct().count()
		context['muj_pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).count()

		# Termínářský přehled
		pocty_neopravenych_reseni = neobodovana_reseni.select_related('problem').values('problem__nazev').annotate(cas=Min('reseni__cas_doruceni')).order_by('cas')
		context["pocty_neopravenych_reseni"] = [(it['problem__nazev'], it['cas'].date) for it in pocty_neopravenych_reseni.all()]

		#FIXME: přidat stav='STAV_ZADANY'
		temata = Tema.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
			rocnik=aktualni_rocnik).distinct()
		ulohy = Uloha.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
			cislo_zadani__rocnik=aktualni_rocnik).distinct()
		clanky = Clanek.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
			cislo__rocnik=aktualni_rocnik).distinct()

		context['temata'] = temata
		context['ulohy'] = ulohy
		context['clanky'] = clanky
		context['organizator'] = organizator
		return context

		#content_type = 'text/plain; charset=UTF8'
	#XXX


class ResitelView(LoginRequiredMixin,generic.DetailView):
	model = m.Resitel
	template_name = 'personalni/profil/resitel.html'

	def get_object(self, queryset=None):
		print(self.request.user)
		return m.Resitel.objects.get(osoba__user=self.request.user)

### Formulare

# pro přidání políčka do formuláře je potřeba
# - mít v modelu tu položku, kterou chci upravovat
# - přidat do views (prihlaskaView, resitelEditView)
# - přidat do forms
# - includovat do html

def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data):
	msg = "{}, form_hash:{}".format(msg,hash(frozenset(form_data.items)))
	logger.warn(msg)
	gdpr_logger.warn(msg+", form:{}".format(form_data))


@sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola')
def resitelEditView(request):
	err_logger = logging.getLogger('personalni.prihlaska.problem')
	## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli
	u = request.user
	osoba_edit = m.Osoba.objects.get(user=u)
	if hasattr(osoba_edit,'resitel'):
		resitel_edit = osoba_edit.resitel
	else:
		resitel_edit = None
	user_edit = osoba_edit.user
	## Vytvoření slovníku, kterým předvyplním formulář 
	prefill_1=model_to_dict(user_edit)
	if resitel_edit:
		prefill_2=model_to_dict(resitel_edit)
		prefill_1.update(prefill_2)
	prefill_3=model_to_dict(osoba_edit)
	prefill_1.update(prefill_3)
	if 'datum_narozeni' in prefill_1:
		prefill_1['datum_narozeni'] = str(prefill_1['datum_narozeni'])
	if 'datum_souhlasu_udaje' in prefill_1:
		prefill_1['spam'] = bool(prefill_1['datum_souhlasu_zasilani'])
	if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year:
		form = PoMaturiteProfileEditForm(initial=prefill_1)
	else:
		form = ProfileEditForm(initial=prefill_1)
	## Změna údajů a jejich uložení
	if request.method == 'POST':
		POST = request.POST.copy()
		POST["username"] = osoba_edit.user.username

		if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year:
			form = PoMaturiteProfileEditForm(POST)
		else:
			form = ProfileEditForm(POST)
		form.username = user_edit.username
		if form.is_valid():
			## Změny v osobě
			fcd = form.cleaned_data
			form_hash = hash(frozenset(fcd.items()))
			form_logger = logging.getLogger('personalni.prihlaska.form')
			form_logger.info("EDIT:" + str(fcd) + str(form_hash))  # TODO možná logovat jinak
			osoba_edit.jmeno = fcd['jmeno']
			osoba_edit.prijmeni = fcd['prijmeni']
			osoba_edit.osloveni = fcd['osloveni']
			osoba_edit.email = fcd['email']
			osoba_edit.telefon = fcd['telefon']
			osoba_edit.ulice = fcd['ulice']
			osoba_edit.mesto = fcd['mesto']
			osoba_edit.psc = fcd['psc']
			osoba_edit.datum_narozeni = fcd['datum_narozeni']
			## Změny v osobě s podmínkami
			if fcd.get('spam',False):
				if not osoba_edit.datum_souhlasu_zasilani:
					osoba_edit.datum_souhlasu_zasilani = date.today()
			else:
				osoba_edit.datum_souhlasu_zasilani = None
			if fcd.get('stat','') in ('CZ','SK'):
				osoba_edit.stat = fcd['stat']
			else:
				## Neznámá země
				msg = "Unknown country {}".format(fcd['stat_text'])

			if resitel_edit:
				## Změny v řešiteli
				resitel_edit.prezdivka_resitele = fcd['prezdivka_resitele'] if fcd['prezdivka_resitele'] != '' else None
				resitel_edit.skola = fcd['skola']
				resitel_edit.rok_maturity = fcd['rok_maturity']
				resitel_edit.zasilat = fcd['zasilat']
				resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
				resitel_edit.zasilat_cislo_papirove = fcd['zasilat_cislo_papirove']
				resitel_edit.upozornovat_na_opravy_reseni = fcd['upozornovat_na_opravy_reseni']
				if fcd.get('skola'):
					resitel_edit.skola = fcd['skola']
				else:
					# Unknown school - log it
					msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
				resitel_edit.save()
			osoba_edit.save()
			return formularOKView(
				request,
				text='Údaje byly úspěšně uloženy.',
				dalsi_odkazy=[("Vrátit se zpět na profil", reverse("profil"))],
			)

	return render(request, 'personalni/udaje/edit.html', {'form': form})


@sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola', 'jak_se_dozvedeli')
def prihlaskaView(request):
	generic_logger = logging.getLogger('personalni.prihlaska')
	err_logger = logging.getLogger('personalni.prihlaska.problem')
	form_logger = logging.getLogger('personalni.prihlaska.form')
	if request.method == 'POST':
		form = PrihlaskaForm(request.POST)
		# TODO vyresit, co se bude v jakych situacich zobrazovat
		if form.is_valid():
			generic_logger.info("Form valid")
			fcd = form.cleaned_data
			form_hash = hash(frozenset(fcd.items()))
			form_logger.info(str(fcd) + str(form_hash))  # TODO možná logovat jinak

			with transaction.atomic():
				u = User.objects.create_user(
					username=fcd['username'],
					email = fcd['email'])
				u.save()
				resitel_perm = Permission.objects.filter(codename__exact='resitel').first()
				u.user_permissions.add(resitel_perm)
				resitel_grp = Group.objects.filter(name__exact='resitel').first()
				u.groups.add(resitel_grp)

				o = m.Osoba(
					jmeno = fcd['jmeno'],
					prijmeni = fcd['prijmeni'],
					osloveni = fcd['osloveni'],
					email = fcd['email'],
					telefon = fcd.get('telefon',''),
					datum_narozeni = fcd.get('datum_narozeni',None),
					datum_souhlasu_udaje = date.today(),
					datum_registrace = date.today(),
					ulice = fcd.get('ulice',''),
					mesto = fcd.get('mesto',''),
					psc = fcd.get('psc',''),
					jak_se_dozvedeli = fcd.get('jak_se_dozvedeli',''),
					poznamka = str(fcd)
					)

				if fcd.get('spam',False):
					o.datum_souhlasu_zasilani = date.today()
				if fcd.get('stat','') in ('CZ','SK'):
					o.stat = fcd['stat']
				else:
					# Unknown country - log it
					msg = "Unknown country {}".format(fcd['stat_text'])
					err_logger.warn(msg + str(form_hash))

				
				# Dovolujeme doregistraci uživatele pro existující mail, takže naopak chceme doplnit/aktualizovat údaje do stávajícího objektu
				try:
					orig_osoba = m.Osoba.objects.get(email=fcd['email'])
					orig_osoba.poznamka += '\nDOREGISTRACE K EXISTUJÍCÍMU E-MAILU, diff níže.'
				except m.Osoba.DoesNotExist:
					# Trik: Budeme aktualizovat údaje nové osoby, takže se asi nic nezmění, ale fungovat to bude.
					orig_osoba = o

				# Porovnání údajů
				assert orig_osoba.user is None, "Právě-registrující-se osoba už má Uživatele!"
				osoba_attrs = ['jmeno', 'prijmeni', 'osloveni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'stat', 'datum_souhlasu_udaje', 'datum_souhlasu_zasilani', 'datum_registrace']
				diffattrs = []
				for attr in osoba_attrs:
					new = getattr(o, attr)
					old = getattr(orig_osoba, attr)
					if new != old:
						orig_osoba.poznamka += f'\nRozdíl v {attr}: Původní {old}, nový {new}'
						diffattrs.append(f'Osoba.{attr}')
						setattr(orig_osoba, attr, new)
				# Datum registrace chceme původní / nižší:
				orig_osoba.datum_registrace = min(orig_osoba.datum_registrace, o.datum_registrace)

				# Od této chvíle dál je správná osoba ta "původní", novou podle formuláře si ale zachováme
				o, o_form = orig_osoba, o



				o.save()
				o.user = u
				o.save()

				# Jednoduchá kvazi-kontrola duplicitních Osob
				kolize = m.Osoba.objects.filter(jmeno=o.jmeno, prijmeni=o.prijmeni)
				if kolize.count() > 1:	# Jednu z nich jsme právě uložili
					err_logger.warning(f'Zaregistrovala se osoba s kolizním jménem. ID osob: {[o.id for o in kolize]}')

				r = m.Resitel(
					prezdivka_resitele=fcd['prezdivka_resitele'] if fcd['prezdivka_resitele'] != "" else None,
					rok_maturity = fcd['rok_maturity'],
					zasilat = fcd['zasilat'],
					zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'],
					zasilat_cislo_papirove = fcd['zasilat_cislo_papirove'],
					)

				if fcd.get('skola'):
					r.skola = fcd['skola']
				else:
					# Unknown school - log it
					msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
					err_logger.warn(msg + str(form_hash))

				# Porovnání údajů u řešitele
				try:
					orig_resitel = o.resitel
					orig_resitel.poznamka += '\nDOREGISTRACE ŘEŠITELE, diff:'
				except m.Resitel.DoesNotExist:
					# Stejný trik:
					orig_resitel = r
				resitel_attrs = ['skola', 'rok_maturity', 'zasilat', 'zasilat_cislo_emailem', 'zasilat_cislo_papirove']
				for attr in resitel_attrs:
					new = getattr(r, attr)
					old = getattr(orig_resitel, attr)
					if new != old:
						orig_resitel.poznamka += f'\nRozdíl v {attr}: Původní {old}, nový {new}'
						diffattrs.append(f'Resitel.{attr}')
						setattr(orig_resitel, attr, new)
				r, r_form = orig_resitel, r

				r.osoba = o	# Tohle by mělo být bezpečné…
				r.save()

				if diffattrs: err_logger.warning(f'Different fields when matching Řešitel id {r.id} or Osoba id {o.id}: {diffattrs}')

			posli_reset_hesla(u, request)
			return formularOKView(request, text='Na tvůj e-mail jsme právě poslali odkaz pro nastavení hesla.')

	# if a GET (or any other method) we'll create a blank form
	else:
		form = PrihlaskaForm()

	return render(request, 'personalni/udaje/prihlaska.html', {'form': form})


# Jen hloupé rozhazovátko
def profilView(request):
	user = request.user
	if not isinstance(user, AnonymousUser) and m.Osoba.objects.filter(user=user).count() != 1:
		# m.Osoba.objects.get() v ostatních views selže
		return render(request, "universal.html", {
			'title': 'Krize identity.',
			'raw_html': r'<blockquote>Zvláštní pocit, že jo?<br>[…]<br>Co to znamená?<br>— Že ti MaMweb neumí říct, kdo jsi.<br>A <a href="/admin">Admin</a> ano?<br>— V tom je rozdíl.</blockquote> — Matrix (1999), parafrázováno',
			})
	if user.has_perm('auth.org'):
		return OrgoRozcestnikView.as_view()(request)
	if user.has_perm('auth.resitel'):
		return ResitelView.as_view()(request)
	else:
		return LoginView.as_view()(request)

def dataResiteluCsvResponse(queryset, columns=None, with_header=True):
	"""Pomocná funkce pro vracení dat řešitelů jako CSV. Musí dostat správný QuerySet, který dává Řešitele"""
	# TODO: Možná nějak zobecnit i na Osoby?
	# TODO: Nemá to spíš být class-based? Tohle je efektivně metoda "get", které ale chybí "get_queryset"…
	
	default_columns = (
		'id',
		'osoba__jmeno',
		'osoba__prijmeni',
		'osoba__prezdivka',
		'osoba__email',
		'osoba__telefon',
		'osoba__user__username',
		'osoba__datum_narozeni',
		'osoba__osloveni',
		'osoba__ulice',
		'osoba__mesto',
		'osoba__psc',
		'osoba__stat',
		'skola',	#FIXME: dává jen ID
		'osoba__jak_se_dozvedeli',
		'poznamka',
		'osoba__poznamka',
		'rok_maturity',
		'zasilat',
		'zasilat_cislo_emailem',
		'zasilat_cislo_papirove',
		'osoba__datum_registrace',
		'osoba__datum_souhlasu_udaje',
		'osoba__datum_souhlasu_zasilani',
		)
	if columns is None: columns = default_columns

	field_name_overrides = {
		# Zrušení prefixu "osoba__"
		'osoba__jmeno':                  'jmeno',
		'osoba__prijmeni':               'prijmeni',
		'osoba__prezdivka':              'prezdivka',
		'osoba__email':                  'email',
		'osoba__telefon':                'telefon',
		'osoba__user__username':         'user',
		'osoba__datum_narozeni':         'datum_narozeni',
		'osoba__osloveni':               'osloveni',
		'osoba__ulice':                  'ulice',
		'osoba__mesto':                  'mesto',
		'osoba__psc':                    'psc',
		'osoba__stat':                   'stat',
		'osoba__datum_registrace':       'datum_registrace',
		'osoba__datum_souhlasu_udaje':   'datum_souhlasu_udaje',
		'osoba__datum_souhlasu_zasilani':'datum_souhlasu_zasilani',
		}
	
	def get_field_name(column_name):
		if column_name in field_name_overrides:
			return field_name_overrides[column_name]
		return column_name
	
	response = HttpResponse(content_type='text/csv')
	writer = csv.writer(response)
	
	# První řádek je záhlaví
	if with_header:
		writer.writerow(map(get_field_name, columns))
	
	# Data:
	queryset_list = queryset.values_list(*columns)
	writer.writerows(queryset_list)

	return response