import csv
import http
import logging

from django.http import HttpResponse, HttpRequest
from django.shortcuts import render, get_object_or_404
from django.views import generic
from django.shortcuts import HttpResponseRedirect
from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction

from various.views.pomocne import formularOKView
from .forms import HlasovaniPrednaskaFormSet, HlasovaniZnalostiFormSet

from various.models import Nastaveni
from prednasky.models import Prednaska, Hlasovani, Znalost, HlasovaniOZnalostech, Seznam
from soustredeni.models import Soustredeni
from personalni.models import Osoba

PREDNASKY_PREFIX = "prednasky"
ZNALOSTI_PREFIX = "znalosti"

logger = logging.getLogger(__name__)

def newPrednaska(request: HttpRequest) -> HttpResponse:
	"""
		View zobrazující a ukládající účastnické hlasování
		(:py:class:`Hlasování <prednasky.models.Hlasovani>`
		a :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>`)
		o :py:class:`Přednáškách <prednasky.models.Prednaska>`
		a :py:class:`Znalostech <prednasky.models.Znalost>`
	"""
	# hlasovani se vztahuje k nejnovejsimu soustredeni
	sous = Nastaveni.get_solo().aktualni_sous
	seznam = Seznam.objects.filter(soustredeni = sous, stav=Seznam.Stav.NAVRH).first()
	if sous is None or seznam is None:
		return render(request, 'universal.html', {
			'title': "Nelze hlasovat",
			'text': "Není žádný seznam přednášek, o kterém by se dalo hlasovat.",
		}, status=http.HTTPStatus.NOT_FOUND)

	osoba = Osoba.objects.filter(user=request.user).first()
	ucastnik = osoba.plne_jmeno() + ' ' + str(osoba.id) # id, kvůli kolizi jmen

	if request.method == 'POST': # Když to byl POST, tak ukládáme.
		# Načteme data do formsetů
		form_set_prednasky = HlasovaniPrednaskaFormSet(request.POST, prefix=PREDNASKY_PREFIX)
		form_set_znalosti = HlasovaniZnalostiFormSet(request.POST, prefix=ZNALOSTI_PREFIX)

		if form_set_prednasky.is_valid() and form_set_znalosti.is_valid():
			with transaction.atomic():
				# Místo updatování data prostě smažeme a vytvoříme nová
				seznam.hlasovani_set.filter(ucastnik=ucastnik).delete()
				seznam.hlasovanioznalostech_set.filter(ucastnik=osoba).delete()

				for form in form_set_prednasky:
					prednaska_id = form.cleaned_data['prednaska_id']
					prednaska = Prednaska.objects.filter(id=prednaska_id).first()
					if prednaska is None:
						logger.error(f"Účastník {ucastnik} hodnotil neexistující přednášku {prednaska_id} číslem {form.cleaned_data['body']}")
						continue

					Hlasovani.objects.create(
						prednaska=prednaska,
						body=form.cleaned_data['body'],
						ucastnik=ucastnik,
						ucastnik_osoba=osoba,
						seznam=seznam,
					)

				for form in form_set_znalosti:
					znalost_id = form.cleaned_data['znalost_id']
					znalost = Znalost.objects.filter(id=znalost_id).first()
					if znalost is None:
						logger.error(f"Účastník {ucastnik} hodnotil neexistující znalost {znalost_id} číslem {form.cleaned_data['odpoved']}")
						continue

					HlasovaniOZnalostech.objects.create(
						odpoved=form.cleaned_data['odpoved'],
						znalost=znalost,
						ucastnik=osoba,
						seznam=seznam,
					)

			return HttpResponseRedirect('./hotovo')

		else: # Pokud je nějaký formset nevalidní, vracíme je k přepracování
			prednasky = seznam.prednaska_set.all()
			znalosti = seznam.znalost_set.all()
			# FIXME Spadnout, pokud nesedí přednáška/znalost s formulářem. (Nějak se mi to nepovedlo.)
			# Může se totiž stát, že se mezitím změnily přednášky (nějaká byla přidána/odebrána)

	else: # Když to nebyl POST, tak inicializujeme (pokud už o přednášce/znalosti účastník hlasoval, předvyplníme mu to).
		def odpoved_prednasky(p: Prednaska) -> Hlasovani.Body:
			hlasovani = p.hlasovani_set.filter(ucastnik=ucastnik).first()
			return hlasovani.body if hlasovani else Hlasovani.Body.JEDNO

		def odpoved_znalosti(z: Znalost) -> HlasovaniOZnalostech.Odpoved:
			hlasovani = z.hlasovanioznalostech_set.filter(ucastnik=osoba).first()
			return hlasovani.odpoved if hlasovani else HlasovaniOZnalostech.Odpoved.CIRCA

		prednasky = seznam.prednaska_set.all()
		znalosti = seznam.znalost_set.all()

		form_set_prednasky = HlasovaniPrednaskaFormSet(initial=[
			{"prednaska_id": p.id, "body": odpoved_prednasky(p)} for p in prednasky
		], prefix=PREDNASKY_PREFIX)

		form_set_znalosti = HlasovaniZnalostiFormSet(initial=[
			{"znalost_id": z.id, "odpoved": odpoved_znalosti(z)} for z in znalosti
		], prefix=ZNALOSTI_PREFIX)


	# V případě nePOSTu nebo chyby při ukládání vracíme hlasování
	return render(
		request,
		'prednasky/base.html',
		{
			'form_set_prednasky': form_set_prednasky, 'form_set_znalosti': form_set_znalosti,
			'formy_a_prednasky': list(zip(form_set_prednasky, prednasky)),
			'formy_a_znalosti': list(zip(form_set_znalosti, znalosti)),
		}
	)


