import rules
import urlman
from django.conf import settings
from django.db import models
from django.utils.functional import cached_property
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from django_scopes import ScopedManager, scopes_disabled

from servala.core import rules as perms
from servala.core.models.mixins import ServalaModelMixin


class Organization(ServalaModelMixin, models.Model):
    name = models.CharField(max_length=100, verbose_name=_("Name"))

    billing_entity = models.ForeignKey(
        to="BillingEntity",
        on_delete=models.PROTECT,
        related_name="organizations",
        verbose_name=_("Billing entity"),
        null=True,  # TODO: billing entity should be required
    )
    origin = models.ForeignKey(
        to="OrganizationOrigin",
        on_delete=models.PROTECT,
        related_name="organizations",
        verbose_name=_("Origin"),
    )

    members = models.ManyToManyField(
        to="core.User",
        through="OrganizationMembership",
        related_name="organizations",
        verbose_name=_("Members"),
    )

    class urls(urlman.Urls):
        base = "/org/{self.slug}/"
        details = "{base}details/"
        services = "{base}services/"

    @cached_property
    def slug(self):
        return f"{slugify(self.name)}-{self.id}"

    def get_absolute_url(self):
        return self.urls.base

    def set_owner(self, user):
        with scopes_disabled():
            OrganizationMembership.objects.filter(user=user, organization=self).delete()
            OrganizationMembership.objects.create(
                user=user, organization=self, role=OrganizationRole.OWNER
            )

    @classmethod
    def create_organization(cls, instance, owner):
        try:
            instance.origin
        except Exception:
            instance.origin = OrganizationOrigin.objects.get(
                pk=settings.SERVALA_DEFAULT_ORIGIN
            )
        instance.save()
        instance.set_owner(owner)
        return instance

    class Meta:
        verbose_name = _("Organization")
        verbose_name_plural = _("Organizations")
        rules_permissions = {
            "view": rules.is_staff | perms.is_organization_member,
            "change": rules.is_staff | perms.is_organization_admin,
            "delete": rules.is_staff | perms.is_organization_owner,
            "add": rules.is_authenticated,
        }

    def __str__(self):
        return self.name


class BillingEntity(ServalaModelMixin, models.Model):
    """
    Every organization has a billing entity, though billing entities
    may be shared across different organizations.
    """

    name = models.CharField(max_length=100, verbose_name=_("Name"))
    description = models.TextField(blank=True, verbose_name=_("Description"))
    erp_reference = models.CharField(
        max_length=100, blank=True, verbose_name=_("ERP reference")
    )

    class Meta:
        verbose_name = _("Billing entity")
        verbose_name_plural = _("Billing entities")

    def __str__(self):
        return self.name


class OrganizationOrigin(ServalaModelMixin, models.Model):
    """
    Every organization has an origin, though origins may be
    shared across different organizations. The default origin
    (by PK) is configured in the settings as SERVALA_DEFAULT_ORIGIN.
    """

    name = models.CharField(max_length=100, verbose_name=_("Name"))
    description = models.TextField(blank=True, verbose_name=_("Description"))

    class Meta:
        verbose_name = _("Organization origin")
        verbose_name_plural = _("Organization origins")

    def __str__(self):
        return self.name


class OrganizationRole(models.TextChoices):
    MEMBER = "member", _("Member")
    ADMIN = "admin", _("Administrator")
    OWNER = "owner", _("Owner")


class OrganizationMembership(ServalaModelMixin, models.Model):
    """Through-model for the many-to-many relationship between organizations and users."""

    user = models.ForeignKey(
        to="core.User",
        on_delete=models.CASCADE,
        related_name="organization_memberships",
        verbose_name=_("User"),
    )
    organization = models.ForeignKey(
        to=Organization,
        on_delete=models.CASCADE,
        related_name="memberships",
        verbose_name=_("Organization"),
    )
    date_joined = models.DateTimeField(
        verbose_name=_("Date joined"), auto_now_add=True, editable=False
    )
    role = models.CharField(
        max_length=20,
        choices=OrganizationRole.choices,
        default=OrganizationRole.MEMBER,
        verbose_name=_("Role"),
    )

    objects = ScopedManager(organization="organization")

    class Meta:
        verbose_name = _("Organization membership")
        verbose_name_plural = _("Organization memberships")

    def __str__(self):
        return f"{self.user} in {self.organization} as {self.role}"
