# -*- coding: utf-8 -*-
import logging

from django.db import models
from django.utils import timezone
from django.conf import settings
from django.core.exceptions import ValidationError
from imagekit.models import ImageSpecField, ProcessedImageField
from imagekit.processors import ResizeToFit, Transpose

from django_countries.fields import CountryField

from reversion import revisions as reversion

from .base import SeminarModelBase

logger = logging.getLogger(__name__)


@reversion.register(ignore_duplicates=True)
class Osoba(SeminarModelBase):
	
	class Meta:
		db_table = 'seminar_osoby'
		verbose_name = 'Osoba'
		verbose_name_plural = 'Osoby'
		ordering = ['prijmeni','jmeno']
	
	id = models.AutoField(primary_key = True)

	jmeno = models.CharField('jméno', max_length=256)

	prijmeni = models.CharField('příjmení', max_length=256)

	prezdivka = models.CharField('přezdívka', blank=True, null=True, max_length=256)

	# User, pokud má na webu účet
	user = models.OneToOneField(settings.AUTH_USER_MODEL, blank=True, null=True, 
				verbose_name='uživatel', on_delete=models.DO_NOTHING)

	# Pohlaví. Že ho neznáme se snad nestane (a ušetří to práci při programování)
	pohlavi_muz = models.BooleanField('pohlaví (muž)', default=False)

	email = models.EmailField('e-mail', max_length=256, blank=True, default='')

	telefon = models.CharField('telefon', max_length=256, blank=True, default='')

	datum_narozeni = models.DateField('datum narození', blank=True, null=True)

	# NULL dokud nedali souhlas
	datum_souhlasu_udaje = models.DateField('datum souhlasu (údaje)', blank=True, null=True,
		help_text='Datum souhlasu se zpracováním osobních údajů')

	# NULL dokud nedali souhlas
	datum_souhlasu_zasilani = models.DateField('datum souhlasu (spam)', blank=True, null=True,
		help_text='Datum souhlasu se zasíláním MFF materiálů')

	# Alespoň odhad (rok či i měsíc)
	datum_registrace = models.DateField('datum registrace do semináře', default=timezone.now)

	# Ulice může být i jen číslo
	ulice = models.CharField('ulice', max_length=256, blank=True, default='')

	mesto = models.CharField('město', max_length=256, blank=True, default='')

	psc = models.CharField('PSČ', max_length=32, blank=True, default='')

	# ISO 3166-1 dvojznakovy kod zeme velkym pismem (CZ, SK)
	# Ekvivalentní s CharField(max_length=2, default='CZ', ...)
	stat = CountryField('stát', default='CZ',
		help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)')

	jak_se_dozvedeli = models.TextField('Jak se dozvěděli', blank=True)

	poznamka = models.TextField('neveřejná poznámka', blank=True,
		help_text='Neveřejná poznámka k osobě (plain text)')

	foto = ProcessedImageField(verbose_name='Fotografie osoby',
			upload_to='image_osoby/velke/%Y/', null = True, blank = True,
			help_text = 'Vlož fotografii osoby o libovolné velikosti',
			processors=[
				Transpose(Transpose.AUTO),
				ResizeToFit(500, 500, upscale=False)
			],
			options={'quality': 95})
	foto_male = ImageSpecField(source='foto',
			processors=[
				ResizeToFit(200, 200, upscale=False)
			],
			options={'quality': 95})

	# má OneToOneField nejvýše s:
	# Resitel
	# Prijemce
	# Organizator

	def plne_jmeno(self):
		return '{} {}'.format(self.jmeno, self.prijmeni)

	def inicial_krestni(self):
		jmena = self.jmeno.split()
		return " ".join(['{}.'.format(jmeno[0]) for jmeno in jmena])

	def __str__(self):
		return self.plne_jmeno()

	# Overridujeme save Osoby, aby když si změní e-mail, aby se projevil i v
	# Userovi (a tak se dal poslat mail s resetem hesla)
	def save(self, *args, **kwargs):
		if self.user is not None:
			u = self.user
			# U svatého tučňáka, prosím ať tohle funguje.
			# (Takhle se kódit asi nemá...)
			u.email = self.email
			u.save()
		super().save()

#
# Mělo by být částečně vytaženo z Aesopa
# viz https://ovvp.mff.cuni.cz/wiki/aesop/export-skol.
#

