from django.db import models
from django.db.models import CASCADE, Q
from typing_extensions import override

from zerver.models import Realm
from zilencer.models import RemoteRealm, RemoteZulipServer


class Customer(models.Model):
    """
    This model primarily serves to connect a Realm with
    the corresponding Stripe customer object for payment purposes
    and the active plan, if any.
    """

    # The actual model object that this customer is associated
    # with. Exactly one of the following will be non-null.
    realm = models.OneToOneField(Realm, on_delete=CASCADE, null=True)
    remote_realm = models.OneToOneField(RemoteRealm, on_delete=CASCADE, null=True)
    remote_server = models.OneToOneField(RemoteZulipServer, on_delete=CASCADE, null=True)

    stripe_customer_id = models.CharField(max_length=255, null=True, unique=True)
    sponsorship_pending = models.BooleanField(default=False)

    # Discounted price for required_plan_tier in cents.
    # We treat 0 as no discount. Not using `null` here keeps the
    # checks simpler and avoids the cases where we forget to
    # check for both `null` and 0.
    monthly_discounted_price = models.IntegerField(default=0, null=False)
    annual_discounted_price = models.IntegerField(default=0, null=False)

    minimum_licenses = models.PositiveIntegerField(null=True)
    # Used for limiting discounted price or a fixed_price
    # to be used only for a particular CustomerPlan tier.
    required_plan_tier = models.SmallIntegerField(null=True)
    # Some non-profit organizations on manual license management pay
    # only for their paid employees.  We don't prevent these
    # organizations from adding more users than the number of licenses
    # they purchased.
    exempt_from_license_number_check = models.BooleanField(default=False)

    # In cents.
    flat_discount = models.IntegerField(default=2000)
    # Number of months left in the flat discount period.
    flat_discounted_months = models.IntegerField(default=0)

    class Meta:
        # Enforce that at least one of these is set.
        constraints = [
            models.CheckConstraint(
                condition=Q(realm__isnull=False)
                | Q(remote_server__isnull=False)
                | Q(remote_realm__isnull=False),
                name="has_associated_model_object",
            )
        ]

    @override
    def __str__(self) -> str:
        if self.realm is not None:
            return f"{self.realm!r} (stripe: {self.stripe_customer_id})"
        elif self.remote_realm is not None:
            return f"{self.remote_realm!r} (stripe: {self.stripe_customer_id})"
        else:
            return f"{self.remote_server!r} (stripe: {self.stripe_customer_id})"

    def get_discounted_price_for_plan(self, plan_tier: int, schedule: int) -> int | None:
        from corporate.models.plans import CustomerPlan

        if plan_tier != self.required_plan_tier:
            return None

        if schedule == CustomerPlan.BILLING_SCHEDULE_ANNUAL:
            return self.annual_discounted_price

        assert schedule == CustomerPlan.BILLING_SCHEDULE_MONTHLY
        return self.monthly_discounted_price


def get_customer_by_realm(realm: Realm) -> Customer | None:
    return Customer.objects.filter(realm=realm).first()


def get_customer_by_remote_server(remote_server: RemoteZulipServer) -> Customer | None:
    return Customer.objects.filter(remote_server=remote_server).first()


def get_customer_by_remote_realm(remote_realm: RemoteRealm) -> Customer | None:  # nocoverage
    return Customer.objects.filter(remote_realm=remote_realm).first()
