from django.db import models
from django.contrib.auth.models import User

class Node(models.Model):
    author = models.ForeignKey(to=User, on_delete=models.DO_NOTHING)
    creation_time = models.DateTimeField(auto_now_add=True)

    friendly_name = models.CharField(max_length = 255)
    hardware_id = models.CharField(max_length = 128)


    def __str__(self):
        return f"{self.friendly_name} - {self.hardware_id}"

class Group(models.Model):
    author = models.ForeignKey(to=User, on_delete=models.DO_NOTHING)
    creation_time = models.DateTimeField(auto_now_add=True)

    name = models.CharField(max_length = 255)

    def __str__(self):
        return self.name

class Node2Group(models.Model):
    author = models.ForeignKey(to=User, on_delete=models.DO_NOTHING)
    creation_time = models.DateTimeField(auto_now_add=True)

    node = models.ForeignKey(to=Node, on_delete=models.CASCADE)
    group = models.ForeignKey(to=Group, on_delete=models.CASCADE)

    def __str__(self):
        return f"{self.node.friendly_name}:{self.group.name}"

class GroupType (models.Model):
    """
    Application-level discriminating field for what a "group" is
    """
    author = models.ForeignKey(to=User, on_delete=models.DO_NOTHING)
    creation_time = models.DateTimeField(auto_now_add=True)

    name = models.CharField(max_length = 255)

    def __str__(self):
        return self.name

class GroupParent(models.Model):
    """
    Groups are allowed to have multiple parents, no constraints on that.

    TODO: Enforce child != parent
    """
    author = models.ForeignKey(to=User, on_delete=models.DO_NOTHING)
    creation_time = models.DateTimeField(auto_now_add=True)

    child = models.ForeignKey(to=Group, on_delete=models.CASCADE, related_name='child_group')
    parent = models.ForeignKey(to=Group, on_delete=models.CASCADE, related_name='parent_group')

    def __str__(self):
        return f"{self.child.name}<{self.parent.name}"

class Group2Type(models.Model):
    author = models.ForeignKey(to=User, on_delete=models.DO_NOTHING)
    creation_time = models.DateTimeField(auto_now_add=True)

    group = models.ForeignKey(to=Group, on_delete=models.CASCADE)
    type = models.ForeignKey(to=GroupType, on_delete=models.CASCADE)

    def __str__(self):
        return f"{self.group.name}:{self.type.name}"

    class Meta:
        """
        A group can only be one type
        """
        constraints = [
            models.UniqueConstraint(fields=['group', 'type'], name='unique_group_type')
        ]


class Measurement(models.Model):
    source_node = models.ForeignKey(to=Node, on_delete=models.DO_NOTHING, related_name='measurement_source_node')
    collection_time = models.DateTimeField()
    server_received_time = models.DateTimeField(auto_now_add=True)
    temperature_18_inch = models.FloatField()
    temperature_36_inch = models.FloatField()
    # Onboard ESP32 temperature
    device_temperature = models.FloatField()
    # Onboard temp, humidity. Probes:SHTxx, BS: BME280
    ambient_temperature = models.FloatField()
    relative_humidity = models.FloatField()
    # Base station only - BME280
    barometric_pressure = models.FloatField()
    accelerometer_x = models.FloatField()
    accelerometer_y = models.FloatField()
    accelerometer_z = models.FloatField()
    battery_charge_percent = models.FloatField()
    battery_voltage = models.FloatField()
    remaining_battery_capacity = models.FloatField()

    # Node (base station, generally) that ultimately sent the server this measurement.
    reporting_node = models.ForeignKey(to=Node, on_delete=models.DO_NOTHING, related_name='measurement_reporting_node')

    # Associated group for this measurement (generally a "temperature site")
    # ~~Nullable because probes sitting somewhere may not be part of a group~~
    # Not nullable because we always want a node parented by some group and this way we have
    # better history for where a measurement value was taken.
    associated_group = models.ForeignKey(to=Group, on_delete=models.DO_NOTHING, related_name='measurement_associated_group')

    class Meta:
        unique_together = (('source_node', 'collection_time'),)

class NodeRssiRecord(models.Model):
    device = models.ForeignKey(to=Node, on_delete=models.DO_NOTHING, related_name='rssi_source_node')
    collection_time = models.DateTimeField()
    server_received_time = models.DateTimeField(auto_now_add=True)

    # Neighbor we heard from
    neighbor = models.ForeignKey(to=Node, on_delete=models.DO_NOTHING)
    # Signal strength of neighbor from device's perspective
    rssi = models.IntegerField()


class NodeFileManifest(models.Model):
    device = models.ForeignKey(to=Node, on_delete=models.DO_NOTHING)
    collection_time = models.DateTimeField()
    server_received_time = models.DateTimeField(auto_now_add=True)
    program_id = models.IntegerField()
    program_version = models.IntegerField()

class NodeReportedStatus(models.Model):
    """
    Status reported by a Node
    """
    """ Status 1 """
    device = models.ForeignKey(to=Node, on_delete=models.DO_NOTHING)
    collection_time = models.DateTimeField()
    server_received_time = models.DateTimeField(auto_now_add=True)
    measurement_interval_minutes = models.IntegerField()
    wake_window_length_minutes = models.IntegerField()
    # Default is things are aligned to midnight. This pushes it off that.
    offset_from_midnight_minutes = models.IntegerField()
    sleep_duration_minutes = models.IntegerField()

    """ Status 2 """
    # Number of saved measurements on a node
    number_saved_measurements = models.IntegerField()

    """ Other Things """
    when_time_was_last_updated = models.DateTimeField()

class NodeConfiguration(models.Model):
    """
    Configuration to be applied to Node(s)
    """
    author = models.ForeignKey(to=User, on_delete=models.DO_NOTHING)
    created_time = models.DateTimeField(auto_now_add=True)

    measurement_interval_minutes = models.IntegerField()
    wake_window_length_minutes = models.IntegerField()
    # Default is things are aligned to midnight. This pushes it off that.
    offset_from_midnight_minutes = models.IntegerField()
    sleep_duration_minutes = models.IntegerField()