"""Service intake business history, separate from repair execution and ownership."""
import uuid
from datetime import datetime

from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import MaxLengthValidator
from django.db import models
from django.utils import timezone

from apps.organization.model_base import TimeStampedModel

NO_CREDENTIALS = "Do not record passwords, screen PINs, account credentials or OTPs."
MAX_JOB_NUMBER = 999999999999999999


class HistoryQuerySet(models.QuerySet):
    def delete(self):
        raise ValidationError("Service intake history cannot be deleted.")


class IntakeRecord(TimeStampedModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    objects = HistoryQuerySet.as_manager()
    immutable_fields = ()

    class Meta:
        abstract = True
        ordering = ["created_at", "id"]

    def clean_fields(self, exclude=None):
        for field in self._meta.concrete_fields:
            value = getattr(self, field.attname)
            if isinstance(field, (models.CharField, models.TextField)) and isinstance(value, str):
                setattr(self, field.attname, value.strip())
        super().clean_fields(exclude=exclude)

    def clean(self):
        super().clean()
        previous = type(self).objects.filter(pk=self.pk).first()
        if previous and any(getattr(previous, name) != getattr(self, name) for name in self.immutable_fields):
            raise ValidationError("Historical intake facts cannot change.")

    def _persist(self):
        self.full_clean()
        super().save(using="default", force_insert=self._state.adding)

    def save(self, *args, **kwargs):
        raise ValidationError("Use the service intake APIs.")

    def delete(self, *args, **kwargs):
        raise ValidationError("Service intake history cannot be deleted.")

    def __str__(self):
        return f"{self._meta.verbose_name} {self.pk}"


class ServiceCaseNumberSequence(models.Model):
    """Internal counter. The ServiceCenter row coordinates first allocation."""
    service_center = models.OneToOneField("organization.ServiceCenter", primary_key=True,
        on_delete=models.PROTECT, related_name="job_number_sequence")
    next_value = models.PositiveBigIntegerField(default=1)

    class Meta:
        constraints = [models.CheckConstraint(condition=models.Q(next_value__gte=1), name="service_sequence_positive")]


class ServiceCase(IntakeRecord):
    class Channel(models.TextChoices):
        WALK_IN = "WALK_IN", "Walk-in"
        COURIER = "COURIER", "Courier"
        PICKUP = "PICKUP", "Pickup"
        DEALER = "DEALER", "Dealer"
        OTHER = "OTHER", "Other"

    class Status(models.TextChoices):
        RECEIVED = "RECEIVED", "Received"
        ASSIGNED = "ASSIGNED", "Assigned"
        DIAGNOSING = "DIAGNOSING", "Diagnosing"
        DIAGNOSED = "DIAGNOSED", "Diagnosed"
        REPAIRING = "REPAIRING", "Repairing"
        REPAIRED = "REPAIRED", "Repaired"
        QC_PENDING = "QC_PENDING", "QC pending"
        QC_IN_PROGRESS = "QC_IN_PROGRESS", "QC in progress"
        QC_PASSED = "QC_PASSED", "QC passed"
        READY_FOR_DELIVERY = "READY_FOR_DELIVERY", "Ready for delivery"
        DELIVERED = "DELIVERED", "Delivered"
        CLOSED = "CLOSED", "Closed"
        CANCELLED = "CANCELLED", "Cancelled"

    company = models.ForeignKey("organization.Company", on_delete=models.PROTECT, related_name="service_cases")
    service_center = models.ForeignKey("organization.ServiceCenter", on_delete=models.PROTECT, related_name="service_cases")
    job_number = models.CharField(max_length=22, editable=False)
    customer = models.ForeignKey("customers.Customer", on_delete=models.PROTECT, related_name="service_cases")
    device = models.ForeignKey("devices.Device", on_delete=models.PROTECT, related_name="service_cases")
    intake_channel = models.CharField(max_length=8, choices=Channel.choices)
    received_at = models.DateTimeField(default=timezone.now)
    status = models.CharField(max_length=18, choices=Status.choices, default=Status.RECEIVED)
    customer_reference = models.CharField(max_length=200, blank=True)
    intake_note = models.TextField(max_length=4000, blank=True, help_text=NO_CREDENTIALS)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="created_service_cases")
    cancelled_at = models.DateTimeField(null=True, blank=True, editable=False)
    cancellation_reason = models.CharField(max_length=2000, blank=True, help_text=NO_CREDENTIALS)
    immutable_fields = ("company_id", "service_center_id", "job_number", "customer_id", "device_id", "created_by_id", "received_at", "intake_channel")

    class Meta(IntakeRecord.Meta):
        ordering = ["-received_at", "id"]
        permissions = [("handle_servicecase", "Can receive service case work"),
                       ("perform_quality_control", "Can perform independent quality control"),
                       ("handover_servicecase", "Can release and hand over service cases"),
                       ("close_servicecase", "Can close delivered service cases")]
        constraints = [
            models.UniqueConstraint(fields=["service_center", "job_number"], name="service_center_job_unique"),
            models.CheckConstraint(condition=models.Q(job_number__regex=r"^JOB-[0-9]{8,18}$"), name="service_job_format"),
            models.CheckConstraint(condition=models.Q(intake_channel__in=["WALK_IN", "COURIER", "PICKUP", "DEALER", "OTHER"]), name="service_channel_valid"),
            models.CheckConstraint(condition=models.Q(status__in=["RECEIVED", "ASSIGNED", "DIAGNOSING", "DIAGNOSED", "REPAIRING", "REPAIRED", "QC_PENDING", "QC_IN_PROGRESS", "QC_PASSED", "READY_FOR_DELIVERY", "DELIVERED", "CLOSED", "CANCELLED"]), name="service_status_valid"),
            models.CheckConstraint(condition=(models.Q(status__in=["RECEIVED", "ASSIGNED", "DIAGNOSING", "DIAGNOSED", "REPAIRING", "REPAIRED", "QC_PENDING", "QC_IN_PROGRESS", "QC_PASSED", "READY_FOR_DELIVERY", "DELIVERED", "CLOSED"], cancelled_at__isnull=True, cancellation_reason="") |
                models.Q(status="CANCELLED", cancelled_at__isnull=False, cancelled_at__gte=models.F("received_at"))), name="service_cancellation_coherent"),
        ]

    def __str__(self):
        return self.job_number

    def clean(self):
        super().clean()
        from apps.customers.models import Customer
        from apps.organization.models import ServiceCenter
        if not ServiceCenter.objects.filter(pk=self.service_center_id, company_id=self.company_id).exists():
            raise ValidationError("ServiceCenter must belong to the selected Company.")
        if not Customer.objects.filter(pk=self.customer_id, company_id=self.company_id).exists():
            raise ValidationError("Customer must belong to the selected Company.")
        if not isinstance(self.received_at, datetime) or timezone.is_naive(self.received_at) or self.received_at > timezone.now():
            raise ValidationError("Received time must be timezone-aware and no later than now.")
        previous = type(self).objects.filter(pk=self.pk).first()
        if previous and previous.status == self.Status.CANCELLED and any(
            getattr(previous, field) != getattr(self, field) for field in
            ("status", "cancelled_at", "cancellation_reason", "intake_note", "customer_reference")
        ):
            raise ValidationError("Cancelled intake is immutable and cannot be reopened.")


