# subscriptions/models.py
import uuid
from datetime import date, timedelta
from decimal import Decimal

from django.db import models
from django.utils.text import slugify

from companies.models import Company, Plan


# ─────────────────────────────────────────────────────────────
# BILLING CYCLE HELPERS
# ─────────────────────────────────────────────────────────────

BILLING_CYCLE_CHOICES = [
    ('monthly',    'Monthly (30 days)'),
    ('bimonthly',  'Every 2 Months (60 days)'),
    ('trimonthly', 'Every 3 Months (90 days)'),
    ('yearly',     'Yearly (365 days)'),
]

BILLING_CYCLE_DAYS = {
    'monthly':    30,
    'bimonthly':  60,
    'trimonthly': 90,
    'yearly':     365,
}

PAYMENT_STATUS_CHOICES = [
    ('pending', 'Pending'),
    ('paid',    'Paid'),
    ('failed',  'Failed'),
    ('trial',   'Trial'),
]

TRANSACTION_STATUS_CHOICES = [
    ('pending',   'Pending'),
    ('paid',      'Paid'),
    ('failed',    'Failed'),
    ('cancelled', 'Cancelled'),
]


# ─────────────────────────────────────────────────────────────
# SUBSCRIPTION
# ─────────────────────────────────────────────────────────────

class Subscription(models.Model):
    """
    Active subscription record for a company.
    One subscription is active at a time (is_active=True).
    Past subscriptions are kept for audit.
    """
    company = models.ForeignKey(
        Company,
        on_delete=models.CASCADE,
        related_name='subscriptions',
        db_index=True,
    )
    plan = models.ForeignKey(
        Plan,
        on_delete=models.PROTECT,
        related_name='subscriptions',
    )
    billing_cycle = models.CharField(
        max_length=15,
        choices=BILLING_CYCLE_CHOICES,
        default='monthly',
    )

    start_date  = models.DateField(default=date.today)
    expiry_date = models.DateField(null=True, blank=True)

    is_trial  = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)

    payment_status = models.CharField(
        max_length=10,
        choices=PAYMENT_STATUS_CHOICES,
        default='trial',
    )

    # For downgrades: the plan to switch to at end of cycle
    pending_downgrade_plan = models.ForeignKey(
        Plan,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='pending_downgrades',
    )

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

    class Meta:
        verbose_name = 'Subscription'
        verbose_name_plural = 'Subscriptions'
        ordering = ['-created_at']

    def __str__(self):
        trial_str = ' [TRIAL]' if self.is_trial else ''
        return f"{self.company.name} → {self.plan.name}{trial_str} (expires {self.expiry_date})"

    @property
    def is_expired(self):
        if not self.expiry_date:
            return False
        return self.expiry_date < date.today()

    @property
    def days_remaining(self):
        if not self.expiry_date:
            return None
        delta = (self.expiry_date - date.today()).days
        return max(delta, 0)


# ─────────────────────────────────────────────────────────────
# PAYMENT TRANSACTION
# ─────────────────────────────────────────────────────────────

def _generate_reference():
    """Generate a unique internal payment reference."""
    return f"SWP-{uuid.uuid4().hex[:12].upper()}"


