from django.db import models
from shimatta_modules import RandomFileName
from django.db.models import F, Sum
from django.contrib.auth.models import User as AuthUser
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.dispatch import receiver
import os
import uuid


storage_name_validator = RegexValidator(r'^[^/]*$', 'Slashes are not allowed in storage names')

# Create your models here.

class ComponentParameterType(models.Model):
	class Meta:
		ordering = ['parameter_name']

	TYPE_CHOICES = (
		('F', 'Free Text'),
		('N', 'Standard float number'),
		('E', 'Engineering / SI Unit'),
		('I', 'IT Type'),
	)

	parameter_name = models.CharField(max_length=50, unique=True)
	parameter_description = models.TextField(null=True, blank=True)
	unit = models.CharField(max_length=10, null=True, blank=True)
	parameter_type = models.CharField(max_length=1, choices=TYPE_CHOICES, default='N')

	def __str__(self):
		unit = ''
		if self.unit:
			unit = ' in ' + self.unit
		return self.parameter_name + unit + ' | ' + self.get_parameter_type_display()

class ComponentType(models.Model):
	class Meta:
		ordering = ['class_name']
	class_name = models.CharField(max_length=50, unique=True)
	passive = models.BooleanField()
	possible_parameter = models.ManyToManyField(ComponentParameterType, blank=True)

	def __str__(self):
		return '[' + self.class_name + ']'

class Storage(models.Model):
	class Meta:
		# permissions = ()
		# Allow only one child with the same name
		unique_together = ('name', 'parent_storage')
		ordering = ['name']

	id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
	name = models.CharField(max_length=100, validators=[storage_name_validator])
	parent_storage = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True)
	responsible = models.ForeignKey(AuthUser, on_delete=models.SET_NULL, blank=True, null=True)

	def get_path_components(self):
		chain = []
		iterator = self
		chain.append(self)
		while iterator.parent_storage is not None:
			chain.append(iterator.parent_storage)
			iterator = iterator.parent_storage

		return chain

	def get_full_path(self):
		output = ''
		
		chain = self.get_path_components()

		for i in range(len(chain) - 1, -1, -1):
			output = output + '/' + chain[i].name
		return output

	def get_qr_code(self):
		qrdata = '[stor_uuid]' + str(self.id)
		return qrdata

	def __str__(self):
		return self.get_full_path()

	def get_children(self):
		if self is None:  # Root node
			return Storage.objects.filter(parent_storage=None)
		else:
			return self.storage_set.all()

	def validate_unique(self, exclude=None):
		if Storage.objects.exclude(id=self.id).filter(name=self.name, parent_storage__isnull=True).exists():
			if self.parent_storage is None:
				raise ValidationError('The fields name, parent_storage must make a unique set')
		try:
			super(Storage, self).validate_unique(exclude)
		except:
			raise

	def get_total_stock_amount(self):
		stocks = Stock.objects.filter(storage=self)
		sum = stocks.aggregate(Sum('amount'))['amount__sum']
		if sum is None:
			sum = 0
		return sum

	def save(self, *args, **kwargs):
		self.validate_unique()
		super(Storage, self).save(*args, **kwargs)

class Distributor(models.Model):
	class Meta:
		ordering = ['name']

	id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
	name = models.CharField(max_length=100, unique=True)
	website = models.CharField(max_length=200, null=True, blank=True)
	image = models.ImageField(upload_to=RandomFileName.RandomFileName('distributor-logos'), null=True, blank=True)
	component_link_pattern = models.CharField(max_length=255, blank=True, null=True)

	def __str__(self):
		return self.name


class Package(models.Model):
	class Meta:
		ordering = ['name']

	id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
	name = models.CharField(max_length=50, unique=True)
	pin_count = models.PositiveIntegerField()
	smd = models.BooleanField()
	image = models.ImageField(upload_to=RandomFileName.RandomFileName('package-images'), blank=True, null=True)

	def __str__(self):
		return self.name


class Manufacturer(models.Model):
	class Meta:
		ordering = ['name']

	id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
	name = models.CharField(max_length=100, unique=True)
	website = models.CharField(max_length=200, null=True, blank=True)
	image = models.ImageField(upload_to=RandomFileName.RandomFileName('manufacturer-images'), blank=True, null=True)

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


