"""Diagnostic sessions and findings; no repair execution or invented causes."""
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 .models import IntakeRecord, NO_CREDENTIALS


class ServiceDiagnosticAssessment(IntakeRecord):
    service_case = models.ForeignKey("service.ServiceCase", on_delete=models.PROTECT, related_name="diagnostic_assessments")
    engineer_assignment = models.ForeignKey("service.ServiceEngineerAssignment", on_delete=models.PROTECT, related_name="diagnostic_assessments")
    started_at = models.DateTimeField(default=timezone.now)
    started_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="started_diagnoses")
    completed_at = models.DateTimeField(null=True, blank=True)
    completed_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.PROTECT, related_name="completed_diagnoses")
    abandoned_at = models.DateTimeField(null=True, blank=True)
    abandoned_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.PROTECT, related_name="abandoned_diagnoses")
    abandon_reason = models.CharField(max_length=2000, blank=True, help_text=NO_CREDENTIALS)
    technical_note = models.TextField(max_length=4000, blank=True, validators=[MaxLengthValidator(4000)], help_text=NO_CREDENTIALS)
    immutable_fields = ("service_case_id", "engineer_assignment_id", "started_at", "started_by_id")

    class Meta(IntakeRecord.Meta):
        ordering = ["started_at", "id"]
        constraints = [
            models.UniqueConstraint(fields=["service_case"], condition=models.Q(completed_at__isnull=True, abandoned_at__isnull=True), name="service_one_open_assessment"),
            models.CheckConstraint(condition=(
                models.Q(completed_at__isnull=True, completed_by__isnull=True) |
                models.Q(completed_at__isnull=False, completed_by__isnull=False, completed_at__gte=models.F("started_at"))
            ), name="service_diagnosis_completion"),
            models.CheckConstraint(condition=(
                models.Q(abandoned_at__isnull=True, abandoned_by__isnull=True, abandon_reason="") |
                (models.Q(abandoned_at__isnull=False, abandoned_by__isnull=False, abandoned_at__gte=models.F("started_at")) & ~models.Q(abandon_reason=""))
            ), name="service_diagnosis_abandonment"),
            models.CheckConstraint(condition=models.Q(completed_at__isnull=True) | models.Q(abandoned_at__isnull=True), name="service_diagnosis_one_outcome"),
        ]

    @property
    def is_open(self):
        return self.completed_at is None and self.abandoned_at is None

    def clean(self):
        super().clean()
        from .models import ServiceEngineerAssignment
        if not ServiceEngineerAssignment.objects.filter(pk=self.engineer_assignment_id, service_case_id=self.service_case_id).exists():
            raise ValidationError("Assessment must reference an assignment on its own case.")
        for value in (self.started_at, self.completed_at, self.abandoned_at):
            if value is not None and (not isinstance(value, datetime) or timezone.is_naive(value) or value > timezone.now()):
                raise ValidationError("Diagnosis times must be aware and no later than now.")
        previous = type(self).objects.filter(pk=self.pk).first()
        if previous and not previous.is_open and any(getattr(previous, field) != getattr(self, field) for field in
            ("completed_at", "completed_by_id", "abandoned_at", "abandoned_by_id", "abandon_reason", "technical_note")):
            raise ValidationError("Completed or abandoned diagnosis is immutable.")


class ServiceDiagnosticFinding(IntakeRecord):
    assessment = models.ForeignKey(ServiceDiagnosticAssessment, on_delete=models.PROTECT, related_name="findings")
    fault_diagnosis = models.ForeignKey("service_catalog.FaultDiagnosis", on_delete=models.PROTECT, related_name="diagnostic_findings")
    root_cause = models.ForeignKey("service_catalog.RootCause", null=True, blank=True, on_delete=models.PROTECT, related_name="diagnostic_findings", help_text="Leave empty when the cause is unknown or unconfirmed.")
    note = models.TextField(max_length=2000, blank=True, validators=[MaxLengthValidator(2000)], help_text=NO_CREDENTIALS)
    removed_at = models.DateTimeField(null=True, blank=True)
    immutable_fields = ("assessment_id",)

    class Meta(IntakeRecord.Meta):
        constraints = [models.UniqueConstraint(fields=["assessment", "fault_diagnosis", "root_cause"],
            condition=models.Q(removed_at__isnull=True), nulls_distinct=False, name="service_diagnostic_finding_unique")]

    def clean(self):
        super().clean()
        if ServiceDiagnosticAssessment.objects.filter(pk=self.assessment_id).exclude(completed_at=None, abandoned_at=None).exists():
            raise ValidationError("Historical findings cannot change.")
        previous = type(self).objects.filter(pk=self.pk).first()
        if previous and previous.removed_at is not None and any(getattr(previous, field) != getattr(self, field) for field in
            ("fault_diagnosis_id", "root_cause_id", "note", "removed_at")):
            raise ValidationError("Removed findings cannot change or be restored.")
