"""Separate global intake and technical vocabularies; no service transactions."""
import uuid

from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.db import models, router, transaction

from apps.catalog.models import ProductCategory
from apps.organization.model_base import TimeStampedModel

CODE_PATTERN = r"^[A-Z0-9][A-Z0-9_-]*$"


class TaxonomyQuerySet(models.QuerySet):
    def active(self):
        return self.filter(is_active=True)


class TaxonomyRecord(TimeStampedModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    code = models.CharField(max_length=64, validators=[RegexValidator(
        CODE_PATTERN, "Use letters, digits, hyphens or underscores; start with a letter or digit.")])
    name = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    is_active = models.BooleanField(default=True)
    objects = TaxonomyQuerySet.as_manager()

    class Meta:
        abstract = True
        ordering = ["code"]
        constraints = [models.CheckConstraint(condition=models.Q(code__regex=CODE_PATTERN),
            name="%(app_label)s_%(class)s_code_format")]

    def clean_fields(self, exclude=None):
        if isinstance(self.code, str):
            self.code = self.code.strip().upper()
        super().clean_fields(exclude=exclude)

    def save(self, *, force_insert=False, force_update=False, using=None, update_fields=None):
        using = using or router.db_for_write(type(self), instance=self)
        if update_fields is not None:
            update_fields = set(update_fields)
            if not update_fields:
                return
            update_fields.add("updated_at")
        with transaction.atomic(using=using):
            previous = type(self).objects.using(using).select_for_update().filter(pk=self.pk).first()
            self.full_clean()
            if previous is not None and update_fields is not None:
                for field in self._meta.concrete_fields:
                    if field.name in update_fields or field.attname in update_fields:
                        setattr(previous, field.attname, getattr(self, field.attname))
                previous.full_clean()
            return super().save(force_insert=force_insert, force_update=force_update,
                using=using, update_fields=update_fields)

    def __str__(self):
        return f"{self.code} - {self.name}"


class ServiceCategory(TaxonomyRecord):
    class Meta(TaxonomyRecord.Meta):
        abstract = False
        verbose_name_plural = "service categories"
        constraints = TaxonomyRecord.Meta.constraints + [
            models.UniqueConstraint(fields=["code"], name="service_category_code_uniq")]


class ComplaintSymptom(TaxonomyRecord):
    applies_to_all_product_categories = models.BooleanField(default=False)

    class Meta(TaxonomyRecord.Meta):
        abstract = False
        constraints = TaxonomyRecord.Meta.constraints + [
            models.UniqueConstraint(fields=["code"], name="service_complaint_code_uniq")]

    def clean(self):
        super().clean()
        if self.applies_to_all_product_categories and self.pk and self.category_mappings.exists():
            raise ValidationError({"applies_to_all_product_categories":
                "Global complaints cannot have explicit category mappings. Use the applicability service."})


class ComplaintSymptomProductCategory(TimeStampedModel):
    """Current applicability configuration; no independent activation lifecycle."""
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    complaint_symptom = models.ForeignKey(ComplaintSymptom, on_delete=models.PROTECT, related_name="category_mappings")
    product_category = models.ForeignKey(ProductCategory, on_delete=models.PROTECT, related_name="complaint_mappings")

    class Meta:
        ordering = ["complaint_symptom_id", "product_category_id"]
        constraints = [models.UniqueConstraint(fields=["complaint_symptom", "product_category"],
            name="service_complaint_category_uniq")]

    def clean(self):
        super().clean()
        previous = type(self).objects.filter(pk=self.pk).first()
        if previous and (previous.complaint_symptom_id != self.complaint_symptom_id or previous.product_category_id != self.product_category_id):
            raise ValidationError("Mapping endpoints cannot change. Replace applicability through the service.")
        if ComplaintSymptom.objects.filter(pk=self.complaint_symptom_id, applies_to_all_product_categories=True).exists():
            raise ValidationError({"complaint_symptom": "Global complaints cannot have explicit category mappings."})

    def save(self, *, force_insert=False, force_update=False, using=None, update_fields=None):
        using = using or router.db_for_write(type(self), instance=self)
        if update_fields is not None:
            update_fields = set(update_fields)
            if not update_fields:
                return
            update_fields.add("updated_at")
        with transaction.atomic(using=using):
            original = type(self).objects.using(using).filter(pk=self.pk).values_list("complaint_symptom_id", flat=True).first()
            ComplaintSymptom.objects.using(using).select_for_update().filter(pk=original or self.complaint_symptom_id).first()
            self.full_clean()
            return super().save(force_insert=force_insert, force_update=force_update,
                using=using, update_fields=update_fields)

    def __str__(self):
        return f"Complaint {self.complaint_symptom_id} / category {self.product_category_id}"


class TechnicalTaxonomyRecord(TaxonomyRecord):
    """Shared applicability validation for the three explicit technical masters."""
    applies_to_all_product_categories = models.BooleanField(default=False)

    class Meta(TaxonomyRecord.Meta):
        abstract = True

    def clean(self):
        super().clean()
        if self.applies_to_all_product_categories and self.pk and self.category_mappings.exists():
            raise ValidationError({"applies_to_all_product_categories":
                "Global records cannot have explicit category mappings. Use the applicability service."})


class FaultDiagnosis(TechnicalTaxonomyRecord):
    class Meta(TechnicalTaxonomyRecord.Meta):
        abstract = False
        verbose_name_plural = "fault diagnoses"
        constraints = TechnicalTaxonomyRecord.Meta.constraints + [
            models.UniqueConstraint(fields=["code"], name="service_fault_diagnosis_code_uniq")]


class RootCause(TechnicalTaxonomyRecord):
    class Meta(TechnicalTaxonomyRecord.Meta):
        abstract = False
        constraints = TechnicalTaxonomyRecord.Meta.constraints + [
            models.UniqueConstraint(fields=["code"], name="service_root_cause_code_uniq")]


class RepairAction(TechnicalTaxonomyRecord):
    class Meta(TechnicalTaxonomyRecord.Meta):
        abstract = False
        constraints = TechnicalTaxonomyRecord.Meta.constraints + [
            models.UniqueConstraint(fields=["code"], name="service_repair_action_code_uniq")]


class TechnicalCategoryMapping(TimeStampedModel):
    """Lock/validation plumbing only; concrete models declare ordinary FKs."""
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

    class Meta:
        abstract = True

    def clean(self):
        super().clean()
        parent_id = getattr(self, f"{self.master_field}_id")
        previous = type(self).objects.filter(pk=self.pk).first()
        if previous and (getattr(previous, f"{self.master_field}_id") != parent_id
                         or previous.product_category_id != self.product_category_id):
            raise ValidationError("Mapping endpoints cannot change. Replace applicability through the service.")
        parent_model = self._meta.get_field(self.master_field).remote_field.model
        if parent_model.objects.filter(pk=parent_id, applies_to_all_product_categories=True).exists():
            raise ValidationError({self.master_field: "Global records cannot have explicit category mappings."})

    def save(self, *, force_insert=False, force_update=False, using=None, update_fields=None):
        using = using or router.db_for_write(type(self), instance=self)
        if update_fields is not None:
            update_fields = set(update_fields)
            if not update_fields:
                return
            update_fields.add("updated_at")
        with transaction.atomic(using=using):
            parent_field = f"{self.master_field}_id"
            original = type(self).objects.using(using).filter(pk=self.pk).values_list(parent_field, flat=True).first()
            parent_model = self._meta.get_field(self.master_field).remote_field.model
            parent_model.objects.using(using).select_for_update().filter(pk=original or getattr(self, parent_field)).first()
            self.full_clean()
            return super().save(force_insert=force_insert, force_update=force_update,
                using=using, update_fields=update_fields)

    def __str__(self):
        return f"{self.master_field} {getattr(self, self.master_field + '_id')} / category {self.product_category_id}"


class FaultDiagnosisProductCategory(TechnicalCategoryMapping):
    master_field = "fault_diagnosis"
    fault_diagnosis = models.ForeignKey(FaultDiagnosis, on_delete=models.PROTECT, related_name="category_mappings")
    product_category = models.ForeignKey(ProductCategory, on_delete=models.PROTECT, related_name="fault_diagnosis_mappings")

    class Meta:
        ordering = ["fault_diagnosis_id", "product_category_id"]
        constraints = [models.UniqueConstraint(fields=["fault_diagnosis", "product_category"],
            name="service_fault_category_uniq")]


class RootCauseProductCategory(TechnicalCategoryMapping):
    master_field = "root_cause"
    root_cause = models.ForeignKey(RootCause, on_delete=models.PROTECT, related_name="category_mappings")
    product_category = models.ForeignKey(ProductCategory, on_delete=models.PROTECT, related_name="root_cause_mappings")

    class Meta:
        ordering = ["root_cause_id", "product_category_id"]
        constraints = [models.UniqueConstraint(fields=["root_cause", "product_category"],
            name="service_root_cause_category_uniq")]


class RepairActionProductCategory(TechnicalCategoryMapping):
    master_field = "repair_action"
    repair_action = models.ForeignKey(RepairAction, on_delete=models.PROTECT, related_name="category_mappings")
    product_category = models.ForeignKey(ProductCategory, on_delete=models.PROTECT, related_name="repair_action_mappings")

    class Meta:
        ordering = ["repair_action_id", "product_category_id"]
        constraints = [models.UniqueConstraint(fields=["repair_action", "product_category"],
            name="service_repair_action_category_uniq")]
