import uuid
import qrcode
import pytz

from io import BytesIO
from django.core.files.base import ContentFile

from django.db import models
from django.db.models import Sum, Count
from django.db import transaction

from django.contrib.auth import get_user_model # If used custom user model

from .constants import ESCOLARIDADE, TIPOSANGUINEO

#Obtem a classe usada para usuarios:
UserModel = get_user_model()

class PapelInstituicao(models.Model):
    descricao = models.CharField(max_length=50)

    class Meta:
        verbose_name = 'Papel da Instituição'
        verbose_name_plural = 'Papeis da Instituição'

    def __str__(self):
        return self.descricao


class Instituicao(models.Model):
    papel = models.ForeignKey(PapelInstituicao)
    nome = models.CharField(max_length=50)
    cnpj = models.CharField(max_length=13)
    e_mail = models.CharField(max_length=50)
    telefone = models.CharField(max_length=30)
    representante = models.CharField(max_length=50)

    class Meta:
        verbose_name = 'Instituição'
        verbose_name_plural = 'Instituições'
        ordering = ['papel', 'nome']

    def __str__(self):
        return '%s - %s' % (self.papel.descricao, self.nome)

class TipoTarefa(models.Model):
    descricao = models.CharField(max_length=50)
    data_encerramento = models.DateTimeField(verbose_name='Data de Encerramento', blank=True, null=True)

    class Meta:
        verbose_name = 'Tipo de Tarefa'
        verbose_name_plural = 'Tipos de Tarefas'

    def __str__(self):
        return self.descricao


class Arena(models.Model):
    nome = models.CharField(max_length=50)
    responsavel = models.CharField(max_length=50)

    class Meta:
        verbose_name = 'Arena'
        verbose_name_plural = 'Arenas'

    def __str__(self):
        return self.nome


class Banda(models.Model):
    nome = models.CharField(max_length=50)

    class Meta:
        verbose_name = 'Banda'
        verbose_name_plural = 'Bandas'

    def __str__(self):
        return self.nome


class TarefaMaxOcorrenciaAtingido(Exception):
    """Indica que o número máximo de ocorrências de uma tarefa foi atingida para um determinado participante"""

class TarefaNaoHabilitadaParticipante(Exception):
    """Indica que a tarefa não está habilitada para um determinado participante"""

class TarefaDataFimExpirada(Exception):
    """Indica que a tarefa não está habilitada pois a data de fim já foi atingida. """


class Tarefa(models.Model):
    codigo_simples = models.CharField(max_length=4, unique=True, verbose_name='# Tarefa', null=True)
    arena = models.ForeignKey(Arena, blank=False, null=True)
    tipo = models.ForeignKey(TipoTarefa, blank=False, null=True)
    nome = models.CharField(max_length=100)
    descricao = models.TextField(verbose_name='Descrição')
    responsavel = models.CharField(max_length=50, verbose_name='Responsável')
    pontuacao = models.PositiveSmallIntegerField(verbose_name='Pontuação')
    ocorrencia_maxima = models.PositiveSmallIntegerField(verbose_name='Ocorrências',
                                                 help_text='Número máximo de ocorrências')

    conquista = models.BooleanField()

    banda = models.ForeignKey(Banda, blank=True, null=True)

    data_inicio = models.DateTimeField(verbose_name='Data de Início', blank=True, null=True)
    data_fim = models.DateTimeField(verbose_name='Data de Fim', blank=True, null=True)
    instrutor = models.CharField(max_length=50, blank=True, null=True, verbose_name='Instrutor/Palestrante')

    codigo = models.UUIDField(default=uuid.uuid4, editable=False)

    carga_horaria = models.DecimalField(max_digits=4, decimal_places=1, default=0.0, blank=False, null=False)

    exclusividades = models.ManyToManyField(PapelInstituicao, blank=True,
                                            help_text='A tarefa se aplica exclusivamente aos papeis instituicionais')

    qrcode = models.ImageField(upload_to='qrcodes',verbose_name='Imagem QR Code', blank=True, null=True)

    tarefa_cadastro = models.BooleanField(default=False, verbose_name='Tarefa de Cadastro do Participante')

    def qrcode_tag(self):
        if self.qrcode:
            return u'<img src="%s" />' % self.qrcode.url

        return ''
    qrcode_tag.short_description = 'QR Code'
    qrcode_tag.allow_tags = True

    def gerar_qrcode(self):
        if self.codigo:
            #Gera o QR Code:
            imagem = qrcode.make(self.codigo)

            #Transforma em bytes para aplicar ao campo do django:
            imagem_io = BytesIO()
            imagem.save(imagem_io, format='png')

            #salva o qrcode gerado no model/sistema de arquivos:
            self.qrcode.save(str(self.id)+'_qr.png', ContentFile(imagem_io.getvalue()),save=False)
            # print('gerar_qrcode...')


    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
        super(Tarefa, self).save()

        #Gera o QR Code apenas se o mesmo não existe.
        if not self.qrcode:
            self.gerar_qrcode()
            super(Tarefa, self).save()

        if not self.codigo_simples:
            self.codigo_simples = '{0:03d}'.format(self.id)
            super(Tarefa, self).save()

    class Meta:
        verbose_name = 'Tarefa'
        verbose_name_plural = 'Tarefas'
        ordering = ('nome',)

    def __str__(self):
        return  '%s - #%s' % (self.nome, self.codigo_simples)

