from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.utils.translation import gettext_lazy as _

from dcim.choices import *
from netbox.config import ConfigItem
from netbox.models import PrimaryModel
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
from utilities.validators import ExclusionValidator

from .device_components import CabledObjectModel, PathEndpoint

__all__ = (
    'PowerFeed',
    'PowerPanel',
)


#
# Power
#

class PowerPanel(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
    """
    A distribution point for electrical power; e.g. a data center RPP.
    """
    site = models.ForeignKey(
        to='Site',
        on_delete=models.PROTECT
    )
    location = models.ForeignKey(
        to='dcim.Location',
        on_delete=models.PROTECT,
        blank=True,
        null=True
    )
    name = models.CharField(
        verbose_name=_('name'),
        max_length=100,
        db_collation="natural_sort"
    )

    prerequisite_models = (
        'dcim.Site',
    )

    class Meta:
        ordering = ['site', 'name']
        constraints = (
            models.UniqueConstraint(
                fields=('site', 'name'),
                name='%(app_label)s_%(class)s_unique_site_name'
            ),
        )
        verbose_name = _('power panel')
        verbose_name_plural = _('power panels')

    def __str__(self):
        return self.name

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

        # Location must belong to assigned Site
        if self.location and self.location.site != self.site:
            raise ValidationError(
                _("Location {location} ({location_site}) is in a different site than {site}").format(
                    location=self.location, location_site=self.location.site, site=self.site)
            )


class PowerFeed(PrimaryModel, PathEndpoint, CabledObjectModel):
    """
    An electrical circuit delivered from a PowerPanel.
    """
    power_panel = models.ForeignKey(
        to='PowerPanel',
        on_delete=models.PROTECT,
        related_name='powerfeeds'
    )
    rack = models.ForeignKey(
        to='Rack',
        on_delete=models.PROTECT,
        related_name='powerfeeds',
        blank=True,
        null=True
    )
    name = models.CharField(
        verbose_name=_('name'),
        max_length=100,
        db_collation="natural_sort"
    )
    status = models.CharField(
        verbose_name=_('status'),
        max_length=50,
        choices=PowerFeedStatusChoices,
        default=PowerFeedStatusChoices.STATUS_ACTIVE
    )
    type = models.CharField(
        verbose_name=_('type'),
        max_length=50,
        choices=PowerFeedTypeChoices,
        default=PowerFeedTypeChoices.TYPE_PRIMARY
    )
    supply = models.CharField(
        verbose_name=_('supply'),
        max_length=50,
        choices=PowerFeedSupplyChoices,
        default=PowerFeedSupplyChoices.SUPPLY_AC
    )
    phase = models.CharField(
        verbose_name=_('phase'),
        max_length=50,
        choices=PowerFeedPhaseChoices,
        default=PowerFeedPhaseChoices.PHASE_SINGLE
    )
    voltage = models.SmallIntegerField(
        verbose_name=_('voltage'),
        default=ConfigItem('POWERFEED_DEFAULT_VOLTAGE'),
        validators=[ExclusionValidator([0])]
    )
    amperage = models.PositiveSmallIntegerField(
        verbose_name=_('amperage'),
        validators=[MinValueValidator(1)],
        default=ConfigItem('POWERFEED_DEFAULT_AMPERAGE')
    )
    max_utilization = models.PositiveSmallIntegerField(
        verbose_name=_('max utilization'),
        validators=[MinValueValidator(1), MaxValueValidator(100)],
        default=ConfigItem('POWERFEED_DEFAULT_MAX_UTILIZATION'),
        help_text=_("Maximum permissible draw (percentage)")
    )
    available_power = models.PositiveIntegerField(
        verbose_name=_('available power'),
        default=0,
        editable=False
    )
    tenant = models.ForeignKey(
        to='tenancy.Tenant',
        on_delete=models.PROTECT,
        related_name='power_feeds',
        blank=True,
        null=True
    )

    clone_fields = (
        'power_panel', 'rack', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
        'max_utilization', 'tenant',
    )
    prerequisite_models = (
        'dcim.PowerPanel',
    )

    class Meta:
        ordering = ['power_panel', 'name']
        constraints = (
            models.UniqueConstraint(
                fields=('power_panel', 'name'),
                name='%(app_label)s_%(class)s_unique_power_panel_name'
            ),
        )
        verbose_name = _('power feed')
        verbose_name_plural = _('power feeds')

    def __str__(self):
        return self.name

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

        # Rack must belong to same Site as PowerPanel
        if self.rack and self.rack.site != self.power_panel.site:
            raise ValidationError(_(
                "Rack {rack} ({rack_site}) and power panel {powerpanel} ({powerpanel_site}) are in different sites."
            ).format(
                rack=self.rack,
                rack_site=self.rack.site,
                powerpanel=self.power_panel,
                powerpanel_site=self.power_panel.site
            ))

        # AC voltage cannot be negative
        if self.voltage < 0 and self.supply == PowerFeedSupplyChoices.SUPPLY_AC:
            raise ValidationError({
                "voltage": _("Voltage cannot be negative for AC supply")
            })

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

        # Cache the available_power property on the instance
        kva = abs(self.voltage) * self.amperage * (self.max_utilization / 100)
        if self.phase == PowerFeedPhaseChoices.PHASE_3PHASE:
            self.available_power = round(kva * 1.732)
        else:
            self.available_power = round(kva)

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

    @property
    def parent_object(self):
        return self.power_panel

    def get_type_color(self):
        return PowerFeedTypeChoices.colors.get(self.type)

    def get_status_color(self):
        return PowerFeedStatusChoices.colors.get(self.status)