class Component(models.Model):
	class Meta:
		unique_together = ('name', 'manufacturer', 'package')
		ordering = ['name', 'manufacturer']

	id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
	name = models.CharField(max_length=100)
	manufacturer = models.ForeignKey(Manufacturer, on_delete=models.PROTECT, blank=True, null=True)
	component_type = models.ForeignKey(ComponentType, on_delete=models.SET_NULL, blank=True, null=True)
	pref_distri = models.ForeignKey(Distributor, on_delete=models.PROTECT, blank=True, null=True)
	description = models.TextField(null=True, blank=True)
	datasheet_link = models.CharField(max_length=300, null=True, blank=True)
	package = models.ForeignKey(Package, on_delete=models.PROTECT, blank=True, null=True)
	image = models.ImageField(upload_to=RandomFileName.RandomFileName('component-images'), blank=True, null=True)

	def __str__(self):
		pack_name = ''
		man_name = ''

		if self.package:
			pack_name = ' in ' + self.package.name
		if self.manufacturer:
			man_name = ' by ' + self.manufacturer.name
		return self.name + pack_name + man_name

	def get_resolved_image(self):
		media_url = ''
		url = None
		if self.image:
			url = '%s%s' % (media_url, self.image.url)
		else:
			if self.package:
				if self.package.image:
					url = '%s%s' % (media_url, self.package.image.url)
		
		return url

	def get_qr_code(self):
		qrdata = '[comp_uuid]' + str(self.id)
		return qrdata
	
	def get_total_amount(self):
		stocks = Stock.objects.filter(component=self)
		sum = stocks.aggregate(Sum('amount'))['amount__sum']
		if sum is None:
			sum = 0
		return sum

		
class ComponentParameter(models.Model):
	class Meta:
		unique_together = ('component', 'parameter_type')
		ordering = ['id']
	id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
	component = models.ForeignKey(Component, on_delete=models.CASCADE)  # A target component is required!
	parameter_type = models.ForeignKey(ComponentParameterType, on_delete=models.CASCADE)
	value = models.FloatField(default=0)
	text_value = models.TextField(null=True, blank=True)

	def __str__(self):
		if self.parameter_type.parameter_type == 'F':
			value = self.text_value
		else:
			value = str(self.value)

		return str(self.component)+ ': '+ str(self.parameter_type) + ': ' + value


class Stock(models.Model):
	class Meta:
		unique_together = ('component', 'storage')
		ordering = ['id']
	id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
	component = models.ForeignKey(Component, on_delete=models.PROTECT, blank=True, null=True)
	storage = models.ForeignKey(Storage, on_delete=models.PROTECT, blank=True, null=True)
	amount = models.PositiveIntegerField()
	watermark = models.IntegerField()  # negative is no watermark
	lot = models.CharField(max_length=255, null=True, blank=True)

	def atomic_increment(self, increment):
		if self.amount + increment < 0:
			return False
		self.amount = F('amount') + increment
		self.save(update_fields=['amount'])
		return True

	def get_qr_code(self):
		qr_data = '[stock]'+str(self.id)
		return qr_data

	def __str__(self):
		return str(self.component) + ' @ ' + str(self.amount) + ' in ' + str(
			self.storage)
	
	@staticmethod
	def get_under_watermark(user_filter = None, invert: bool = False):
		query =  Stock.objects.filter(amount__lt = F('watermark'))
		if user_filter is not None:
			if invert:
				query = query.exclude(storage__responsible=user_filter)
			else:
				query = query.filter(storage__responsible=user_filter)
		return query

class DistributorNum(models.Model):
	class Meta:
		unique_together = ('component', 'distributor')
		ordering = ['distributor__name']
	id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
	distributor_part_number = models.CharField(max_length=100)
	distributor = models.ForeignKey(Distributor, on_delete=models.CASCADE)
	component = models.ForeignKey(Component, on_delete=models.CASCADE)

	def __str__(self):
		return self.component.name + '@' + self.distributor.name + ': ' + self.distributor_part_number

# These functions ensure that the uploaded images are deleted if the model is deleted or the image file is changed.
@receiver(models.signals.post_delete, sender=Component)
@receiver(models.signals.post_delete, sender=Distributor)
@receiver(models.signals.post_delete, sender=Package)
@receiver(models.signals.post_delete, sender=Manufacturer)
def auto_delete_file_on_delete(sender, instance, **kwargs):
    """
    Deletes file from filesystem
    when corresponding `MediaFile` object is deleted.
    """
    if instance.image:
        if os.path.isfile(instance.image.path):
            os.remove(instance.image.path)

@receiver(models.signals.pre_save, sender=Component)
@receiver(models.signals.pre_save, sender=Distributor)
@receiver(models.signals.pre_save, sender=Package)
@receiver(models.signals.pre_save, sender=Manufacturer)
def auto_delete_file_on_change(sender, instance, **kwargs):
    """
    Deletes old file from filesystem
    when corresponding `MediaFile` object is updated
    with new file.
    """
    if not instance.pk:
        return False

    try:
        old_file = sender.objects.get(pk=instance.pk).image
    except sender.DoesNotExist:
        return False

    new_file = instance.image
    if not old_file == new_file:
        if not old_file:
            return True
        if os.path.isfile(old_file.path):
            os.remove(old_file.path)