from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django_softdelete.models import SoftDeleteModel
from rest_framework.exceptions import ValidationError

from authentication.models import ToolshedUser, KnownIdentity, Group
from files.models import File


class Category(SoftDeleteModel):
    name = models.CharField(max_length=255)
    description = models.TextField(null=True, blank=True)
    parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, related_name='children')
    origin = models.CharField(max_length=255, null=False, blank=False)

    class Meta:
        verbose_name_plural = 'categories'
        constraints = [
            models.UniqueConstraint(fields=['name', 'parent'], condition=models.Q(parent__isnull=False),
                                    name='category_unique_name_parent'),
            models.UniqueConstraint(fields=['name'], condition=models.Q(parent__isnull=True),
                                    name='category_unique_name_no_parent')
        ]

    def __str__(self):
        parent = str(self.parent) + "/" if self.parent else ""
        return parent + self.name

    def get_handle(self):
        """Return a fully qualified handle like 'git:base#category:tools'"""
        return f"{self.origin}#category:{self.name}"


class Property(models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField(null=True, blank=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, related_name='properties')
    unit_symbol = models.CharField(max_length=16, null=True, blank=True)
    unit_name = models.CharField(max_length=255, null=True, blank=True)
    unit_name_plural = models.CharField(max_length=255, null=True, blank=True)
    base2_prefix = models.BooleanField(default=False)
    dimensions = models.IntegerField(null=False, blank=False, default=1, validators=[MinValueValidator(1)])
    origin = models.CharField(max_length=255, null=False, blank=False)

    class Meta:
        verbose_name_plural = 'properties'
        constraints = [
            models.UniqueConstraint(fields=['name', 'category'], condition=models.Q(category__isnull=False),
                                    name='property_unique_name_category'),
            models.UniqueConstraint(fields=['name'], condition=models.Q(category__isnull=True),
                                    name='property_unique_name_no_category')
        ]

    def __str__(self):
        return self.name

    def get_handle(self):
        """Return a fully qualified handle like 'git:base#property:length'"""
        return f"{self.origin}#property:{self.name}"


class Tag(models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField(null=True, blank=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, related_name='tags')
    origin = models.CharField(max_length=255, null=False, blank=False)

    class Meta:
        verbose_name_plural = 'tags'
        constraints = [
            models.UniqueConstraint(fields=['name', 'category'], condition=models.Q(category__isnull=False),
                                    name='tag_unique_name_category'),
            models.UniqueConstraint(fields=['name'], condition=models.Q(category__isnull=True),
                                    name='tag_unique_name_no_category')
        ]

    def __str__(self):
        return self.name

    def get_handle(self):
        """Return a fully qualified handle like 'git:tools#tag:drill'"""
        return f"{self.origin}#tag:{self.name}"


class InventoryItem(SoftDeleteModel):
    AVAILABILITY_POLICY_CHOICES = (
        ('sell', 'Sell'),
        ('rent', 'Rent'),
        ('lend', 'Lend'),
        ('share', 'Share'),
        ('private', 'Private'),
    )

    published = models.BooleanField(default=False)
    name = models.CharField(max_length=255, null=True, blank=True)
    description = models.TextField(null=True, blank=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, related_name='inventory_items')
    availability_policy = models.CharField(max_length=20, choices=AVAILABILITY_POLICY_CHOICES, default='private')
    owned_quantity = models.IntegerField(default=1, validators=[MinValueValidator(0)])
    owner = models.ForeignKey(ToolshedUser, on_delete=models.CASCADE, null=True, blank=True,
                              related_name='inventory_items')
    owner_group = models.ForeignKey(Group, on_delete=models.CASCADE, null=True, blank=True,
                                    related_name='inventory_items')
    created_at = models.DateTimeField(auto_now_add=True)
    tags = models.ManyToManyField(Tag, through='ItemTag', related_name='inventory_items')
    properties = models.ManyToManyField(Property, through='ItemProperty')
    files = models.ManyToManyField(File, related_name='connected_items')
    storage_location = models.ForeignKey('StorageLocation', on_delete=models.SET_NULL, null=True, blank=True,
                                         related_name='inventory_items')

    def clean(self):
        if (self.name is None or self.name == "") and self.files.count() == 0:
            raise ValidationError("Name or at least one file must be set")
        if (self.owner is None) == (self.owner_group is None):
            raise ValidationError("Exactly one of owner or owner_group must be set")


class ItemProperty(models.Model):
    property = models.ForeignKey(Property, on_delete=models.CASCADE)
    inventory_item = models.ForeignKey(InventoryItem, on_delete=models.CASCADE)
    value = models.CharField(max_length=255)


class ItemTag(models.Model):
    tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
    inventory_item = models.ForeignKey(InventoryItem, on_delete=models.CASCADE)


class StorageLocation(models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField(null=True, blank=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True,
                                 related_name='storage_locations')
    parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
    owner = models.ForeignKey(ToolshedUser, on_delete=models.CASCADE, related_name='storage_locations')

    def __str__(self):
        parent = str(self.parent) + "/" if self.parent else ""
        return parent + self.name


class WorkflowInstance(models.Model):
    slug = models.CharField(max_length=255)
    state = models.CharField(max_length=255)
    payload = models.TextField(default='', blank=True) # an opaque, frontend-serialized JSON string on the backend.
    owner = models.ForeignKey(ToolshedUser, on_delete=models.CASCADE, related_name='workflows')
    staged_files = models.ManyToManyField(File, related_name='staged_by_workflows', blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.slug} ({self.state})"


