import os
import enum
import hashlib
from django.db import models
from django.core.exceptions import ValidationError
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from . import utils


class Esic(models.Model):
    url = models.URLField(unique=True)
    username = models.CharField(max_length=255, blank=True)
    # NOTE: This `username` will be extracted to its own class later, whenever
    # we have multiple accounts per eSIC. Meanwhile, we'll keep it here for
    # simplicity. This is the username in the eSIC system, for systems that
    # have logins.
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.url


class PublicBody(models.Model):
    LEVELS = (
        ('Local', 'Municipal'),
        ('State', 'Estadual'),
        ('Federal', 'Federal'),
    )

    UFS = (
        ('AC', 'Acre'),
        ('AL', 'Alagoas'),
        ('AM', 'Amazonas'),
        ('AP', 'Amapá'),
        ('BA', 'Bahia'),
        ('CE', 'Ceará'),
        ('DF', 'Distrito Federal'),
        ('ES', 'Espírito Santo'),
        ('GO', 'Goiás'),
        ('MA', 'Maranhão'),
        ('MG', 'Minas Gerais'),
        ('MS', 'Mato Grosso do Sul'),
        ('MT', 'Mato Grosso'),
        ('PA', 'Pará'),
        ('PB', 'Paraíba'),
        ('PE', 'Pernambuco'),
        ('PI', 'Piauí'),
        ('PR', 'Paraná'),
        ('RJ', 'Rio de Janeiro'),
        ('RN', 'Rio Grande do Norte'),
        ('RO', 'Rondônia'),
        ('RR', 'Roraima'),
        ('RS', 'Rio Grande do Sul'),
        ('SC', 'Santa Catarina'),
        ('SE', 'Sergipe'),
        ('SP', 'São Paulo'),
        ('TO', 'Tocantins'),
    )

    # NOTE: We might add a "parent_public_body" attribute later to deal with
    # cases like Secretaria de Meio-Ambiente de São Paulo, whose parent would
    # be the Prefeitura de São Paulo. If the time comes, We can get a very
    # similar relationship by looking for the PublicBodies that are in the
    # same Esic system. This is a pretty clear relationship.
    esic = models.ForeignKey(Esic, null=True, blank=True, on_delete=models.PROTECT)
    name = models.CharField(max_length=255, blank=False, unique=True)
    level = models.CharField(max_length=10, choices=LEVELS, default='Local')
    municipality = models.CharField(max_length=255, blank=True)
    uf = models.CharField(max_length=2, choices=UFS, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name

    def clean(self, *args, **kwargs):
        error_data = {}

        if self.level == 'Local':
            if not self.uf:
                error_data['uf'] = _('Local Public Bodies must have a "UF".')
            if not self.municipality:
                error_data['municipality'] = _('Local Public Bodies must have a "municipality".')
        elif self.level == 'State':
            if not self.uf:
                error_data['uf'] = _('State Public Bodies must have a "UF".')
            if self.municipality:
                msg = _('State Public Bodies must not have a "municipality".')
                error_data['municipality'] = msg
        elif self.level == 'Federal':
            if self.uf:
                error_data['uf'] = _('Federal Public Bodies must not have a "UF".')
            if self.municipality:
                msg = _('Federal Public Bodies must not have a "municipality".')
                error_data['municipality'] = msg

        if error_data:
            raise ValidationError(error_data)

        return super(PublicBody, self).clean(*args, **kwargs)


class FOIRequest(models.Model):
    class STATUS(enum.Enum):
        delayed = _('Delayed')
        finished = _('Finished')
        waiting_government = _('Waiting for government reply')
        waiting_user = _('Waiting for user reply')

    REPLY_DAYS = 20  # Public body has to answer in X days
    APPEAL_DAYS = 10  # Citizen can appeal in X days

    protocol = models.CharField(
        max_length=8,
        unique=True,
        default=utils.generate_protocol
    )
    esic_protocol = models.CharField(max_length=255, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    can_publish = models.BooleanField(default=True)

    class Meta:
        ordering = ['-created_at']

    def __init__(self, *args, **kwargs):
        super(FOIRequest, self).__init__(*args, **kwargs)
        self._original_protocol = self.protocol

    def save(self, *args, **kwargs):
        self.clean()
        return super(FOIRequest, self).save(*args, **kwargs)

    def clean(self, *args, **kwargs):
        if self._original_protocol != self.protocol:
            raise ValidationError(
                {'protocol': _('Protocol can not be changed.')}
            )
        return super(FOIRequest, self).clean(*args, **kwargs)

    def get_absolute_url(self):
        from django.urls import reverse
        return reverse('foirequest_detail', args=[self.protocol])

    def __str__(self):
        return self.protocol

    @property
    def public_body(self):
        if self.first_message:
            return self.first_message.receiver

    @property
    def esic(self):
        if self.public_body:
            return self.public_body.esic

    @property
    def summary(self):
        if self.first_message:
            return self.first_message.summary

    @property
    def status(self):
        last_message = self.last_message
        status = None

        if not last_message:
            status = self.STATUS.waiting_user
        elif not last_message.is_from_user:
            appeal_deadline = timezone.now() - timezone.timedelta(days=self.APPEAL_DAYS)
            if last_message.sent_at <= appeal_deadline:
                status = self.STATUS.finished
            else:
                status = self.STATUS.waiting_user
            pass
        elif last_message.is_sent:
            reply_deadline = timezone.now() - timezone.timedelta(days=self.REPLY_DAYS)
            if last_message.sent_at <= reply_deadline:
                status = self.STATUS.delayed
            else:
                status = self.STATUS.waiting_government
        else:
            status = last_message.status

        return status

    @property
    def moderation_message(self):
        first_message = self.first_message
        if first_message:
            return first_message.moderation_message

    @property
    def first_message(self):
        return self.message_set.order_by('created_at').first()

    @property
    def last_message(self):
        return self.message_set.order_by('created_at').last()


class Message(models.Model):
    class STATUS(enum.Enum):
        pending = _('Pending moderation')
        rejected = _('Rejected')
        ready = _('Ready to be sent')
        sent = _('Sent')

    foi_request = models.ForeignKey(FOIRequest, on_delete=models.CASCADE)
    sender = models.ForeignKey(
        PublicBody,
        null=True,
        blank=True,
        related_name='messages_sent',
        on_delete=models.PROTECT
    )
    receiver = models.ForeignKey(
        PublicBody,
        null=True,
        blank=True,
        related_name='messages_received',
        on_delete=models.PROTECT
    )
    summary = models.TextField(blank=True)
    body = models.TextField(blank=False)
    sent_at = models.DateTimeField(null=True, blank=True, verbose_name="Sent date")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    # Moderation-related attributes
    moderation_status = models.NullBooleanField(
        choices=(
            (None, 'Pending'),
            (True, 'Approved'),
            (False, 'Rejected'),
        )
    )
    moderation_message = models.TextField(blank=True)
    moderated_at = models.DateTimeField(null=True, blank=True)

    @property
    def status(self):
        status = None

        if self.is_pending_moderation:
            status = self.STATUS.pending
        elif self.is_rejected:
            status = self.STATUS.rejected
        elif not self.is_sent:
            status = self.STATUS.ready
        else:
            status = self.STATUS.sent

        return status

    @property
    def sender_type(self):
        sender_type = 'user'
        if self.sender is not None:
            sender_type = 'government'
        return sender_type

    def __str__(self):
        summary = self.summary
        if not summary:
            summary = self.body[0:100]

        return '(%s) %s' % (self.sender_type, summary)

    def _attached_file_path(self, filename):
        root, ext = os.path.splitext(filename)
        hash_size = 24

        hasher = hashlib.sha256()
        hasher.update(root.encode('utf-8'))
        hashed_filename = '{}{}'.format(hasher.hexdigest()[:hash_size], ext)

        return hashed_filename

    attached_file = models.FileField(
        upload_to=_attached_file_path,
        blank=True,
        null=True
    )

    @property
    def is_from_user(self):
        return self.sender is None

    @property
    def is_approved(self):
        return self.moderation_status is True

    @property
    def is_rejected(self):
        return self.moderation_status is False

    @property
    def is_pending_moderation(self):
        return self.moderation_status is None

    @property
    def is_sent(self):
        return self.sent_at is not None

    class Meta:
        ordering = ['-created_at', '-moderation_status']

    def __init__(self, *args, **kwargs):
        super(Message, self).__init__(*args, **kwargs)
        self._original_moderation_status = self.moderation_status

    def save(self, *args, **kwargs):
        self.clean()
        return super(Message, self).save(*args, **kwargs)

    def clean(self):
        self._update_moderated_at_if_needed()

        if self.sender and self.receiver:
            msg = _('Message can either have a "sender" or a "receiver", not both.')
            raise ValidationError({
                'sender': msg,
                'receiver': msg,
            })

        if not self.is_from_user:
            # Government messages are automatically approved
            if not self.moderation_status:
                self.approve()
            if not self.sent_at:
                raise ValidationError({
                    'sent_at': _('Government messages must have a "sent_at" date.'),
                })

        if self.is_from_user and not self.is_approved:
            if self.sent_at is not None:
                raise ValidationError({
                    'sent_at': _('Only approved user messages can be marked as sent.'),
                })

        if self.is_rejected and not self.moderation_message:
            raise ValidationError({
                'moderation_status': _('A message can not be rejected without an explanation.'),  # noqa: E501
            })

    def approve(self):
        self.moderated_at = timezone.now()
        self.moderation_status = True

    def reject(self):
        self.moderated_at = timezone.now()
        self.moderation_status = False

    def get_absolute_url(self):
        return self.foi_request.get_absolute_url()

    def _update_moderated_at_if_needed(self):
        if self._original_moderation_status != self.moderation_status:
            self.moderated_at = timezone.now()
            self._original_moderation_status = self.moderation_status