"""
Billing models for subscription, entitlement, and invoicing system.

All models are in the public schema (SHARED_APPS) with FK to Tenant.
This enables centralized plan management and simpler queries.
"""

from datetime import timedelta

from django.db import models
from django.utils import timezone


class Feature(models.Model):
    """
    Reusable capability that can be granted via plans, add-ons, or overrides.

    Examples:
    - Boolean: 'sms_notifications', 'advanced_reporting', 'api_access'
    - Integer: 'max_users', 'max_resources', 'monthly_sms_limit'
    """

    FEATURE_TYPE_CHOICES = [
        ("boolean", "Boolean"),
        ("integer", "Integer Limit"),
    ]

    CATEGORY_CHOICES = [
        ("limits", "Limits"),
        ("payments", "Payments & Revenue"),
        ("communication", "Communication"),
        ("customization", "Customization"),
        ("plugins", "Plugins & Automation"),
        ("advanced", "Advanced Features"),
        ("scheduling", "Scheduling"),
        ("enterprise", "Enterprise & Security"),
    ]

    code = models.CharField(max_length=100, unique=True, db_index=True)
    name = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    feature_type = models.CharField(max_length=20, choices=FEATURE_TYPE_CHOICES)

    # Dynamic feature management
    category = models.CharField(
        max_length=50,
        choices=CATEGORY_CHOICES,
        default="advanced",
        help_text="UI grouping category"
    )
    tenant_field_name = models.CharField(
        max_length=100,
        blank=True,
        help_text="Corresponding field on Tenant model (e.g., 'can_use_sms_reminders')"
    )
    display_order = models.PositiveIntegerField(
        default=100,
        help_text="Order within category (lower = first)"
    )
    is_overridable = models.BooleanField(
        default=True,
        help_text="Can this feature be overridden at tenant level?"
    )
    depends_on = models.ForeignKey(
        'self',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='dependents',
        help_text="Parent feature this depends on (e.g., tasks depends on plugins)"
    )

    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["category", "display_order", "name"]

    def __str__(self):
        return self.name


class Plan(models.Model):
    """
    Logical plan grouping (Free, Starter, Pro, Enterprise).

    Plans are the marketing-level concept. Each Plan can have multiple
    PlanVersions for different pricing periods, promotions, etc.
    """

    code = models.CharField(max_length=50, unique=True, db_index=True)
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    display_order = models.PositiveIntegerField(default=0)
    is_active = models.BooleanField(default=True)

    max_pages = models.PositiveIntegerField(default=1)
    allow_custom_domains = models.BooleanField(default=False)
    max_custom_domains = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["display_order", "name"]

    def __str__(self):
        return self.name


class PlanVersion(models.Model):
    """
    Specific offer of a plan with pricing and feature set.

    Each PlanVersion is a concrete billable offer. Examples:
    - "Pro Plan v1" (original pricing)
    - "Pro Plan - 2024 Holiday Promo" (20% off)
    - "Enterprise v2" (new feature set)

    Legacy versions (is_legacy=True) are hidden from new signups but
    existing subscribers can continue using them (grandfathering).

    IMPORTANT: Features/permissions/limits are stored via the PlanFeature
    M2M relationship, not as direct fields. Use EntitlementService to
    resolve what a tenant can do based on their subscription.
    """

    plan = models.ForeignKey(Plan, on_delete=models.CASCADE, related_name="versions")
    version = models.PositiveIntegerField(default=1)
    name = models.CharField(max_length=200)

    # Visibility
    is_public = models.BooleanField(default=True)
    is_legacy = models.BooleanField(default=False)

    # Availability window (null = no constraint)
    starts_at = models.DateTimeField(null=True, blank=True)
    ends_at = models.DateTimeField(null=True, blank=True)

    # Pricing (in cents / minor currency units)
    price_monthly_cents = models.PositiveIntegerField(default=0)
    price_yearly_cents = models.PositiveIntegerField(default=0)

    # Transaction fees (platform revenue from tenant transactions)
    transaction_fee_percent = models.DecimalField(
        max_digits=5, decimal_places=2, default=4.0,
        help_text="Platform transaction fee percentage (e.g., 4.0 = 4%)"
    )
    transaction_fee_fixed_cents = models.PositiveIntegerField(
        default=40,
        help_text="Platform transaction fixed fee in cents (e.g., 40 = $0.40)"
    )

    # Trial period
    trial_days = models.PositiveIntegerField(
        default=0,
        help_text="Number of trial days for new subscribers"
    )

    # Communication pricing (cost to tenant when using these features)
    # Note: Whether the feature is ENABLED is controlled by Feature flags
    sms_price_per_message_cents = models.PositiveIntegerField(
        default=3,
        help_text="Price per SMS message in cents (if SMS feature enabled)"
    )
    masked_calling_price_per_minute_cents = models.PositiveIntegerField(
        default=5,
        help_text="Price per minute of masked calling in cents (if enabled)"
    )
    proxy_number_monthly_fee_cents = models.PositiveIntegerField(
        default=200,
        help_text="Monthly fee per proxy phone number in cents (if enabled)"
    )

    # Default credit settings for new businesses on this plan
    default_auto_reload_enabled = models.BooleanField(default=False)
    default_auto_reload_threshold_cents = models.PositiveIntegerField(
        default=1000,
        help_text="Reload when balance falls below this amount (in cents)"
    )
    default_auto_reload_amount_cents = models.PositiveIntegerField(
        default=2500,
        help_text="Amount to add when auto-reloading (in cents)"
    )

    # Display settings (for marketing/pricing pages)
    is_most_popular = models.BooleanField(default=False)
    show_price = models.BooleanField(default=True)

    # Marketing features list (display-only strings for pricing page)
    marketing_features = models.JSONField(
        default=list, blank=True,
        help_text="List of feature descriptions for marketing display"
    )

    # Stripe integration
    stripe_product_id = models.CharField(max_length=100, blank=True)
    stripe_price_id_monthly = models.CharField(max_length=100, blank=True)
    stripe_price_id_yearly = models.CharField(max_length=100, blank=True)

    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ["plan", "version"]
        ordering = ["plan", "-version"]

    def __str__(self):
        return self.name

    @property
    def is_available(self) -> bool:
        """Check if this version is available for new signups."""
        if not self.is_public or self.is_legacy:
            return False

        now = timezone.now()
        if self.starts_at and now < self.starts_at:
            return False
        if self.ends_at and now > self.ends_at:
            return False

        return True


