from django.db import models
from django.urls import reverse
from django.utils.timezone import datetime
from django.db.models import Sum

# constants
datetime_format_string = '%Y-%m-%dT%H:%M:%SZ'

TASK_TYPE_OBSERVATION = 'observation'
TASK_TYPE_DATAPRODUCT = 'dataproduct'

TYPE_VISIBILITY = 'visibility'

# common model functions
def get_sum_from_dataproduct_field(taskID, field):
    """
    sum the values of a field for certain taskID. (used to sum total of dataproduct sizes)
    :param taskID:
    :param field:
    :return:
    """
    query = field + '__sum'
    sum_value = DataProduct.objects.filter(taskID=taskID).aggregate(Sum(field))[query]
    if sum_value == None:
        sum_value = 0.0
    return sum_value

"""
a base class of both Observation and Dataproducts
"""
class TaskObject(models.Model):
    name = models.CharField(max_length=100, default="unknown")
    task_type = models.CharField(max_length=20, default=TASK_TYPE_DATAPRODUCT)

    taskID = models.CharField('runId', db_index=True, max_length=30, blank=True, null=True)
    creationTime = models.DateTimeField(default=datetime.utcnow, blank=True)

    new_status = models.CharField(max_length=50, default="defined", null=True)
    data_location = models.CharField(max_length=255, default="unknown",null=True)
    irods_collection = models.CharField(max_length=255, default="unknown", null=True)

    # my_status is 'platgeslagen', because django-filters can not filter on a related property,
    # and I need services to be able to filter on a status to execute their tasks.
    my_status = models.CharField(db_index=True, max_length=50,default="defined")
    node = models.CharField(max_length=10, null=True)

    def __str__(self):
        return str(self.id)


class Status(models.Model):
    name = models.CharField(max_length=50, default="unknown")
    timestamp = models.DateTimeField(default=datetime.utcnow, blank=True)
    taskObject = models.ForeignKey(TaskObject, related_name='status_history', on_delete=models.CASCADE, null=False)

    @property
    def property_taskID(self):
        return self.taskObject.taskID

    @property
    def property_task_type(self):
        return self.taskObject.task_type

    # the representation of the value in the REST API
    def __str__(self):
        formatedDate = self.timestamp.strftime(datetime_format_string)
        return str(self.name)+' ('+str(formatedDate)+')'


class Observation(TaskObject):
    starttime = models.DateTimeField('start time', null=True)
    endtime = models.DateTimeField('end time', null=True)

    # beamPattern is used to understand how many beams (dataproducts) must be created
    beamPattern = models.CharField(max_length=50, null=True)

    # can be used to distinguish types of observations, like for ARTS.
    process_type = models.CharField(max_length=50, default="observation")
    observing_mode = models.CharField(max_length=50, default="imaging")

    # json object containing unmodelled parameters that are used by the 'executor' service
    # to create the parset based on a template and these parameters
    field_name = models.CharField(max_length=50, null=True)
    field_ra = models.FloatField('field_ra', null = True)
    field_dec = models.FloatField('field_dec', null = True)

    control_parameters = models.CharField(max_length=255, default="unknown", null=True)
    telescopes = models.CharField(max_length=100, default="all", null=True)
    skip_auto_ingest = models.BooleanField(default=False)
    beams = models.CharField(max_length=255, default="0..39")
    quality = models.CharField(max_length=30, default="unknown")

    ingest_progress = models.CharField(max_length=40, default="", null=True)

    # this translates a view-name (from urls.py) back to a url, to avoid hardcoded url's in the html templates
    # bad : <td><a href="/atdb/observations/{{ observation.id }}/" target="_blank">{{ observation.taskID }} </a> </td>
    # good: <td><a href="{{ observation.get_absolute_url }}" target="_blank">{{ observation.taskID }} </a> </td>
    def get_absolute_url(self):
        return reverse('observation-detail-view-api', kwargs={'pk': self.pk})

    @property
    def duration(self):
        try:
            duration = (self.endtime - self.starttime).seconds
        except:
            # to prevent crash for invalid observations that do not have a starttime
            duration = 0
        return duration

    @property
    def size(self):
        # sum the sizes of all dataproducts with this taskID. In Mb
        size = get_sum_from_dataproduct_field(self.taskID,'size')
        return size

    def __str__(self):
        return str(self.taskID)


class DataProduct(TaskObject):
    # properties
    filename = models.CharField(max_length=200, default="unknown")
    description = models.CharField(max_length=255, default="unknown")
    dataproduct_type = models.CharField('type', default=TYPE_VISIBILITY, max_length=50)
    size = models.BigIntegerField(default=0)
    quality = models.CharField(max_length=30, default="unknown")

    # relationships
    parent = models.ForeignKey(Observation, related_name='generated_dataproducts', on_delete=models.CASCADE, null=False)

    # this translates a view-name (from urls.py) back to a url, to avoid hardcoded url's in the html templates
    # bad : <td><a href="/atdb/observations/{{ observation.id }}/" target="_blank">{{ observation.taskID }} </a> </td>
    # good: <td><a href="{{ observation.get_absolute_url }}" target="_blank">{{ observation.taskID }} </a> </td>
    def get_absolute_url(self):
        return reverse('dataproduct-detail-view-api', kwargs={'pk': self.pk})

    def __str__(self):
        return self.filename