"""
Communication Credits Models

Prepaid credit system for SMS and voice services.
Integrates with Twilio subaccounts for usage tracking.
"""
from django.db import models
from django.utils import timezone


class CommunicationCredits(models.Model):
    """
    Prepaid communication credits for a tenant.

    Credits are used for:
    - SMS reminders to customers and staff
    - Masked calling/SMS through proxy numbers
    - Voice minutes

    Usage is tracked via Twilio subaccounts and synced periodically.
    """
    tenant = models.OneToOneField(
        'core.Tenant',
        on_delete=models.CASCADE,
        related_name='communication_credits'
    )

    # Current balance (stored in cents for precision)
    balance_cents = models.IntegerField(
        default=0,
        help_text="Current credit balance in cents"
    )

    # Auto-reload settings
    auto_reload_enabled = models.BooleanField(
        default=False,
        help_text="Automatically reload credits when balance falls below threshold"
    )
    auto_reload_threshold_cents = models.IntegerField(
        default=1000,  # $10
        help_text="Reload when balance falls below this amount (cents)"
    )
    auto_reload_amount_cents = models.IntegerField(
        default=2500,  # $25
        help_text="Amount to reload (cents)"
    )

    # Notification settings
    low_balance_warning_cents = models.IntegerField(
        default=500,  # $5
        help_text="Send warning email when balance falls below this amount (cents)"
    )
    low_balance_warning_sent = models.BooleanField(
        default=False,
        help_text="Whether low balance warning has been sent (reset on reload)"
    )
    low_balance_warning_sent_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text="When the last low balance warning was sent"
    )

    # Stripe integration
    stripe_customer_id = models.CharField(
        max_length=255,
        blank=True,
        default='',
        help_text="Stripe Customer ID for this tenant"
    )
    stripe_payment_method_id = models.CharField(
        max_length=255,
        blank=True,
        default='',
        help_text="Stripe Payment Method ID for auto-reload"
    )

    # Twilio usage sync tracking
    last_twilio_sync_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text="When usage was last synced from Twilio"
    )
    twilio_sync_period_start = models.DateField(
        null=True,
        blank=True,
        help_text="Start of current Twilio billing period"
    )
    twilio_raw_usage_cents = models.IntegerField(
        default=0,
        help_text="Raw Twilio cost for current period (cents)"
    )
    billed_usage_cents = models.IntegerField(
        default=0,
        help_text="Amount already deducted from credits for current period (cents)"
    )

    # Lifetime stats
    total_loaded_cents = models.IntegerField(
        default=0,
        help_text="Total credits ever loaded (cents)"
    )
    total_spent_cents = models.IntegerField(
        default=0,
        help_text="Total credits ever spent (cents)"
    )

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

    class Meta:
        app_label = 'comms_credits'
        verbose_name = 'Communication Credits'
        verbose_name_plural = 'Communication Credits'

    def __str__(self):
        return f"{self.tenant.name} - ${self.balance_cents/100:.2f}"

    @property
    def balance(self):
        """Balance in dollars"""
        return self.balance_cents / 100

    @property
    def auto_reload_threshold(self):
        """Threshold in dollars"""
        return self.auto_reload_threshold_cents / 100

    @property
    def auto_reload_amount(self):
        """Reload amount in dollars"""
        return self.auto_reload_amount_cents / 100

    def deduct(self, amount_cents, description, reference_type=None, reference_id=None):
        """
        Deduct credits from balance.

        Args:
            amount_cents: Amount to deduct in cents
            description: Human-readable description
            reference_type: Type of reference (e.g., 'sms', 'voice', 'proxy_number')
            reference_id: External ID (e.g., Twilio SID)

        Returns:
            CreditTransaction if successful, None if insufficient balance
        """
        if self.balance_cents < amount_cents:
            return None

        self.balance_cents -= amount_cents
        self.total_spent_cents += amount_cents
        self.save(update_fields=['balance_cents', 'total_spent_cents', 'updated_at'])

        # Create transaction record
        transaction = CreditTransaction.objects.create(
            credits=self,
            amount_cents=-amount_cents,
            balance_after_cents=self.balance_cents,
            transaction_type='usage',
            description=description,
            reference_type=reference_type or '',
            reference_id=reference_id or '',
        )

        # Check thresholds
        self._check_thresholds()

        return transaction

    def add_credits(self, amount_cents, transaction_type='manual',
                    stripe_charge_id=None, description=None):
        """
        Add credits to balance.

        Args:
            amount_cents: Amount to add in cents
            transaction_type: 'manual', 'auto_reload', 'refund', 'adjustment'
            stripe_charge_id: Stripe charge ID if applicable
            description: Optional description
        """
        self.balance_cents += amount_cents
        self.total_loaded_cents += amount_cents
        self.low_balance_warning_sent = False  # Reset warning flag
        self.save(update_fields=[
            'balance_cents', 'total_loaded_cents',
            'low_balance_warning_sent', 'updated_at'
        ])

        # Create transaction record
        CreditTransaction.objects.create(
            credits=self,
            amount_cents=amount_cents,
            balance_after_cents=self.balance_cents,
            transaction_type=transaction_type,
            description=description or f"Credits added ({transaction_type})",
            stripe_charge_id=stripe_charge_id or '',
        )

    def _check_thresholds(self):
        """Check and trigger warnings/auto-reload."""
        # Low balance warning
        if (self.balance_cents <= self.low_balance_warning_cents
            and not self.low_balance_warning_sent):
            self._send_low_balance_warning()

        # Auto-reload
        if (self.auto_reload_enabled
            and self.balance_cents <= self.auto_reload_threshold_cents
            and self.stripe_payment_method_id):
            self._trigger_auto_reload()

    def _send_low_balance_warning(self):
        """Send low balance warning email."""
        from smoothschedule.communication.credits.tasks import send_low_balance_warning
        send_low_balance_warning.delay(self.id)

        self.low_balance_warning_sent = True
        self.low_balance_warning_sent_at = timezone.now()
        self.save(update_fields=['low_balance_warning_sent', 'low_balance_warning_sent_at'])

    def _trigger_auto_reload(self):
        """Trigger auto-reload of credits."""
        from smoothschedule.communication.credits.tasks import process_auto_reload
        process_auto_reload.delay(self.id)