class PlanFeature(models.Model):
    """
    Maps a PlanVersion to Features with specific values.

    For boolean features, use bool_value.
    For integer features, use int_value.
    """

    plan_version = models.ForeignKey(
        PlanVersion, on_delete=models.CASCADE, related_name="features"
    )
    feature = models.ForeignKey(Feature, on_delete=models.CASCADE)

    bool_value = models.BooleanField(null=True, blank=True)
    int_value = models.PositiveIntegerField(null=True, blank=True)

    class Meta:
        unique_together = ["plan_version", "feature"]

    def __str__(self):
        return f"{self.plan_version.name} - {self.feature.name}"

    def get_value(self):
        """Return the appropriate value based on feature type."""
        if self.feature.feature_type == "boolean":
            return self.bool_value
        return self.int_value


class Subscription(models.Model):
    """
    A business's active subscription to a PlanVersion.

    This is a public schema model with FK to Tenant, enabling
    centralized billing management.
    """

    STATUS_CHOICES = [
        ("trial", "Trial"),
        ("active", "Active"),
        ("past_due", "Past Due"),
        ("canceled", "Canceled"),
    ]

    # FK to Tenant (public schema)
    business = models.OneToOneField(
        "core.Tenant",
        on_delete=models.CASCADE,
        related_name="billing_subscription",
    )
    plan_version = models.ForeignKey(PlanVersion, on_delete=models.PROTECT)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="active")

    started_at = models.DateTimeField(auto_now_add=True)
    current_period_start = models.DateTimeField()
    current_period_end = models.DateTimeField()
    trial_ends_at = models.DateTimeField(null=True, blank=True)
    canceled_at = models.DateTimeField(null=True, blank=True)

    # Stripe integration
    stripe_subscription_id = models.CharField(max_length=100, blank=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return f"{self.business.name} - {self.plan_version.name}"

    @property
    def is_active(self) -> bool:
        """Check if subscription grants access to features."""
        return self.status in ("active", "trial")


class AddOnProduct(models.Model):
    """
    Purchasable add-on product that grants additional features.

    Add-ons can be:
    - Monthly recurring (price_monthly_cents > 0)
    - One-time purchase (price_one_time_cents > 0)

    Stackable add-ons can be purchased multiple times and their integer
    feature values compound (e.g., 3x "5 extra resources" = +15 resources).
    Non-stackable add-ons can only be purchased once per subscription.
    """

    code = models.CharField(max_length=50, unique=True, db_index=True)
    name = models.CharField(max_length=200)
    description = models.TextField(blank=True)

    # Pricing (in cents)
    price_monthly_cents = models.PositiveIntegerField(default=0)
    price_one_time_cents = models.PositiveIntegerField(default=0)

    # Stripe integration
    stripe_product_id = models.CharField(max_length=100, blank=True)
    stripe_price_id = models.CharField(max_length=100, blank=True)

    # Stacking behavior
    is_stackable = models.BooleanField(
        default=False,
        help_text="If True, can be purchased multiple times. Integer features compound.",
    )

    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["name"]

    def __str__(self):
        return self.name


class AddOnFeature(models.Model):
    """
    Maps an AddOnProduct to Features.

    Similar to PlanFeature, but for add-on products.
    """

    addon = models.ForeignKey(
        AddOnProduct, on_delete=models.CASCADE, related_name="features"
    )
    feature = models.ForeignKey(Feature, on_delete=models.CASCADE)

    bool_value = models.BooleanField(null=True, blank=True)
    int_value = models.PositiveIntegerField(null=True, blank=True)

    class Meta:
        unique_together = ["addon", "feature"]

    def __str__(self):
        return f"{self.addon.name} - {self.feature.name}"

    def get_value(self):
        """Return the appropriate value based on feature type."""
        if self.feature.feature_type == "boolean":
            return self.bool_value
        return self.int_value


class SubscriptionAddOn(models.Model):
    """
    Links an add-on to a subscription.

    Tracks when add-ons were activated, when they expire,
    and their Stripe subscription item ID.

    For stackable add-ons, quantity tracks how many are purchased.
    Integer feature values are multiplied by quantity during entitlement resolution.
    """

    STATUS_CHOICES = [
        ("trial", "Trial"),
        ("active", "Active"),
        ("canceled", "Canceled"),
    ]

    subscription = models.ForeignKey(
        Subscription, on_delete=models.CASCADE, related_name="addons"
    )
    addon = models.ForeignKey(AddOnProduct, on_delete=models.PROTECT)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="active")

    # Quantity for stackable add-ons (e.g., 3x "5 extra resources")
    quantity = models.PositiveIntegerField(
        default=1,
        help_text="Number of units purchased. Only applies to stackable add-ons.",
    )

    activated_at = models.DateTimeField(auto_now_add=True)
    expires_at = models.DateTimeField(null=True, blank=True)
    canceled_at = models.DateTimeField(null=True, blank=True)

    # Stripe integration
    stripe_subscription_item_id = models.CharField(max_length=100, blank=True)

    class Meta:
        unique_together = ["subscription", "addon"]
        ordering = ["-activated_at"]

    def __str__(self):
        return f"{self.subscription.business.name} - {self.addon.name}"

    @property
    def is_active(self) -> bool:
        """Check if this add-on is currently active."""
        if self.status != "active":
            return False
        if self.expires_at and self.expires_at < timezone.now():
            return False
        return True