@reversion.register(ignore_duplicates=True)
class Skola(SeminarModelBase):

	class Meta:
		db_table = 'seminar_skoly'
		verbose_name = 'Škola'
		verbose_name_plural = 'Školy'
		ordering = ['mesto', 'nazev']

	# Interní ID
	id = models.AutoField(primary_key = True)

	# Aesopi ID "izo:..." nebo "aesop:..."
	# NULL znamená v exportu do aesopa "ufo"
	aesop_id = models.CharField('Aesop ID', max_length=32, blank=True, default='',
		help_text='Aesopi ID typu "izo:..." nebo "aesop:..."')

	# IZO školy (jen české školy)
	izo = models.CharField('IZO', max_length=32, blank=True,
		help_text='IZO školy (jen české školy)')

	# Celý název školy
	nazev = models.CharField('název', max_length=256,
		help_text='Celý název školy')

	# Zkraceny nazev pro zobrazení ve výsledkovce, volitelné.
	# Není v Aesopovi, musíme vytvářet sami.
	kratky_nazev = models.CharField('zkrácený název', max_length=256, blank=True,
		help_text="Zkrácený název pro zobrazení ve výsledkovce")

	# Ulice může být jen číslo
	ulice = models.CharField('ulice', max_length=256)

	mesto = models.CharField('město', max_length=256)

	psc = models.CharField('PSČ', max_length=32)

	# ISO 3166-1 dvojznakovy kod zeme velkym pismem (CZ, SK)
	# Ekvivalentní s CharField(max_length=2, default='CZ', ...)
	stat = CountryField('stát', default='CZ',
		help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)')

	# Jaké vzdělání škpla poskytuje?
	je_zs = models.BooleanField('základní stupeň', default=True)
	je_ss = models.BooleanField('střední stupeň', default=True)

	poznamka = models.TextField('neveřejná poznámka', blank=True,
		help_text='Neveřejná poznámka ke škole (plain text)')
	
	kontaktni_osoba = models.ForeignKey(Osoba, verbose_name='Kontaktní osoba', 
			blank=True, null=True, on_delete=models.SET_NULL)

	def __str__(self):
		return '{}, {}, {}'.format(self.nazev, self.ulice, self.mesto)

class Prijemce(SeminarModelBase):
	class Meta:
		db_table = 'seminar_prijemce'
		verbose_name = 'příjemce'
		verbose_name_plural = 'příjemce'
	

	# Interní ID
	id = models.AutoField(primary_key = True)

	poznamka = models.TextField('neveřejná poznámka', blank=True,
		help_text='Neveřejná poznámka k příemci čísel (plain text)')

	osoba = models.OneToOneField(Osoba, verbose_name='komu', blank=False, null=False,
		help_text='Které osobě či na jakou adresu se mají zasílat čísla',
		on_delete=models.CASCADE)

	# FIXME: možná chceme něco jako vazbu na osobu XOR školu a počet kusů k zaslání
	# FIXME: a možná taky posílání na mail a možná taky přes něj chceme posílat i řešitelům

	def __str__(self):
		return self.osoba.plne_jmeno()
	