from datetime import datetime

class Participante(models.Model):
    nome = models.CharField(max_length=50)
    e_mail = models.EmailField(max_length=50, unique=True)
    data_nascimento = models.DateField(verbose_name='Data de Nascimento', blank=True, null=True)
    telefone = models.CharField(max_length=30,  blank=True, null=True)
    escolaridade = models.SmallIntegerField(choices=ESCOLARIDADE.choices, blank=True, null=True)
    tipo_sanguineo = models.SmallIntegerField(verbose_name='Tipo Sanguíneo', choices=TIPOSANGUINEO.choices, blank=True, null=True)
    telefone_emergencia = models.CharField(verbose_name='Telefone para Emergências', max_length=30,  blank=True, null=True)
    pessoa_emergencia = models.CharField('Pessoa para Emergências', max_length=30,  blank=True, null=True)
    tarefas = models.ManyToManyField(Tarefa, through='Resultado')
    vinculos = models.ManyToManyField(Instituicao, through='Vinculo')

    user = models.OneToOneField(UserModel, blank=True, null=True)


    def contabilizar_tarefa(self, codigo_tarefa, user_responsavel=None):
        tarefa = Tarefa.objects.filter(codigo=codigo_tarefa).first()

        if self.id and tarefa:
            if tarefa.id in self.tarefas_habilitadas():

                # Se a data de fim estiver preenchida compara com a data atual, senão libera a tarefa:
                data_agora = datetime.now(pytz.UTC)
                data_limite = tarefa.tipo.data_encerramento

                if data_limite is None or data_limite.astimezone(pytz.UTC) >= data_agora:
                    # contar resultados existentes para a tarefa atual
                    resultados_existentes = self.tarefas.filter(codigo=tarefa.codigo)

                    # criar novo resultado apenas se o limite não foi atingido.
                    if resultados_existentes.count() < tarefa.ocorrencia_maxima:
                        resultado = Resultado.objects.create(participante=self, tarefa=tarefa, user=user_responsavel)
                        return tarefa.pontuacao
                    else:
                        raise TarefaMaxOcorrenciaAtingido
                else:
                    raise TarefaDataFimExpirada
            else:
                raise TarefaNaoHabilitadaParticipante
        else:
            raise Tarefa.DoesNotExist("Tarefa não encontrada!")

        return 0

    def pontos_obtidos(self):
        """ Total de pontos obtidos até o momento em todas as tarefas que executou """
        pontos = self.tarefas.filter(registro_ok=True).aggregate(Sum('pontuacao'))['pontuacao__sum']
        if pontos is None:
            pontos = 0

        return pontos

    def execucoes_tarefa(self, codigo_tarefa):
        """ Número de vezes que o participante executou uma determinada tarefa """
        return self.tarefas.filter(registro_ok=True, codigo=codigo_tarefa).aggregate(Count('codigo'))['codigo__count']

    def tarefas_habilitadas(self):
        #Apenas tarefas exclusivas para o participante com base em seus vínculos:
        papeis = self.vinculos.values_list('papel', flat=True)
        tarefas_exclusivas = list(Tarefa.objects.filter(exclusividades__id__in=papeis).values_list('id', flat=True))

        #Tarefas que não possuem exclusividade (válido para todos os participantes):
        tarefas_sem_exclusividade = list(Tarefa.objects.filter(exclusividades__id=None).values_list('id', flat=True))

        tarefas_participante = list(set(tarefas_exclusivas + tarefas_sem_exclusividade))

        return tarefas_participante

    class Meta:
        verbose_name = 'Participante'
        verbose_name_plural = 'Participantes'
        ordering = ('nome',)

    def __str__(self):
        return '%s' % (self.nome)


