from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

from netbox.choices import *
from utilities.conversion import to_grams, to_meters

__all__ = (
    'DistanceMixin',
    'OwnerMixin',
    'WeightMixin',
)


class OwnerMixin(models.Model):
    """
    Adds a ForeignKey to users.Owner to indicate an object's owner.
    """
    owner = models.ForeignKey(
        to='users.Owner',
        on_delete=models.PROTECT,
        blank=True,
        null=True
    )

    class Meta:
        abstract = True


class WeightMixin(models.Model):
    weight = models.DecimalField(
        verbose_name=_('weight'),
        max_digits=8,
        decimal_places=2,
        blank=True,
        null=True
    )
    weight_unit = models.CharField(
        verbose_name=_('weight unit'),
        max_length=50,
        choices=WeightUnitChoices,
        blank=True,
        null=True,
    )
    # Stores the normalized weight (in grams) for database ordering
    _abs_weight = models.PositiveBigIntegerField(
        blank=True,
        null=True
    )

    class Meta:
        abstract = True

    @property
    def abs_weight(self):
        # Public alias for _abs_weight; Django templates cannot access underscore-prefixed attributes.
        return self._abs_weight

    def save(self, *args, **kwargs):

        # Store the given weight (if any) in grams for use in database ordering
        if self.weight and self.weight_unit:
            self._abs_weight = to_grams(self.weight, self.weight_unit)
        else:
            self._abs_weight = None

        super().save(*args, **kwargs)

    def clean(self):
        super().clean()

        # Validate weight and weight_unit
        if self.weight and not self.weight_unit:
            raise ValidationError(_("Must specify a unit when setting a weight"))


class DistanceMixin(models.Model):
    distance = models.DecimalField(
        verbose_name=_('distance'),
        max_digits=8,
        decimal_places=2,
        blank=True,
        null=True,
    )
    distance_unit = models.CharField(
        verbose_name=_('distance unit'),
        max_length=50,
        choices=DistanceUnitChoices,
        blank=True,
        null=True,
    )
    # Stores the normalized distance (in meters) for database ordering
    _abs_distance = models.DecimalField(
        max_digits=13,
        decimal_places=4,
        blank=True,
        null=True
    )

    class Meta:
        abstract = True

    @property
    def abs_distance(self):
        # Public alias for _abs_distance; Django templates cannot access underscore-prefixed attributes.
        return self._abs_distance

    def save(self, *args, **kwargs):
        # Store the given distance (if any) in meters for use in database ordering
        if self.distance is not None and self.distance_unit:
            self._abs_distance = to_meters(self.distance, self.distance_unit)
        else:
            self._abs_distance = None

        # Clear distance_unit if no distance is defined
        if self.distance is None:
            self.distance_unit = None

        super().save(*args, **kwargs)

    def clean(self):
        super().clean()

        # Validate distance and distance_unit
        if self.distance and not self.distance_unit:
            raise ValidationError(_("Must specify a unit when setting a distance"))