class PaymentTransaction(models.Model):
    """
    Every Monnify payment attempt, successful or otherwise.
    """
    company      = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='payment_transactions', db_index=True)
    subscription = models.ForeignKey(Subscription, on_delete=models.SET_NULL, null=True, blank=True, related_name='transactions')
    plan         = models.ForeignKey(Plan, on_delete=models.PROTECT, related_name='transactions')
    billing_cycle = models.CharField(max_length=15, choices=BILLING_CYCLE_CHOICES, default='monthly')

    amount         = models.DecimalField(max_digits=12, decimal_places=2)
    discount_amount = models.DecimalField(max_digits=12, decimal_places=2, default=Decimal('0.00'))
    final_amount    = models.DecimalField(max_digits=12, decimal_places=2, default=Decimal('0.00'))

    # Gateway
    GATEWAY_CHOICES = [
        ('monnify',  'Monnify'),
        ('paystack', 'Paystack'),
        ('free',     'Free / Bypass'),
    ]
    gateway           = models.CharField(max_length=20, choices=GATEWAY_CHOICES, default='monnify')
    gateway_reference = models.CharField(max_length=200, blank=True, help_text='Gateway-side transaction reference')

    # Reference IDs
    payment_reference = models.CharField(max_length=50, unique=True, default=_generate_reference)
    monnify_reference = models.CharField(max_length=200, blank=True)  # kept for backwards compat

    # Promo
    promo_code_used = models.CharField(max_length=50, blank=True)

    # Status
    status = models.CharField(max_length=15, choices=TRANSACTION_STATUS_CHOICES, default='pending')
    payment_method = models.CharField(max_length=50, blank=True)

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

    # Raw gateway webhook payload for audit
    raw_webhook_payload = models.JSONField(default=dict, blank=True)

    class Meta:
        verbose_name = 'Payment Transaction'
        verbose_name_plural = 'Payment Transactions'
        ordering = ['-created_at']

    def __str__(self):
        return f"{self.payment_reference} | {self.company.name} | {self.get_status_display()} | ₦{self.final_amount}"

    def save(self, *args, **kwargs):
        if not self.final_amount:
            self.final_amount = self.amount - self.discount_amount
        super().save(*args, **kwargs)


# ─────────────────────────────────────────────────────────────
# SAAS PROMO CODE  (global subscription discounts, not POS promos)
# ─────────────────────────────────────────────────────────────

class SaasPromoCode(models.Model):
    DISCOUNT_TYPE_CHOICES = [
        ('percentage',    'Percentage (%)'),
        ('fixed_amount',  'Fixed Amount (₦)'),
    ]

    code           = models.CharField(max_length=50, unique=True, help_text="Case-insensitive promo code")
    description    = models.CharField(max_length=300, blank=True)
    discount_type  = models.CharField(max_length=15, choices=DISCOUNT_TYPE_CHOICES, default='percentage')
    discount_value = models.DecimalField(max_digits=10, decimal_places=2)

    valid_from  = models.DateField(null=True, blank=True)
    valid_until = models.DateField(null=True, blank=True)

    max_usage   = models.PositiveIntegerField(default=0, help_text="0 = unlimited")
    usage_count = models.PositiveIntegerField(default=0)

    # Restrict to specific plans (blank = valid for all plans)
    allowed_plans = models.ManyToManyField(Plan, blank=True, related_name='saas_promos')

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

    class Meta:
        verbose_name = 'SaaS Promo Code'
        verbose_name_plural = 'SaaS Promo Codes'
        ordering = ['-created_at']

    def __str__(self):
        return f"{self.code} ({self.get_discount_type_display()}: {self.discount_value})"

    def save(self, *args, **kwargs):
        self.code = self.code.upper().strip()
        super().save(*args, **kwargs)

    @property
    def is_expired(self):
        if not self.valid_until:
            return False
        return self.valid_until < date.today()

    @property
    def is_not_yet_valid(self):
        if not self.valid_from:
            return False
        return self.valid_from > date.today()

    @property
    def is_exhausted(self):
        return self.max_usage > 0 and self.usage_count >= self.max_usage

    @property
    def is_valid(self):
        return (
            self.is_active
            and not self.is_expired
            and not self.is_not_yet_valid
            and not self.is_exhausted
        )

    def calculate_discount(self, amount: Decimal) -> Decimal:
        """Return discount amount for the given subscription price."""
        if self.discount_type == 'percentage':
            disc = (self.discount_value / Decimal('100')) * amount
            return min(disc, amount)
        else:  # fixed_amount
            return min(self.discount_value, amount)