@reversion.register(ignore_duplicates=True)
class Resitel(SeminarModelBase):

	class Meta:
		db_table = 'seminar_resitele'
		verbose_name = 'Řešitel'
		verbose_name_plural = 'Řešitelé'
		ordering = ['osoba']

	# Interní ID
	id = models.AutoField(primary_key = True)

	osoba = models.OneToOneField(Osoba, blank=False, null=False, verbose_name='osoba',
		on_delete=models.PROTECT)
	

	skola = models.ForeignKey(Skola, blank=True, null=True, verbose_name='škola',
		on_delete=models.SET_NULL)

	# Očekávaný rok maturity a vyřazení z aktivních řešitelů
	rok_maturity = models.IntegerField('rok maturity', blank=True, null=True)

	ZASILAT_DOMU = 'domu'
	ZASILAT_DO_SKOLY = 'do_skoly'
	ZASILAT_NIKAM = 'nikam'
	ZASILAT_CHOICES = [
		(ZASILAT_DOMU, 'Domů'),
		(ZASILAT_DO_SKOLY, 'Do školy'),
		(ZASILAT_NIKAM, 'Nikam'),
		]

	zasilat = models.CharField('kam zasílat', max_length=32, choices=ZASILAT_CHOICES, blank=False, default=ZASILAT_DOMU)

	zasilat_cislo_emailem = models.BooleanField('zasílat číslo emailem', help_text='True pokud chce řešitel dostávat číslo emailem', default=False)

	poznamka = models.TextField('neveřejná poznámka', blank=True,
		help_text='Neveřejná poznámka k řešiteli (plain text)')


	def export_row(self):
		"Slovnik pro pouziti v AESOP exportu"
		return {
			'id': self.id,
			'name': self.osoba.jmeno,
			'surname': self.osoba.prijmeni,
			'gender': 'M' if self.osoba.pohlavi_muz else 'F',
			'born': self.osoba.datum_narozeni.isoformat() if self.osoba.datum_narozeni else '',
			'email': self.osoba.email,
			'end-year': self.rok_maturity or '',

			'street': self.osoba.ulice,
			'town': self.osoba.mesto,
			'postcode': self.osoba.psc,
			'country': self.osoba.stat,

			'spam-flag': 'Y' if self.osoba.datum_souhlasu_zasilani else '',
			'spam-date': self.osoba.datum_souhlasu_zasilani.isoformat() if self.osoba.datum_souhlasu_zasilani else '',

			'school': self.skola.aesop_id if self.skola else '',
			'school-name': str(self.skola) if self.skola else 'Skola neni znama',
			}

	def rocnik(self, rocnik):
		"""Vrati skolni rocnik resitele pro zadany Rocnik.
				Vraci '' pro neznamy rok maturity resitele, Z* pro ekvivalent ZŠ."""
		if self.rok_maturity is None:
			return ''
		rozdil = 5 - (self.rok_maturity - rocnik.prvni_rok)
		if rozdil >= 1:
			return str(rozdil)
		else:
			return 'Z' + str(rozdil + 9)

	def vsechny_body(self):
		"Spočítá body odjakživa."
		vsechna_reseni = self.reseni_set.all()
		from .odevzdavatko import Hodnoceni
		vsechna_hodnoceni = Hodnoceni.objects.filter(
			reseni__in=vsechna_reseni)
		return sum(h.body for h in list(vsechna_hodnoceni) if h.body is not None)


	def get_titul(self, body=None):
		"Vrati titul jako řetězec."
		
		# Nejprve si zadefinujeme titul
		from enum import Enum
		from functools import total_ordering
		@total_ordering
		class Titul(Enum):
			""" Třída reprezentující možné tituly. Hodnoty jsou dvojice (dolní hranice, stringifikace). """
			nic =  (0, '')
			bc =   (20, 'Bc.')
			mgr =  (50, 'Mgr.')
			dr =   (100, 'Dr.')
			doc =  (200, 'Doc.')
			prof = (500, 'Prof.')
			akad = (1000, 'Akad.')

			def __lt__(self, other):
				return True if self.value[0] < other.value[0] else False
			def __eq__(self, other): # Měla by být implicitní, ale klidně explicitně.
				return True if self.value[0] == other.value[0] else False

			def __str__(self):
				return self.value[1]

			@classmethod
			def z_bodu(cls, body):
				aktualni = cls.nic
				# TODO: ověřit, že to funguje
				for titul in cls: # Kdyžtak použít __members__.items()
					if titul.value[0] <= body:
						aktualni = titul
					else:
						break
				return aktualni

		# Hledáme body v databázi
		# V listopadu 2020 jsme se na filosofické schůzce shodli o změně hranic titulů:
		#  - body z 25. ročníku a dříve byly shledány dvakrát hodnotnějšími
		#  - proto se započítávají dvojnásobně a byly posunuté hranice titulů
		#  - staré tituly se ale nemají odebrat, pokud řešitel v t.č. minulém (26.) ročníku měl titul, má ho mít pořád.
		from .odevzdavatko import Hodnoceni
		hodnoceni_do_25_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=25,reseni__in=self.reseni_set.all())
		novejsi_hodnoceni = Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()).difference(hodnoceni_do_25_rocniku)

		def body_z_hodnoceni(hh : list):
			return sum(h.body for h in hh if h.body is not None)

		stare_body = body_z_hodnoceni(hodnoceni_do_25_rocniku)
		if body is None:
			nove_body = body_z_hodnoceni(novejsi_hodnoceni)
		else:
			# Zjistíme, kolik bodů jsou staré, tedy hodnotnější
			nove_body = max(0, body - stare_body) # Všechny body nad počet původních hodnotnějších
			stare_body = min(stare_body, body) # Skutečný počet hodnotnějších bodů
		logicke_body = 2*stare_body + nove_body

	
		# Titul se určí následovně:
		#  - Pokud se řeší body, které jsou starší, než do 26 ročníku (včetně), dáváme tituly postaru.
		#  - Jinak dáváme tituly po novu...
		#  - ... ale titul se nesmí odebrat, pokud se zmenšil.
		def titul_do_26_rocniku(body):
			""" Původní hranice bodů za tituly """
			if body < 10:
				return Titul.nic
			elif body < 20:
				return Titul.bc
			elif body < 50:
				return Titul.mgr
			elif body < 100:
				return Titul.dr
			elif body < 200:
				return Titul.doc
			elif body < 500:
				return Titul.prof
			else:
				return Titul.akad

		from .odevzdavatko import Hodnoceni
		hodnoceni_do_26_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=26,reseni__in=self.reseni_set.all())
		novejsi_body = body_z_hodnoceni(
			Hodnoceni.objects.filter(reseni__in=self.reseni_set.all())
			.difference(hodnoceni_do_26_rocniku)
			)
		starsi_body = body_z_hodnoceni(hodnoceni_do_26_rocniku)
		if body is not None:
			# Ještě z toho vybereme ty správně staré body
			novejsi_body = max(0, body - starsi_body)
			starsi_body = min(starsi_body, body)

		# Titul pro 26. ročník
		stary_titul = titul_do_26_rocniku(starsi_body)
		# Titul podle aktuálních pravidel
		novy_titul = Titul.z_bodu(logicke_body)

		if novejsi_body == 0:
			# Žádné nové body -- titul podle starých pravidel
			return str(stary_titul)
		return str(max(novy_titul, stary_titul))


	def __str__(self):
		return self.osoba.plne_jmeno()


