"""Front-desk history; all operational writes go through services."""
import uuid

from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import timezone

from apps.organization.model_base import TimeStampedModel


class HistoryQuerySet(models.QuerySet):
    def delete(self):
        raise ValidationError("Front-desk history cannot be deleted.")

    def update(self, **kwargs):
        raise ValidationError("Use front-desk services.")

    def bulk_create(self, *args, **kwargs):
        raise ValidationError("Use front-desk services.")

    def bulk_update(self, *args, **kwargs):
        raise ValidationError("Use front-desk services.")


class Record(TimeStampedModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    objects = HistoryQuerySet.as_manager()

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        raise ValidationError("Use front-desk services.")

    def delete(self, *args, **kwargs):
        raise ValidationError("Front-desk history cannot be deleted.")

    def _persist(self):
        self.full_clean()
        super().save(using="default", force_insert=self._state.adding)


class AppointmentSlot(Record):
    service_center = models.ForeignKey("organization.ServiceCenter", on_delete=models.PROTECT)
    date = models.DateField()
    start_time = models.TimeField()
    end_time = models.TimeField()
    capacity = models.PositiveIntegerField()
    is_active = models.BooleanField(default=True)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)

    class Meta:
        ordering = ["date", "start_time", "pk"]
        default_permissions = ()
        permissions = [("manage_slots", "Configure appointment availability")]
        constraints = [
            models.CheckConstraint(condition=models.Q(capacity__gte=1), name="frontdesk_capacity_positive"),
            models.CheckConstraint(condition=models.Q(end_time__gt=models.F("start_time")), name="frontdesk_slot_time_order"),
            models.UniqueConstraint(fields=["service_center", "date", "start_time"], name="frontdesk_slot_unique"),
        ]

    def __str__(self):
        return f"{self.date} {self.start_time:%H:%M}–{self.end_time:%H:%M} (capacity {self.capacity})"


class Appointment(Record):
    class Status(models.TextChoices):
        SCHEDULED = "SCHEDULED", "Scheduled"
        CHECKED_IN = "CHECKED_IN", "Checked in"
        COMPLETED = "COMPLETED", "Completed"
        CANCELLED = "CANCELLED", "Cancelled"
        NO_SHOW = "NO_SHOW", "No show"

    company = models.ForeignKey("organization.Company", on_delete=models.PROTECT)
    service_center = models.ForeignKey("organization.ServiceCenter", on_delete=models.PROTECT)
    slot = models.ForeignKey(AppointmentSlot, on_delete=models.PROTECT, related_name="appointments")
    customer = models.ForeignKey("customers.Customer", on_delete=models.PROTECT)
    device = models.ForeignKey("devices.Device", null=True, blank=True, on_delete=models.PROTECT)
    reason = models.CharField(max_length=2000, blank=True)
    channel = models.CharField(max_length=10, choices=[("PHONE", "Phone"), ("DESK", "Front desk"), ("OTHER", "Other")])
    status = models.CharField(max_length=10, choices=Status.choices, default=Status.SCHEDULED)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
    checked_in_at = models.DateTimeField(null=True, blank=True)
    ended_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ["slot__date", "slot__start_time", "created_at", "pk"]
        default_permissions = ()
        permissions = [("view_appointment", "View appointments"), ("create_appointment", "Book appointments"),
                       ("cancel_appointment", "Cancel appointments or record no show"),
                       ("check_in_appointment", "Check in appointments")]
        constraints = [models.CheckConstraint(condition=(
            models.Q(status="SCHEDULED", checked_in_at=None, ended_at=None)
            | models.Q(status="CHECKED_IN", checked_in_at__isnull=False, ended_at=None)
            | models.Q(status="COMPLETED", checked_in_at__isnull=False, ended_at__isnull=False)
            | models.Q(status="CANCELLED", ended_at__isnull=False)
            | models.Q(status="NO_SHOW", checked_in_at=None, ended_at__isnull=False)
        ), name="frontdesk_appointment_state")]

    def clean(self):
        if (self.service_center.company_id != self.company_id or self.customer.company_id != self.company_id
                or self.slot.service_center_id != self.service_center_id):
            raise ValidationError("Appointment relationships must share the same company and center.")