class CreditTransaction(models.Model):
    """
    Transaction history for communication credits.

    Tracks all credit additions and deductions for auditing
    and billing reconciliation.
    """

    class TransactionType(models.TextChoices):
        MANUAL = 'manual', 'Manual Top-up'
        AUTO_RELOAD = 'auto_reload', 'Auto Reload'
        USAGE = 'usage', 'Usage'
        REFUND = 'refund', 'Refund'
        ADJUSTMENT = 'adjustment', 'Adjustment'
        PROMO = 'promo', 'Promotional Credit'

    credits = models.ForeignKey(
        CommunicationCredits,
        on_delete=models.CASCADE,
        related_name='transactions'
    )

    # Amount (positive = add, negative = deduct)
    amount_cents = models.IntegerField(
        help_text="Amount in cents (positive=credit, negative=debit)"
    )
    balance_after_cents = models.IntegerField(
        help_text="Balance after this transaction (cents)"
    )

    # Transaction type
    transaction_type = models.CharField(
        max_length=20,
        choices=TransactionType.choices,
        default=TransactionType.USAGE
    )

    # Description and reference
    description = models.CharField(
        max_length=255,
        blank=True,
        default=''
    )
    reference_type = models.CharField(
        max_length=50,
        blank=True,
        default='',
        help_text="Type: sms, voice, proxy_number, etc."
    )
    reference_id = models.CharField(
        max_length=100,
        blank=True,
        default='',
        help_text="External reference (Twilio SID, etc.)"
    )

    # Stripe integration
    stripe_charge_id = models.CharField(
        max_length=255,
        blank=True,
        default='',
        help_text="Stripe Charge/PaymentIntent ID"
    )

    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        app_label = 'comms_credits'
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['credits', '-created_at']),
            models.Index(fields=['transaction_type', '-created_at']),
        ]

    def __str__(self):
        sign = '+' if self.amount_cents > 0 else ''
        return f"{sign}${self.amount_cents/100:.2f} - {self.description}"

    @property
    def amount(self):
        """Amount in dollars"""
        return self.amount_cents / 100


class ProxyPhoneNumber(models.Model):
    """
    Pool of Twilio phone numbers for masked calling.

    Numbers can be:
    - Unassigned (in pool, available)
    - Assigned to a tenant (dedicated line)
    - Reserved for a specific session (temporary masking)
    """

    class Status(models.TextChoices):
        AVAILABLE = 'available', 'Available'
        ASSIGNED = 'assigned', 'Assigned to Tenant'
        RESERVED = 'reserved', 'Reserved for Session'
        INACTIVE = 'inactive', 'Inactive'

    phone_number = models.CharField(
        max_length=20,
        unique=True,
        help_text="E.164 format phone number"
    )
    twilio_sid = models.CharField(
        max_length=50,
        help_text="Twilio Phone Number SID"
    )

    # Status
    status = models.CharField(
        max_length=20,
        choices=Status.choices,
        default=Status.AVAILABLE
    )

    # Assignment
    assigned_tenant = models.ForeignKey(
        'core.Tenant',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='proxy_phone_numbers',
        help_text="Tenant this number is assigned to"
    )
    assigned_at = models.DateTimeField(
        null=True,
        blank=True
    )

    # Billing
    monthly_fee_cents = models.IntegerField(
        default=200,  # $2.00
        help_text="Monthly fee charged to tenant (cents)"
    )
    last_billed_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text="When this number was last billed"
    )

    # Metadata
    friendly_name = models.CharField(
        max_length=100,
        blank=True,
        default='',
        help_text="Friendly name for the number"
    )
    capabilities = models.JSONField(
        default=dict,
        blank=True,
        help_text="Twilio capabilities (voice, sms, mms)"
    )

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

    class Meta:
        app_label = 'comms_credits'
        ordering = ['phone_number']
        verbose_name = 'Proxy Phone Number'
        verbose_name_plural = 'Proxy Phone Numbers'

    def __str__(self):
        tenant_info = f" ({self.assigned_tenant.name})" if self.assigned_tenant else ""
        return f"{self.phone_number}{tenant_info}"

    def assign_to_tenant(self, tenant):
        """
        Assign this number to a tenant.

        Raises:
            PermissionError: If tenant doesn't have masked calling feature
        """
        from rest_framework.exceptions import PermissionDenied

        # Check feature permission
        if not tenant.has_feature('can_use_masked_phone_numbers'):
            raise PermissionDenied(
                "Your current plan does not include Masked Calling. "
                "Please upgrade your subscription to access this feature."
            )

        self.assigned_tenant = tenant
        self.assigned_at = timezone.now()
        self.status = self.Status.ASSIGNED
        self.save(update_fields=['assigned_tenant', 'assigned_at', 'status', 'updated_at'])

    def release(self):
        """Release this number back to the pool."""
        self.assigned_tenant = None
        self.assigned_at = None
        self.status = self.Status.AVAILABLE
        self.save(update_fields=['assigned_tenant', 'assigned_at', 'status', 'updated_at'])