@reversion.register(ignore_duplicates=True)
class Organizator(SeminarModelBase):
	osoba = models.OneToOneField(Osoba, verbose_name='osoba', related_name='org',
		help_text='osobní údaje organizátora', null=False, blank=False,
		on_delete=models.PROTECT)

	vytvoreno = models.DateTimeField(
		'Vytvořeno',
		default=timezone.now,
		blank=True,
		editable=False
	)

	organizuje_od = models.DateTimeField('Organizuje od', blank=True, null=True)
	
	organizuje_do = models.DateTimeField('Organizuje do', blank=True, null=True)

	studuje = models.CharField('Studium aj.', max_length = 256,
			null = True, blank = True,
			help_text="Např. 'Studuje Obecnou fyziku (Bc.), 3. ročník', "
			"'Vystudovala Diskrétní modely a algoritmy (Mgr.)' nebo "
			"'Přednáší na MFF'")

	strucny_popis_organizatora = models.TextField('Stručný popis organizátora',
			null = True, blank = True)

	skola = models.CharField('Škola, kterou studuje', max_length = 256, null=True, blank=True,
		help_text="Škola, např. MFF, VŠCHT, VUT, ... prostě aby se nemuselo psát do studuje"
		"školu, ale jen obor, možnost zobrazit zvlášť")

	def clean(self):
		if self.organizuje_od and self.organizuje_do and (self.organizuje_od > self.organizuje_do):
			raise ValidationError("Organizátor nemůže skončit s organizováním dříve než začal!")
		super().clean()

	def __str__(self):
		if self.osoba.prezdivka:
			return "{} '{}' {}".format(self.osoba.jmeno,
				self.osoba.prezdivka,
				self.osoba.prijmeni)
		else:
			return "{} {}".format(self.osoba.jmeno, self.osoba.prijmeni)

	class Meta:
		verbose_name = 'Organizátor'
		verbose_name_plural = 'Organizátoři'
		# Řadí aktivní orgy na začátek, pod tím v pořadí od nejstarších neaktivní orgy.
		# TODO: Chtěl bych spíš mít nejstarší orgy dole.
		# TODO: Zohledňovat přezdívky?
		# TODO: Sjednotit s tím, jak se řadí organizátoři v seznau orgů na webu
		ordering = ['-organizuje_do', 'osoba__jmeno', 'osoba__prijmeni']