import uuid

from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import timezone


class HistoryQuerySet(models.QuerySet):
    def delete(self):
        raise ValidationError("Communication history cannot be deleted.")

    def update(self, **kwargs):
        raise ValidationError("Use communication services.")

    def bulk_create(self, *args, **kwargs):
        raise ValidationError("Use communication services.")

    def bulk_update(self, *args, **kwargs):
        raise ValidationError("Use communication services.")


class Record(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created_at = models.DateTimeField(default=timezone.now, editable=False)
    objects = HistoryQuerySet.as_manager()

    class Meta:
        abstract = True
        ordering = ["created_at", "pk"]

    def save(self, *args, **kwargs):
        raise ValidationError("Use communication services.")

    def delete(self, *args, **kwargs):
        raise ValidationError("Communication history cannot be deleted.")

    def _persist(self):
        self.full_clean()
        super().save(using="default", force_insert=self._state.adding)


CHANNELS = [("SMS", "SMS"), ("EMAIL", "Email")]


class NotificationTemplate(Record):
    company = models.ForeignKey("organization.Company", on_delete=models.PROTECT)
    code = models.SlugField(max_length=64)
    name = models.CharField(max_length=150)
    channel = models.CharField(max_length=10, choices=CHANNELS)
    subject = models.CharField(max_length=200, blank=True)
    body = models.TextField(max_length=4000)
    is_active = models.BooleanField(default=True)
    updated_at = models.DateTimeField(default=timezone.now)
    updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)

    class Meta(Record.Meta):
        default_permissions = ()
        permissions = [("manage_templates", "Manage company notification templates")]
        constraints = [models.UniqueConstraint(fields=["company", "code", "channel"], name="communication_template_unique"),
            models.CheckConstraint(condition=models.Q(channel__in=["SMS", "EMAIL"]), name="communication_template_channel")]

    def clean(self):
        from .rendering import validate_template
        validate_template(self.subject, self.body, self.channel)

    def __str__(self):
        return f"{self.code} / {self.channel}"


class Notification(Record):
    company = models.ForeignKey("organization.Company", on_delete=models.PROTECT)
    service_center = models.ForeignKey("organization.ServiceCenter", null=True, blank=True, on_delete=models.PROTECT)
    customer = models.ForeignKey("customers.Customer", on_delete=models.PROTECT)
    template = models.ForeignKey(NotificationTemplate, on_delete=models.PROTECT)
    template_code = models.CharField(max_length=64)
    channel = models.CharField(max_length=10, choices=CHANNELS)
    destination = models.CharField(max_length=254)
    subject = models.CharField(max_length=200, blank=True)
    body = models.TextField(max_length=4000)
    event = models.CharField(max_length=40)
    related_id = models.UUIDField(null=True, blank=True)
    related_reference = models.CharField(max_length=150, blank=True)
    idempotency_key = models.CharField(max_length=64)
    requested_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="+")
    status = models.CharField(max_length=10, default="PENDING", choices=[(x, x.title()) for x in
        ["PENDING", "SENDING", "SENT", "FAILED", "UNKNOWN", "CANCELLED"]])
    sent_at = models.DateTimeField(null=True, blank=True)
    cancelled_at = models.DateTimeField(null=True, blank=True)
    cancelled_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.PROTECT, related_name="+")

    class Meta(Record.Meta):
        default_permissions = ()
        permissions = [("view_notification", "View communication history"), ("send_notification", "Request, send, retry or cancel notifications")]
        constraints = [
            models.UniqueConstraint(fields=["company", "idempotency_key"], name="communication_event_unique"),
            models.CheckConstraint(condition=models.Q(channel__in=["SMS", "EMAIL"]), name="communication_channel_valid"),
            models.CheckConstraint(condition=(models.Q(status="SENT", sent_at__isnull=False, cancelled_at=None, cancelled_by=None)
                | models.Q(status="CANCELLED", sent_at=None, cancelled_at__isnull=False, cancelled_by__isnull=False)
                | models.Q(status__in=["PENDING", "SENDING", "FAILED", "UNKNOWN"], sent_at=None, cancelled_at=None, cancelled_by=None)),
                name="communication_status_valid"),
        ]

    @property
    def masked_destination(self):
        return "***" + self.destination[-3:]

    def clean(self):
        if self.customer.company_id != self.company_id or self.template.company_id != self.company_id:
            raise ValidationError("Communication references must belong to one company.")
        if self.service_center_id and self.service_center.company_id != self.company_id:
            raise ValidationError("Communication center belongs to another company.")
        previous = type(self).objects.filter(pk=self.pk).first()
        immutable = ("company_id", "service_center_id", "customer_id", "template_id", "template_code", "channel",
                     "destination", "subject", "body", "event", "related_id", "related_reference", "idempotency_key",
                     "requested_by_id", "created_at")
        if previous and any(getattr(previous, name) != getattr(self, name) for name in immutable):
            raise ValidationError("Notification snapshots are immutable.")


class NotificationAttempt(Record):
    notification = models.ForeignKey(Notification, on_delete=models.PROTECT, related_name="attempts")
    number = models.PositiveIntegerField()
    provider = models.CharField(max_length=64)
    provider_reference = models.CharField(max_length=128, blank=True)
    actor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
    finished_at = models.DateTimeField(null=True, blank=True)
    result = models.CharField(max_length=10, default="STARTED", choices=[(x, x.title()) for x in ["STARTED", "ACCEPTED", "REJECTED", "UNKNOWN"]])
    error_code = models.CharField(max_length=40, blank=True)

    class Meta(Record.Meta):
        default_permissions = ()
        constraints = [models.UniqueConstraint(fields=["notification", "number"], name="communication_attempt_unique"),
            models.CheckConstraint(condition=models.Q(number__gte=1), name="communication_attempt_positive"),
            models.CheckConstraint(condition=(models.Q(result="STARTED", finished_at=None)
                | models.Q(result__in=["ACCEPTED", "REJECTED", "UNKNOWN"], finished_at__isnull=False)), name="communication_attempt_result")]

    def _persist(self):
        if not self._state.adding and type(self).objects.filter(pk=self.pk).exclude(result="STARTED").exists():
            raise ValidationError("Finished delivery attempts are immutable.")
        super()._persist()