class ServiceEngineerAssignment(IntakeRecord):
    service_case = models.ForeignKey(ServiceCase, on_delete=models.PROTECT, related_name="engineer_assignments")
    engineer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="service_engineer_assignments")
    assigned_at = models.DateTimeField(default=timezone.now)
    assigned_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="assigned_service_work")
    ended_at = models.DateTimeField(null=True, blank=True)
    ended_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.PROTECT, related_name="ended_service_work")
    end_reason = models.CharField(max_length=2000, blank=True, help_text=NO_CREDENTIALS)
    note = models.TextField(max_length=2000, blank=True, validators=[MaxLengthValidator(2000)], help_text=NO_CREDENTIALS)
    immutable_fields = ("service_case_id", "engineer_id", "assigned_at", "assigned_by_id", "note")

    class Meta(IntakeRecord.Meta):
        ordering = ["assigned_at", "id"]
        constraints = [
            models.UniqueConstraint(fields=["service_case"], condition=models.Q(ended_at__isnull=True), name="service_one_current_engineer"),
            models.CheckConstraint(condition=(
                models.Q(ended_at__isnull=True, ended_by__isnull=True, end_reason="") |
                models.Q(ended_at__isnull=False, ended_by__isnull=False, ended_at__gte=models.F("assigned_at"))
            ), name="service_engineer_period_coherent"),
        ]

    def clean(self):
        super().clean()
        for value in (self.assigned_at, self.ended_at):
            if value is not None and (not isinstance(value, datetime) or timezone.is_naive(value) or value > timezone.now()):
                raise ValidationError("Assignment times must be aware and no later than now.")
        previous = type(self).objects.filter(pk=self.pk).first()
        if previous and previous.ended_at is not None and any(
            getattr(previous, field) != getattr(self, field) for field in ("ended_at", "ended_by_id", "end_reason")
        ):
            raise ValidationError("Closed engineer assignment history cannot change.")


class ServiceCaseComplaint(IntakeRecord):
    service_case = models.ForeignKey(ServiceCase, on_delete=models.PROTECT, related_name="complaints")
    complaint_symptom = models.ForeignKey("service_catalog.ComplaintSymptom", on_delete=models.PROTECT, related_name="service_case_complaints")
    customer_description = models.TextField(max_length=2000, blank=True, help_text=NO_CREDENTIALS)
    removed_at = models.DateTimeField(null=True, blank=True, editable=False)
    immutable_fields = ("service_case_id", "complaint_symptom_id", "customer_description")

    class Meta(IntakeRecord.Meta):
        constraints = [models.UniqueConstraint(fields=["service_case", "complaint_symptom"], name="service_case_complaint_unique")]

    def clean(self):
        super().clean()
        previous = type(self).objects.filter(pk=self.pk).first()
        if previous and previous.removed_at is not None and previous.removed_at != self.removed_at:
            raise ValidationError("Removed complaints cannot be restored or rewritten.")