class QueueSequence(models.Model):
    service_center = models.ForeignKey("organization.ServiceCenter", on_delete=models.PROTECT)
    business_date = models.DateField()
    next_value = models.PositiveBigIntegerField(default=1)

    class Meta:
        default_permissions = ()
        constraints = [
            models.UniqueConstraint(fields=["service_center", "business_date"], name="frontdesk_sequence_unique"),
            models.CheckConstraint(condition=models.Q(next_value__gte=1), name="frontdesk_sequence_positive"),
        ]


class QueueEntry(Record):
    class Status(models.TextChoices):
        WAITING = "WAITING", "Waiting"
        CALLED = "CALLED", "Called"
        SERVING = "SERVING", "Serving"
        COMPLETED = "COMPLETED", "Completed"
        CANCELLED = "CANCELLED", "Cancelled"

    company = models.ForeignKey("organization.Company", on_delete=models.PROTECT)
    service_center = models.ForeignKey("organization.ServiceCenter", on_delete=models.PROTECT)
    business_date = models.DateField()
    token = models.PositiveBigIntegerField()
    customer = models.ForeignKey("customers.Customer", on_delete=models.PROTECT)
    device = models.ForeignKey("devices.Device", null=True, blank=True, on_delete=models.PROTECT)
    appointment = models.OneToOneField(Appointment, null=True, blank=True, on_delete=models.PROTECT, related_name="queue_entry")
    service_case = models.OneToOneField("service.ServiceCase", null=True, blank=True, on_delete=models.PROTECT, related_name="frontdesk_visit")
    arrived_at = models.DateTimeField(default=timezone.now)
    called_at = models.DateTimeField(null=True, blank=True)
    serving_at = models.DateTimeField(null=True, blank=True)
    ended_at = models.DateTimeField(null=True, blank=True)
    status = models.CharField(max_length=10, choices=Status.choices, default=Status.WAITING)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
    reason = models.CharField(max_length=2000, blank=True)

    class Meta:
        ordering = ["business_date", "token"]
        default_permissions = ()
        permissions = [("view_queue", "View front-desk queue"), ("manage_queue", "Manage front-desk arrivals and queue")]
        constraints = [
            models.UniqueConstraint(fields=["service_center", "business_date", "token"], name="frontdesk_token_unique"),
            models.CheckConstraint(condition=models.Q(token__gte=1), name="frontdesk_token_positive"),
            models.CheckConstraint(condition=(
                models.Q(status="WAITING", called_at=None, serving_at=None, ended_at=None)
                | models.Q(status="CALLED", called_at__isnull=False, serving_at=None, ended_at=None)
                | models.Q(status="SERVING", called_at__isnull=False, serving_at__isnull=False, ended_at=None)
                | models.Q(status="COMPLETED", called_at__isnull=False, serving_at__isnull=False, ended_at__isnull=False)
                | models.Q(status="CANCELLED", ended_at__isnull=False, service_case=None)
            ), name="frontdesk_queue_state"),
        ]

    def clean(self):
        if self.customer.company_id != self.company_id or self.service_center.company_id != self.company_id:
            raise ValidationError("Queue relationships must share a company.")
        if self.appointment_id and any(getattr(self.appointment, f) != getattr(self, f)
                                      for f in ("company_id", "service_center_id", "customer_id")):
            raise ValidationError("Queue must match its appointment.")
        if self.service_case_id and any(getattr(self.service_case, f) != getattr(self, f)
                                       for f in ("company_id", "service_center_id", "customer_id", "device_id")):
            raise ValidationError("Intake must match the queue visit.")


class FrontdeskEvent(Record):
    appointment = models.ForeignKey(Appointment, null=True, blank=True, on_delete=models.PROTECT, related_name="events")
    queue_entry = models.ForeignKey(QueueEntry, null=True, blank=True, on_delete=models.PROTECT, related_name="events")
    slot = models.ForeignKey(AppointmentSlot, null=True, blank=True, on_delete=models.PROTECT, related_name="events")
    actor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
    action = models.CharField(max_length=40)
    reason = models.CharField(max_length=2000, blank=True)
    occurred_at = models.DateTimeField(default=timezone.now)

    class Meta:
        default_permissions = ()
        ordering = ["occurred_at", "id"]

    def _persist(self):
        if not self._state.adding:
            raise ValidationError("Events are immutable.")
        super()._persist()