class EntitlementOverride(models.Model):
    """
    Per-business override for a specific feature.

    Overrides take highest precedence in entitlement resolution.
    Use cases:
    - Manual: Support grants temporary access
    - Promo: Marketing promotion
    - Support: Technical support grant for debugging
    """

    SOURCE_CHOICES = [
        ("manual", "Manual Override"),
        ("promo", "Promotional"),
        ("support", "Support Grant"),
    ]

    # FK to Tenant (public schema)
    business = models.ForeignKey(
        "core.Tenant",
        on_delete=models.CASCADE,
        related_name="entitlement_overrides",
    )
    feature = models.ForeignKey(Feature, on_delete=models.CASCADE)
    source = models.CharField(max_length=20, choices=SOURCE_CHOICES)

    bool_value = models.BooleanField(null=True, blank=True)
    int_value = models.PositiveIntegerField(null=True, blank=True)

    reason = models.TextField(blank=True)
    granted_by = models.ForeignKey(
        "users.User",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    expires_at = models.DateTimeField(null=True, blank=True)

    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ["business", "feature"]
        ordering = ["-created_at"]

    def __str__(self):
        return f"{self.business.name} - {self.feature.name} override"

    @property
    def is_active(self) -> bool:
        """Check if this override is currently active."""
        if self.expires_at and self.expires_at < timezone.now():
            return False
        return True

    def get_value(self):
        """Return the appropriate value based on feature type."""
        if self.feature.feature_type == "boolean":
            return self.bool_value
        return self.int_value


class Invoice(models.Model):
    """
    Immutable billing snapshot.

    Once status='paid', this record and its InvoiceLines are NEVER modified.
    This ensures historical accuracy for accounting and auditing.
    """

    STATUS_CHOICES = [
        ("draft", "Draft"),
        ("open", "Open"),
        ("paid", "Paid"),
        ("void", "Void"),
        ("refunded", "Refunded"),
    ]

    # FK to Tenant (public schema)
    business = models.ForeignKey(
        "core.Tenant",
        on_delete=models.CASCADE,
        related_name="invoices",
    )
    subscription = models.ForeignKey(
        Subscription,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="invoices",
    )

    # Billing period
    period_start = models.DateTimeField()
    period_end = models.DateTimeField()

    # Currency
    currency = models.CharField(max_length=3, default="USD")

    # Amounts (in cents - IMMUTABLE after paid)
    subtotal_amount = models.PositiveIntegerField(default=0)
    discount_amount = models.PositiveIntegerField(default=0)
    tax_amount = models.PositiveIntegerField(default=0)
    total_amount = models.PositiveIntegerField(default=0)

    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="draft")

    # Snapshots (IMMUTABLE - captured at invoice creation)
    plan_code_at_billing = models.CharField(max_length=50, blank=True)
    plan_name_at_billing = models.CharField(max_length=200, blank=True)
    plan_version_id_at_billing = models.PositiveIntegerField(null=True, blank=True)
    billing_address_snapshot = models.JSONField(default=dict, blank=True)
    tax_rate_snapshot = models.DecimalField(
        max_digits=5, decimal_places=4, default=0
    )

    # External
    stripe_invoice_id = models.CharField(max_length=100, blank=True)

    created_at = models.DateTimeField(auto_now_add=True)
    paid_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return f"Invoice {self.id} - {self.business.name} ({self.status})"