class ServiceCaseIntakeCondition(IntakeRecord):
    class Type(models.TextChoices):
        COSMETIC = "COSMETIC", "Cosmetic"
        PHYSICAL_DAMAGE = "PHYSICAL_DAMAGE", "Physical damage"
        LIQUID_INDICATOR = "LIQUID_INDICATOR", "Liquid indicator"
        POWER_STATE = "POWER_STATE", "Power state"
        OTHER = "OTHER", "Other"

    service_case = models.ForeignKey(ServiceCase, on_delete=models.PROTECT, related_name="intake_conditions")
    condition_type = models.CharField(max_length=16, choices=Type.choices)
    description = models.TextField(max_length=2000, help_text=NO_CREDENTIALS)
    immutable_fields = ("service_case_id", "condition_type", "description")

    class Meta(IntakeRecord.Meta):
        constraints = [
            models.CheckConstraint(condition=models.Q(condition_type__in=["COSMETIC", "PHYSICAL_DAMAGE", "LIQUID_INDICATOR", "POWER_STATE", "OTHER"]), name="service_condition_type_valid"),
            models.CheckConstraint(condition=~models.Q(description=""), name="service_condition_description"),
        ]


class ServiceCaseAccessory(IntakeRecord):
    class Type(models.TextChoices):
        CHARGER = "CHARGER", "Charger"
        CABLE = "CABLE", "Cable"
        CASE = "CASE", "Case"
        SIM = "SIM", "SIM"
        MEMORY_CARD = "MEMORY_CARD", "Memory card"
        BOX = "BOX", "Box"
        OTHER = "OTHER", "Other"

    service_case = models.ForeignKey(ServiceCase, on_delete=models.PROTECT, related_name="accessories")
    accessory_type = models.CharField(max_length=11, choices=Type.choices)
    description = models.TextField(max_length=2000, blank=True, help_text=NO_CREDENTIALS)
    quantity = models.PositiveIntegerField(default=1)
    immutable_fields = ("service_case_id", "accessory_type", "description", "quantity")

    class Meta(IntakeRecord.Meta):
        constraints = [
            models.CheckConstraint(condition=models.Q(accessory_type__in=["CHARGER", "CABLE", "CASE", "SIM", "MEMORY_CARD", "BOX", "OTHER"]), name="service_accessory_type_valid"),
            models.CheckConstraint(condition=models.Q(quantity__gt=0), name="service_accessory_quantity_positive"),
        ]


class ServiceCaseWarrantySnapshot(IntakeRecord):
    service_case = models.OneToOneField(ServiceCase, on_delete=models.PROTECT, related_name="warranty_snapshot")
    recorded_coverage = models.BooleanField(default=False)
    coverage_source = models.CharField(max_length=19, null=True, blank=True)
    coverage_start_date = models.DateField(null=True, blank=True)
    coverage_end_date = models.DateField(null=True, blank=True)
    coverage_reference = models.CharField(max_length=200, null=True, blank=True)
    captured_at = models.DateTimeField(default=timezone.now, editable=False)
    immutable_fields = ("service_case_id", "recorded_coverage", "coverage_source", "coverage_start_date", "coverage_end_date", "coverage_reference", "captured_at")

    class Meta(IntakeRecord.Meta):
        constraints = [models.CheckConstraint(condition=(
            models.Q(recorded_coverage=False, coverage_source__isnull=True, coverage_start_date__isnull=True,
                     coverage_end_date__isnull=True, coverage_reference__isnull=True) |
            models.Q(recorded_coverage=True, coverage_source__in=["PURCHASE_EVIDENCE", "MANUFACTURER_RECORD", "MANUAL_OVERRIDE"],
                     coverage_source__isnull=False, coverage_start_date__isnull=False, coverage_end_date__isnull=False,
                     coverage_reference__isnull=False, coverage_end_date__gte=models.F("coverage_start_date"))
        ), name="service_snapshot_coherent")]

# Register the service-owned diagnostic models after their shared base.
from .diagnostic_models import ServiceDiagnosticAssessment, ServiceDiagnosticFinding  # noqa: E402,F401
from .repair_models import ServiceRepairExecution, ServiceRepairAction  # noqa: E402,F401

from .quality_control_models import ServiceQualityControl, ServiceQualityControlCheck, ServiceQualityControlComplaintCheck  # noqa: E402,F401

from .handover_models import ServiceCaseDeliveryRelease, ServiceCaseHandover, ServiceCaseHandoverAccessory, ServiceCaseClosure  # noqa: E402,F401
