from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.urls import reverse
from taggit.managers import TaggableManager

from dcim.choices import *
from dcim.constants import *
from extras.models import ChangeLoggedModel, CustomFieldModel, TaggedItem
from extras.utils import extras_features
from utilities.querysets import RestrictedQuerySet
from utilities.validators import ExclusionValidator
from .device_components import CableTermination

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


#
# Power
#

@extras_features('custom_links', 'export_templates', 'webhooks')
class PowerPanel(ChangeLoggedModel):
    """
    A distribution point for electrical power; e.g. a data center RPP.
    """
    site = models.ForeignKey(
        to='Site',
        on_delete=models.PROTECT
    )
    rack_group = models.ForeignKey(
        to='RackGroup',
        on_delete=models.PROTECT,
        blank=True,
        null=True
    )
    name = models.CharField(
        max_length=50
    )
    tags = TaggableManager(through=TaggedItem)

    objects = RestrictedQuerySet.as_manager()

    csv_headers = ['site', 'rack_group', 'name']

    class Meta:
        ordering = ['site', 'name']
        unique_together = ['site', 'name']

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('dcim:powerpanel', args=[self.pk])

    def to_csv(self):
        return (
            self.site.name,
            self.rack_group.name if self.rack_group else None,
            self.name,
        )

    def clean(self):

        # RackGroup must belong to assigned Site
        if self.rack_group and self.rack_group.site != self.site:
            raise ValidationError("Rack group {} ({}) is in a different site than {}".format(
                self.rack_group, self.rack_group.site, self.site
            ))


@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
    """
    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,
        blank=True,
        null=True
    )
    connected_endpoint = models.OneToOneField(
        to='dcim.PowerPort',
        on_delete=models.SET_NULL,
        related_name='+',
        blank=True,
        null=True
    )
    connection_status = models.BooleanField(
        choices=CONNECTION_STATUS_CHOICES,
        blank=True,
        null=True
    )
    name = models.CharField(
        max_length=50
    )
    status = models.CharField(
        max_length=50,
        choices=PowerFeedStatusChoices,
        default=PowerFeedStatusChoices.STATUS_ACTIVE
    )
    type = models.CharField(
        max_length=50,
        choices=PowerFeedTypeChoices,
        default=PowerFeedTypeChoices.TYPE_PRIMARY
    )
    supply = models.CharField(
        max_length=50,
        choices=PowerFeedSupplyChoices,
        default=PowerFeedSupplyChoices.SUPPLY_AC
    )
    phase = models.CharField(
        max_length=50,
        choices=PowerFeedPhaseChoices,
        default=PowerFeedPhaseChoices.PHASE_SINGLE
    )
    voltage = models.SmallIntegerField(
        default=POWERFEED_VOLTAGE_DEFAULT,
        validators=[ExclusionValidator([0])]
    )
    amperage = models.PositiveSmallIntegerField(
        validators=[MinValueValidator(1)],
        default=POWERFEED_AMPERAGE_DEFAULT
    )
    max_utilization = models.PositiveSmallIntegerField(
        validators=[MinValueValidator(1), MaxValueValidator(100)],
        default=POWERFEED_MAX_UTILIZATION_DEFAULT,
        help_text="Maximum permissible draw (percentage)"
    )
    available_power = models.PositiveIntegerField(
        default=0,
        editable=False
    )
    comments = models.TextField(
        blank=True
    )
    custom_field_values = GenericRelation(
        to='extras.CustomFieldValue',
        content_type_field='obj_type',
        object_id_field='obj_id'
    )
    tags = TaggableManager(through=TaggedItem)

    objects = RestrictedQuerySet.as_manager()

    csv_headers = [
        'site', 'power_panel', 'rack_group', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage',
        'amperage', 'max_utilization', 'comments',
    ]
    clone_fields = [
        'power_panel', 'rack', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization',
        'available_power',
    ]

    STATUS_CLASS_MAP = {
        PowerFeedStatusChoices.STATUS_OFFLINE: 'warning',
        PowerFeedStatusChoices.STATUS_ACTIVE: 'success',
        PowerFeedStatusChoices.STATUS_PLANNED: 'info',
        PowerFeedStatusChoices.STATUS_FAILED: 'danger',
    }

    TYPE_CLASS_MAP = {
        PowerFeedTypeChoices.TYPE_PRIMARY: 'success',
        PowerFeedTypeChoices.TYPE_REDUNDANT: 'info',
    }

    class Meta:
        ordering = ['power_panel', 'name']
        unique_together = ['power_panel', 'name']

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('dcim:powerfeed', args=[self.pk])

    def to_csv(self):
        return (
            self.power_panel.site.name,
            self.power_panel.name,
            self.rack.group.name if self.rack and self.rack.group else None,
            self.rack.name if self.rack else None,
            self.name,
            self.get_status_display(),
            self.get_type_display(),
            self.get_supply_display(),
            self.get_phase_display(),
            self.voltage,
            self.amperage,
            self.max_utilization,
            self.comments,
        )

    def clean(self):

        # Rack must belong to same Site as PowerPanel
        if self.rack and self.rack.site != self.power_panel.site:
            raise ValidationError("Rack {} ({}) and power panel {} ({}) are in different sites".format(
                self.rack, self.rack.site, self.power_panel, 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(self):
        return self.power_panel

    def get_type_class(self):
        return self.TYPE_CLASS_MAP.get(self.type)

    def get_status_class(self):
        return self.STATUS_CLASS_MAP.get(self.status)