def Prednaska_hotovo(request: HttpRequest) -> HttpResponse:
	""" View po vyplnění :py:func:`hlasování <prednasky.views.newPrednaska>` """
	return formularOKView(request, "Děkujeme za vyplnění hlasování o přednáškách a těšíme se na soustředění.")

class MetaSeznamListView(generic.ListView):
	""" Seznam všech :py:class:`Seznamů <prednasky.models.Seznam>` s odkazy na exporty """
	model = Seznam
	template_name = 'prednasky/metaseznam_prednasek.html'


def PrednaskyExportView(request: HttpRequest, seznam: int, **kwargs) -> HttpResponse:
	"""
		Vrátí všechna :py:class:`Hlasování <prednasky.models.Hlasovani>`
		i :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>`
		v daném :py:class:`Seznamu <prednasky.models.Seznam>`
		jako csv soubor (řádky = účastníci, sloupce = přednášky&znalosti).

		:param seznam: ID daného :py:class:`Seznamu <prednasky.models.Seznam>`
	"""
	hlasovani = Hlasovani.objects.filter(seznam=seznam).select_related("prednaska")
	hlasovani_o_znalostech = HlasovaniOZnalostech.objects.filter(seznam=seznam).select_related('ucastnik', 'znalost')

	# Inicializujeme sloupce
	prednasky = list(Prednaska.objects.filter(seznamy=seznam))
	znalosti = list(Znalost.objects.filter(seznamy=seznam))

	prednasky_map: dict[int, int] = {p.id: i for i, p in enumerate(prednasky, 1)}
	offset = len(prednasky_map)
	znalosti_map: dict[int, int] = {z.id: i for i, z in enumerate(znalosti, offset + 1)}
	width = offset + len(znalosti_map)

	# A po inicializaci sloupců vyplníme tabulku
	table: [str, list[str|Prednaska|Znalost,]] = {}

	errors = []

	for h in hlasovani:
		if h.ucastnik not in table: # Pokud jsme účastníka ještě neviděli, předgenerujeme si jeho řádek
			table[h.ucastnik] = [h.ucastnik] + ([""] * width)

		if h.prednaska.id in prednasky_map:
			table[h.ucastnik][prednasky_map[h.prednaska.id]] = h.body
		else:
			errors.append(f"Přednáška {h.prednaska.id} ({h.prednaska}) dostala od Účastníka {h.ucastnik} následující hodnocení: {h.body}")

	for h in hlasovani_o_znalostech:
		ucastnik = str(h.ucastnik) + ' ' + str(h.ucastnik.id) # id, kvůli kolizi jmen
		if ucastnik not in table: # Pokud jsme účastníka ještě neviděli, předgenerujeme si jeho řádek
			table[ucastnik] = [ucastnik] + ([""] * width)

		if h.znalost.id in znalosti_map:
			table[ucastnik][znalosti_map[h.znalost.id]] = h.odpoved
		else:
			errors.append(f"Znalost {h.znalost.id} ({h.znalost}) dostala od Účastníka {h.ucastnik.id} následující odpověď: {h.odpoved}")

	if len(errors) > 0:
		logger.error("Při exportování hlasování o přednáškách a znalostech se neexportovali hodnocení a přednášky (pravděpodobně se od hlasování vyškrtla nějaká znalost/přednáška ze seznamu):\n" + "\n".join(errors))

	response = HttpResponse(content_type="text/csv", charset="utf-8")
	response["Content-Disposition"] = 'attachment; filename="hlasovani.csv"'

	writer = csv.writer(response)
	writer.writerow(["jména \\ přednáška|znalost"] + list(map(str, prednasky + znalosti)))
	for row in table.values():
		writer.writerow(list(map(str, row)))
	return response