class MaskedSession(models.Model):
    """
    Temporary masked communication session between parties.

    Links a customer and staff member through a proxy number
    for the duration of an appointment/event.
    """

    class Status(models.TextChoices):
        ACTIVE = 'active', 'Active'
        CLOSED = 'closed', 'Closed'
        EXPIRED = 'expired', 'Expired'

    tenant = models.ForeignKey(
        'core.Tenant',
        on_delete=models.CASCADE,
        related_name='masked_sessions'
    )

    # The event this session is for (stored as ID since Event is in tenant schema)
    event_id = models.IntegerField(
        null=True,
        blank=True,
        help_text="ID of the Event this session is for (in tenant schema)"
    )

    # Proxy number used for this session
    proxy_number = models.ForeignKey(
        ProxyPhoneNumber,
        on_delete=models.SET_NULL,
        null=True,
        related_name='sessions'
    )

    # Participant phone numbers
    customer_phone = models.CharField(
        max_length=20,
        help_text="Customer's real phone number"
    )
    staff_phone = models.CharField(
        max_length=20,
        help_text="Staff member's real phone number"
    )

    # Status
    status = models.CharField(
        max_length=20,
        choices=Status.choices,
        default=Status.ACTIVE
    )

    # Timing
    expires_at = models.DateTimeField(
        help_text="When this session automatically expires"
    )
    closed_at = models.DateTimeField(
        null=True,
        blank=True
    )

    # Usage tracking
    sms_count = models.IntegerField(
        default=0,
        help_text="Number of SMS messages sent through this session"
    )
    voice_seconds = models.IntegerField(
        default=0,
        help_text="Total voice call duration in seconds"
    )

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

    class Meta:
        app_label = 'comms_credits'
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['tenant', 'status']),
            models.Index(fields=['proxy_number', 'status']),
            models.Index(fields=['expires_at']),
        ]

    def __str__(self):
        return f"Session {self.id}: {self.customer_phone} <-> {self.staff_phone}"

    def is_active(self):
        """Check if session is still active."""
        if self.status != self.Status.ACTIVE:
            return False
        if timezone.now() >= self.expires_at:
            return False
        return True

    def close(self):
        """Close this session."""
        self.status = self.Status.CLOSED
        self.closed_at = timezone.now()
        self.save(update_fields=['status', 'closed_at', 'updated_at'])

        # Release proxy number back to pool if it was reserved for this session
        if self.proxy_number and self.proxy_number.status == ProxyPhoneNumber.Status.RESERVED:
            self.proxy_number.status = ProxyPhoneNumber.Status.AVAILABLE
            self.proxy_number.save(update_fields=['status', 'updated_at'])

    def get_destination_for_caller(self, caller_phone):
        """
        Determine where to route a call/SMS based on who's calling.

        Args:
            caller_phone: The phone number of the caller

        Returns:
            The phone number to forward to, or None if caller not in session
        """
        # Normalize phone numbers for comparison
        caller_normalized = caller_phone.replace('+', '').replace('-', '').replace(' ', '')
        customer_normalized = self.customer_phone.replace('+', '').replace('-', '').replace(' ', '')
        staff_normalized = self.staff_phone.replace('+', '').replace('-', '').replace(' ', '')

        if caller_normalized.endswith(customer_normalized) or customer_normalized.endswith(caller_normalized):
            return self.staff_phone
        elif caller_normalized.endswith(staff_normalized) or staff_normalized.endswith(caller_normalized):
            return self.customer_phone
        return None