class InvoiceLine(models.Model):
    """
    Line item on an invoice.

    Each line represents a charge (plan, add-on, overage) or credit.
    Amounts are in cents.
    """

    LINE_TYPE_CHOICES = [
        ("plan", "Plan Subscription"),
        ("addon", "Add-On"),
        ("overage", "Usage Overage"),
        ("credit", "Credit"),
        ("adjustment", "Adjustment"),
    ]

    invoice = models.ForeignKey(
        Invoice, on_delete=models.CASCADE, related_name="lines"
    )
    line_type = models.CharField(max_length=20, choices=LINE_TYPE_CHOICES)
    description = models.CharField(max_length=500)

    quantity = models.PositiveIntegerField(default=1)
    unit_amount = models.IntegerField(default=0)  # Can be negative for credits
    subtotal_amount = models.IntegerField(default=0)
    tax_amount = models.IntegerField(default=0)
    total_amount = models.IntegerField(default=0)

    # Context (for audit trail)
    feature_code = models.CharField(max_length=100, blank=True)
    plan_version = models.ForeignKey(
        PlanVersion,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    addon = models.ForeignKey(
        AddOnProduct,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    metadata = models.JSONField(default=dict, blank=True)

    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["id"]

    def __str__(self):
        return f"{self.line_type}: {self.description} ({self.total_amount} cents)"


class TenantCustomTier(models.Model):
    """
    A custom tier for a specific tenant that overrides their plan's features.

    When present and active, the EntitlementService uses this instead of
    the subscription's plan features. The subscription plan is used for
    pricing only.

    If the subscription lapses (becomes inactive) for more than 30 days,
    the custom tier is deleted by the grace period cleanup task.
    """

    GRACE_PERIOD_DAYS = 30

    business = models.OneToOneField(
        "core.Tenant",
        on_delete=models.CASCADE,
        related_name="custom_tier",
    )

    # Audit fields
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    created_by = models.ForeignKey(
        "users.User",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="created_custom_tiers",
    )

    # Grace period tracking
    # If subscription becomes inactive, this tracks when grace period started
    grace_period_started_at = models.DateTimeField(null=True, blank=True)

    # All features stored as JSON (complete feature set)
    # Format: {"feature_code": value, ...}
    # e.g., {"max_users": 50, "can_use_plugins": true, "sms_enabled": true}
    features = models.JSONField(default=dict)

    notes = models.TextField(
        blank=True,
        help_text="Admin notes about why this custom tier exists",
    )

    class Meta:
        verbose_name = "Tenant Custom Tier"
        verbose_name_plural = "Tenant Custom Tiers"
        ordering = ["-updated_at"]

    def __str__(self):
        return f"Custom Tier for {self.business.name}"

    @property
    def is_active(self) -> bool:
        """
        Check if custom tier is still valid.

        Returns False if grace period has exceeded 30 days.
        """
        if self.grace_period_started_at:
            grace_end = self.grace_period_started_at + timedelta(
                days=self.GRACE_PERIOD_DAYS
            )
            if timezone.now() > grace_end:
                return False
        return True

    @property
    def days_until_expiry(self) -> int | None:
        """
        Returns number of days until grace period expires, or None if not in grace period.
        """
        if not self.grace_period_started_at:
            return None
        grace_end = self.grace_period_started_at + timedelta(
            days=self.GRACE_PERIOD_DAYS
        )
        remaining = grace_end - timezone.now()
        return max(0, remaining.days)