class Resultado(models.Model):
    participante = models.ForeignKey(Participante)
    tarefa = models.ForeignKey(Tarefa)
    registro = models.DateTimeField(auto_now_add=True)
    user = models.ForeignKey(UserModel, null=True, verbose_name='Responsável')
    registro_ok = models.BooleanField(null=False, blank=False, default=True, verbose_name='Registro Ok')

    class Meta:
        verbose_name = 'Resultado'
        verbose_name_plural = 'Resultados'

    def __str__(self):
        return '%s - %s' %(self.participante.nome, self.tarefa.nome)


class ExecucaoTarefa(Resultado):

    class Meta:
        proxy = True
        verbose_name = 'Execução de Tarefa'
        verbose_name_plural = 'Execuções de Tarefas'


class ParticipanteApoio(Participante):

    class Meta:
        proxy = True
        verbose_name = 'Participante (Apoio UCL Go)'
        verbose_name_plural = 'Participantes (Apoio UCL Go)'

    def __str__(self):
        return '%s - %s' % (self.nome, self.e_mail)


class UsuarioApoio(UserModel):

    class Meta:
        proxy = True
        verbose_name = 'Usuário (Apoio UCL Go)'
        verbose_name_plural = 'Usuários (Apoio UCL Go)'

    def __str__(self):
        return self.username


class Vinculo(models.Model):
    participante = models.ForeignKey(Participante)
    instituicao = models.ForeignKey(Instituicao, verbose_name='Instituição')

    # Mantido apenas por questões de compatibilidade com a API/Apps. Não está sendo atualizado:
    total_pontos = models.IntegerField(verbose_name='Total de Pontos', default=0)

    class Meta:
        verbose_name = 'Vínculo'
        verbose_name_plural = 'Vínculos'
        unique_together = ['participante', 'instituicao']

    def __str__(self):
        return '%s - %s' %(self.participante.nome, self.instituicao.nome)


class Estado(models.Model):
    nome = models.CharField(max_length=50)
    sigla = models.CharField(max_length=2, verbose_name='UF')

    class Meta:
        verbose_name = 'Estado'
        verbose_name_plural = 'Estados'

    def __str__(self):
        return self.sigla


class Cidade(models.Model):
    nome = models.CharField(max_length=50)
    estado = models.ForeignKey(Estado)

    class Meta:
        verbose_name = 'Cidade'
        verbose_name_plural = 'Cidades'

    def __str__(self):
        return self.nome


class Endereco(models.Model):
    participante = models.ForeignKey(Participante)
    logradouro = models.CharField(max_length=100)
    complemento = models.CharField(max_length=50, blank=True, null=True)
    cidade = models.ForeignKey(Cidade)
    cep = models.CharField(max_length=8, verbose_name='CEP')

    class Meta:
        verbose_name = 'Endereço'
        verbose_name_plural = 'Endereços'

    def __str__(self):
        return '%s - %s - %s' %(self.logradouro, self.cidade, str(self.cidade.estado))



