import uuid
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _
from djangoldp.models import Model
from rest_framework.exceptions import ValidationError

from djangoldp_component.models import Component, Package


class ApplicationTemplate(Model):
    friendly_name = models.CharField(max_length=255, blank=True, null=True)
    short_description = models.CharField(max_length=255, blank=True, null=True)
    repository = models.CharField(max_length=255, blank=True, null=True)
    creator = models.ForeignKey(
        get_user_model(),
        related_name="templates",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    slug = models.SlugField(unique=True, blank=True, null=True)

    def __str__(self):
        try:
            return "{} ({})".format(self.friendly_name, self.urlid)
        except:
            return self.urlid

    class Meta(Model.Meta):
        anonymous_perms = ["view"]
        authenticated_perms = ["inherit", "add"]
        auto_author = "creator"
        container_path = "/templates/"
        depth = 1  # Do not serialize user
        lookup_field = "slug"
        nested_fields = [
            "components",
            "packages",
        ]
        ordering = ["slug"]
        owner_field = "creator"
        owner_perms = ["inherit", "change", "delete"]
        rdf_context = {
            "friendly_name": "sib:friendlyName",
            "short_description": "sib:shortDescription",
            "creator": "foaf:user",
        }
        rdf_type = "sib:template"
        serializer_fields = [
            "@id",
            "friendly_name",
            "short_description",
            "creator",
            "components",
            "packages",
        ]
        superuser_perms = ["view"]
        verbose_name = _("template")
        verbose_name_plural = _("templates")


class TemplateRequiredComponent(Model):
    template = models.ForeignKey(
        ApplicationTemplate,
        on_delete=models.CASCADE,
        related_name="components",
        null=True,
        blank=True,
    )
    component = models.ForeignKey(
        Component,
        on_delete=models.CASCADE,
        related_name="templates",
        null=True,
        blank=True,
    )

    def __str__(self):
        try:
            return "{} + {} ({})".format(
                self.template.friendly_name, self.component.friendly_name, self.urlid
            )
        except:
            return self.urlid

    class Meta(Model.Meta):
        anonymous_perms = ["view"]
        authenticated_perms = ["inherit"]
        container_path = "template-required-components/"
        nested_fields = ["component"]
        owner_field = "template__creator"
        owner_perms = ["inherit", "change", "delete"]
        rdf_context = {
            "template": "sib:template",
            "component": "sib:component",
        }
        rdf_type = "sib:dependency"
        serializer_fields = ["@id", "component"]
        superuser_perms = ["inherit"]
        verbose_name = _("template required component")
        verbose_name_plural = _("template required components")

    def save(self, *args, **kwargs):
        if (
            not self.pk
            and TemplateRequiredComponent.objects.filter(
                component=self.component, template=self.template
            ).exists()
        ):
            return

        super(TemplateRequiredComponent, self).save(*args, **kwargs)


class TemplateRequiredPackage(Model):
    template = models.ForeignKey(
        ApplicationTemplate,
        on_delete=models.CASCADE,
        related_name="packages",
        null=True,
        blank=True,
    )
    package = models.ForeignKey(
        Package,
        on_delete=models.CASCADE,
        related_name="templates",
        null=True,
        blank=True,
    )

    def __str__(self):
        try:
            return "{} + {} ({})".format(
                self.template.friendly_name, self.package.friendly_name, self.urlid
            )
        except:
            return self.urlid

    class Meta(Model.Meta):
        anonymous_perms = ["view"]
        authenticated_perms = ["inherit"]
        container_path = "template-required-packages/"
        nested_fields = ["package"]
        owner_field = "template__creator"
        owner_perms = ["inherit", "change", "delete"]
        rdf_context = {
            "template": "sib:template",
            "package": "sib:package",
        }
        rdf_type = "sib:dependency"
        serializer_fields = ["@id", "package"]
        superuser_perms = ["inherit"]
        verbose_name = _("template required package")
        verbose_name_plural = _("template required packages")

    def save(self, *args, **kwargs):
        if (
            not self.pk
            and TemplateRequiredPackage.objects.filter(
                component=self.component, package=self.package
            ).exists()
        ):
            return

        super(TemplateRequiredPackage, self).save(*args, **kwargs)


class Application(Model):
    template = models.ForeignKey(
        ApplicationTemplate,
        on_delete=models.CASCADE,
        related_name="applications",
        null=True,
        blank=True,
    )
    friendly_name = models.CharField(max_length=255, blank=True, null=True)
    short_description = models.CharField(max_length=255, blank=True, null=True)
    creator = models.ForeignKey(
        get_user_model(),
        related_name="applications",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    client_url = models.CharField(max_length=255, blank=True, null=True)
    application_title = models.CharField(max_length=255, blank=True, null=True)
    application_logo = models.CharField(max_length=255, blank=True, null=True)
    slug = models.SlugField(unique=True, blank=True, null=True)

    def __str__(self):
        try:
            return "{} ({})".format(self.friendly_name, self.urlid)
        except:
            return self.urlid

    class Meta(Model.Meta):
        anonymous_perms = ["view"]
        authenticated_perms = ["inherit", "add"]
        auto_author = "creator"
        container_path = "/applications/"
        depth = 1  # Do not serialize user
        lookup_field = "slug"
        nested_fields = ["components"]
        ordering = ["slug"]
        owner_field = "creator"
        owner_perms = ["inherit", "change", "delete"]
        rdf_context = {
            "friendly_name": "sib:friendlyName",
            "short_description": "sib:shortDescription",
            "creator": "foaf:user",
        }
        rdf_type = "sib:application"
        serializer_fields = [
            "@id",
            "friendly_name",
            "short_description",
            "creator",
            "client_url",
            "application_title",
            "application_logo",
            "components",
            "federation",
        ]
        superuser_perms = ["view"]
        verbose_name = _("application")
        verbose_name_plural = _("applications")


class Federation(Model):
    application = models.ForeignKey(
        Application,
        on_delete=models.CASCADE,
        related_name="federation",
        null=True,
        blank=True,
    )
    target = models.ForeignKey(
        Application,
        on_delete=models.CASCADE,
        related_name="targeted_federation",
        null=True,
        blank=True,
    )

    def __str__(self):
        try:
            return "{} ({})".format(self.friendly_name, self.urlid)
        except:
            return self.urlid

    class Meta(Model.Meta):
        anonymous_perms = ["view"]
        authenticated_perms = ["inherit", "add"]
        container_path = "/federations/"
        depth = 0
        # nested_fields = []
        ordering = ["application__urlid", "target__urlid"]
        owner_field = "application__creator"
        owner_perms = ["inherit", "change", "delete"]
        rdf_context = {
            "application": "sib:application",
            "target": "sib:application",
        }
        rdf_type = "sib:federation"
        serializer_fields = [
            "@id",
            "target",
        ]
        superuser_perms = ["view"]
        verbose_name = _("federation")
        verbose_name_plural = _("federations")


class ApplicationComponent(Model):
    application = models.ForeignKey(
        Application,
        on_delete=models.CASCADE,
        related_name="components",
        null=True,
        blank=True,
    )
    component = models.ForeignKey(
        Component,
        on_delete=models.CASCADE,
        related_name="applications",
        null=True,
        blank=True,
    )

    def __str__(self):
        try:
            return "{} + {} ({})".format(
                self.application.friendly_name, self.component.friendly_name, self.urlid
            )
        except:
            return self.urlid

    class Meta(Model.Meta):
        anonymous_perms = ["view"]
        authenticated_perms = ["inherit"]
        container_path = "application-components/"
        nested_fields = ["component", "parameters"]
        owner_field = "application__creator"
        owner_perms = ["inherit", "change", "delete"]
        rdf_context = {
            "application": "sib:application",
            "component": "sib:component",
        }
        rdf_type = "sib:dependency"
        serializer_fields = ["@id", "component", "parameters"]
        superuser_perms = ["inherit"]
        verbose_name = _("application component")
        verbose_name_plural = _("application components")

    def save(self, *args, **kwargs):
        if (
            not self.pk
            and ApplicationComponent.objects.filter(
                component=self.component, application=self.application
            ).exists()
        ):
            return

        super(ApplicationComponent, self).save(*args, **kwargs)


class ApplicationComponentParameter(Model):
    component = models.ForeignKey(
        ApplicationComponent,
        on_delete=models.CASCADE,
        related_name="parameters",
        null=True,
        blank=True,
    )
    key = models.CharField(max_length=255, blank=True, null=True)
    value = models.CharField(max_length=255, blank=True, null=True)

    def __str__(self):
        try:
            return "{} -> {} ({})".format(
                self.component.application.friendly_name, self.component.component.friendly_name, self.urlid
            )
        except:
            return self.urlid

    class Meta(Model.Meta):
        anonymous_perms = ["view"]
        authenticated_perms = ["inherit"]
        container_path = "application-component-parameters/"
        owner_field = "component__application__creator"
        owner_perms = ["inherit", "change", "delete"]
        rdf_context = {
            "component": "sib:dependency",
            "key": "sib:key",
            "value": "sib:value",
        }
        rdf_type = "sib:parameter"
        serializer_fields = ["@id", "key", "value"]
        superuser_perms = ["inherit"]
        verbose_name = _("component parameter")
        verbose_name_plural = _("component parameters")


class ApplicationPackage(Model):
    application = models.ForeignKey(
        Application,
        on_delete=models.CASCADE,
        related_name="packages",
        null=True,
        blank=True,
    )
    package = models.ForeignKey(
        Package,
        on_delete=models.CASCADE,
        related_name="applications",
        null=True,
        blank=True,
    )

    def __str__(self):
        try:
            return "{} + {} ({})".format(
                self.application.friendly_name, self.package.friendly_name, self.urlid
            )
        except:
            return self.urlid

    class Meta(Model.Meta):
        anonymous_perms = ["view"]
        authenticated_perms = ["inherit"]
        container_path = "application-packages/"
        nested_fields = ["package", "parameters"]
        owner_field = "application__creator"
        owner_perms = ["inherit", "change", "delete"]
        rdf_context = {
            "application": "sib:application",
            "package": "sib:package",
        }
        rdf_type = "sib:dependency"
        serializer_fields = ["@id", "package", "parameters"]
        superuser_perms = ["inherit"]
        verbose_name = _("application package")
        verbose_name_plural = _("application packages")

    def save(self, *args, **kwargs):
        if (
            not self.pk
            and ApplicationPackage.objects.filter(
                package=self.package, application=self.application
            ).exists()
        ):
            return

        super(ApplicationPackage, self).save(*args, **kwargs)


class ApplicationPackageParameter(Model):
    package = models.ForeignKey(
        ApplicationPackage,
        on_delete=models.CASCADE,
        related_name="parameters",
        null=True,
        blank=True,
    )
    key = models.CharField(max_length=255, blank=True, null=True)
    value = models.CharField(max_length=255, blank=True, null=True)

    def __str__(self):
        try:
            return "{} -> {} ({})".format(
                self.package.application.friendly_name, self.package.package.friendly_name, self.urlid
            )
        except:
            return self.urlid

    class Meta(Model.Meta):
        anonymous_perms = ["view"]
        authenticated_perms = ["inherit"]
        container_path = "application-package-parameters/"
        owner_field = "package__application__creator"
        owner_perms = ["inherit", "change", "delete"]
        rdf_context = {
            "package": "sib:dependency",
            "key": "sib:key",
            "value": "sib:value",
        }
        rdf_type = "sib:parameter"
        serializer_fields = ["@id", "key", "value"]
        superuser_perms = ["inherit"]
        verbose_name = _("package parameter")
        verbose_name_plural = _("package parameters")


class ComponentExtension(Model):
    base_component = models.ForeignKey(
        ApplicationComponent,
        on_delete=models.CASCADE,
        related_name="extensions",
        null=True,
        blank=True,
    )
    component = models.ForeignKey(
        Component,
        on_delete=models.CASCADE,
        related_name="extends",
        null=True,
        blank=True,
    )

    def __str__(self):
        try:
            return "{} + {} ({})".format(
                self.base_component.application.friendly_name, self.component.friendly_name, self.urlid
            )
        except:
            return self.urlid

    class Meta(Model.Meta):
        anonymous_perms = ["view"]
        authenticated_perms = ["inherit"]
        container_path = "component-extensions/"
        nested_fields = ["component", "base_component"]
        owner_field = "base_component__application__creator"
        owner_perms = ["inherit", "change", "delete"]
        rdf_context = {
            "base_component": "sib:dependency",
            "component": "sib:component",
        }
        rdf_type = "sib:dependency"
        serializer_fields = ["@id", "component"]
        superuser_perms = ["inherit"]
        verbose_name = _("component extension")
        verbose_name_plural = _("component extensions")

    def save(self, *args, **kwargs):
        if (
            not self.pk
            and ComponentExtension.objects.filter(
                base_component=self.base_component, component=self.component
            ).exists()
        ):
            return

        super(ComponentExtension, self).save(*args, **kwargs)


class ComponentExtensionParameter(Model):
    component = models.ForeignKey(
        ComponentExtension,
        on_delete=models.CASCADE,
        related_name="parameters",
        null=True,
        blank=True,
    )
    key = models.CharField(max_length=255, blank=True, null=True)
    value = models.CharField(max_length=255, blank=True, null=True)

    def __str__(self):
        try:
            return "{} -> {} ({})".format(
                self.component.base_component.application.friendly_name, self.component.component.friendly_name, self.urlid
            )
        except:
            return self.urlid

    class Meta(Model.Meta):
        anonymous_perms = ["view"]
        authenticated_perms = ["inherit"]
        container_path = "component-extension-parameters/"
        owner_field = "component__base_component__application__creator"
        owner_perms = ["inherit", "change", "delete"]
        rdf_context = {
            "component": "sib:dependency",
            "key": "sib:key",
            "value": "sib:value",
        }
        rdf_type = "sib:parameter"
        serializer_fields = ["@id", "key", "value"]
        superuser_perms = ["inherit"]
        verbose_name = _("component extension parameter")
        verbose_name_plural = _("component extension parameters")


@receiver(pre_save, sender=Application)
@receiver(pre_save, sender=ApplicationTemplate)
def pre_save_slugify(sender, instance, **kwargs):
    if not instance.urlid or instance.urlid.startswith(settings.SITE_URL):
        if getattr(instance, Model.slug_field(instance)) != slugify(
            instance.friendly_name
        ):
            if (
                sender.objects.local()
                .filter(slug=slugify(instance.friendly_name))
                .count()
                > 0
            ):
                raise ValidationError(sender.__name__ + str(_(" must be unique")))
            setattr(
                instance, Model.slug_field(instance), slugify(instance.friendly_name)
            )
            setattr(instance, "urlid", "")
    else:
        # Is a distant object, generate a random slug
        setattr(instance, Model.slug_field(instance), uuid.uuid4().hex.upper()[0:8])
