"""
Communication Credits API Views

API endpoints for managing prepaid communication credits.
Integrates with Stripe for payments and Twilio for phone numbers.
"""
import stripe
from django.conf import settings
from django.db import transaction
from django.utils import timezone
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from twilio.rest import Client as TwilioClient
from twilio.base.exceptions import TwilioRestException, TwilioException

from .models import CommunicationCredits, CreditTransaction, ProxyPhoneNumber

# Initialize Stripe
stripe.api_key = settings.STRIPE_SECRET_KEY


class TransactionPagination(PageNumberPagination):
    page_size = 20
    page_size_query_param = 'limit'
    max_page_size = 100


def get_or_create_credits(tenant):
    """Get or create CommunicationCredits for a tenant."""
    credits, created = CommunicationCredits.objects.get_or_create(
        tenant=tenant
    )
    return credits


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_credits_view(request):
    """
    Get current communication credits for the business.

    Returns the credit balance and settings.
    """
    tenant = request.tenant
    if not tenant:
        return Response(
            {'error': 'No business context'},
            status=status.HTTP_400_BAD_REQUEST
        )

    credits = get_or_create_credits(tenant)

    return Response({
        'id': credits.id,
        'balance_cents': credits.balance_cents,
        'auto_reload_enabled': credits.auto_reload_enabled,
        'auto_reload_threshold_cents': credits.auto_reload_threshold_cents,
        'auto_reload_amount_cents': credits.auto_reload_amount_cents,
        'low_balance_warning_cents': credits.low_balance_warning_cents,
        'low_balance_warning_sent': credits.low_balance_warning_sent,
        'stripe_payment_method_id': credits.stripe_payment_method_id,
        'last_twilio_sync_at': credits.last_twilio_sync_at,
        'total_loaded_cents': credits.total_loaded_cents,
        'total_spent_cents': credits.total_spent_cents,
        'created_at': credits.created_at,
        'updated_at': credits.updated_at,
    })


@api_view(['PATCH'])
@permission_classes([IsAuthenticated])
def update_settings_view(request):
    """
    Update credit settings (auto-reload, thresholds, etc.)
    """
    tenant = request.tenant
    if not tenant:
        return Response(
            {'error': 'No business context'},
            status=status.HTTP_400_BAD_REQUEST
        )

    credits = get_or_create_credits(tenant)

    # Allowed fields to update
    allowed_fields = [
        'auto_reload_enabled',
        'auto_reload_threshold_cents',
        'auto_reload_amount_cents',
        'low_balance_warning_cents',
        'stripe_payment_method_id',
    ]

    update_fields = ['updated_at']
    for field in allowed_fields:
        if field in request.data:
            setattr(credits, field, request.data[field])
            update_fields.append(field)

    credits.save(update_fields=update_fields)

    return Response({
        'id': credits.id,
        'balance_cents': credits.balance_cents,
        'auto_reload_enabled': credits.auto_reload_enabled,
        'auto_reload_threshold_cents': credits.auto_reload_threshold_cents,
        'auto_reload_amount_cents': credits.auto_reload_amount_cents,
        'low_balance_warning_cents': credits.low_balance_warning_cents,
        'low_balance_warning_sent': credits.low_balance_warning_sent,
        'stripe_payment_method_id': credits.stripe_payment_method_id,
        'last_twilio_sync_at': credits.last_twilio_sync_at,
        'total_loaded_cents': credits.total_loaded_cents,
        'total_spent_cents': credits.total_spent_cents,
        'created_at': credits.created_at,
        'updated_at': credits.updated_at,
    })


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def add_credits_view(request):
    """
    Add credits via Stripe payment.

    Expects:
    - amount_cents: Amount to add (in cents, minimum $5 = 500 cents)
    - payment_method_id: Stripe Payment Method ID
    - save_payment_method: Optional, whether to save for auto-reload
    """
    tenant = request.tenant
    if not tenant:
        return Response(
            {'error': 'No business context'},
            status=status.HTTP_400_BAD_REQUEST
        )

    amount_cents = request.data.get('amount_cents')
    payment_method_id = request.data.get('payment_method_id')
    save_payment_method = request.data.get('save_payment_method', False)

    if not amount_cents or amount_cents < 500:
        return Response(
            {'error': 'Minimum amount is $5.00 (500 cents)'},
            status=status.HTTP_400_BAD_REQUEST
        )

    if not payment_method_id:
        return Response(
            {'error': 'Payment method is required'},
            status=status.HTTP_400_BAD_REQUEST
        )

    credits = get_or_create_credits(tenant)

    try:
        # Get or create a Stripe customer for this tenant
        stripe_customer_id = _get_or_create_stripe_customer(credits, tenant, request.user)

        # Attach payment method to customer if not already attached
        try:
            stripe.PaymentMethod.attach(
                payment_method_id,
                customer=stripe_customer_id,
            )
        except stripe.error.InvalidRequestError as e:
            # Payment method might already be attached
            if 'already been attached' not in str(e):
                raise

        # Create payment intent
        payment_intent = stripe.PaymentIntent.create(
            amount=amount_cents,
            currency='usd',
            customer=stripe_customer_id,
            payment_method=payment_method_id,
            confirm=True,
            automatic_payment_methods={
                'enabled': True,
                'allow_redirects': 'never',
            },
            description=f'Communication credits for {tenant.name}',
            metadata={
                'tenant_id': str(tenant.id),
                'tenant_name': tenant.name,
                'type': 'communication_credits',
            },
        )

        if payment_intent.status == 'succeeded':
            # Add credits to balance
            with transaction.atomic():
                credits.add_credits(
                    amount_cents=amount_cents,
                    transaction_type='manual',
                    stripe_charge_id=payment_intent.id,
                    description=f'Added ${amount_cents/100:.2f} via Stripe'
                )

                # Save payment method for auto-reload if requested
                if save_payment_method:
                    credits.stripe_payment_method_id = payment_method_id
                    credits.save(update_fields=['stripe_payment_method_id', 'updated_at'])

            return Response({
                'success': True,
                'balance_cents': credits.balance_cents,
                'payment_intent_id': payment_intent.id,
            })

        elif payment_intent.status == 'requires_action':
            # 3D Secure or additional authentication required
            return Response({
                'requires_action': True,
                'payment_intent_client_secret': payment_intent.client_secret,
            })

        else:
            return Response(
                {'error': f'Payment failed: {payment_intent.status}'},
                status=status.HTTP_400_BAD_REQUEST
            )

    except stripe.error.CardError as e:
        return Response(
            {'error': e.user_message or 'Card was declined'},
            status=status.HTTP_400_BAD_REQUEST
        )
    except stripe.error.StripeError as e:
        return Response(
            {'error': 'Payment processing error. Please try again.'},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def create_payment_intent_view(request):
    """
    Create a Stripe PaymentIntent for credit purchase.

    Use this for payment flows that need client-side confirmation.
    Returns the client_secret for Stripe Elements.

    Expects:
    - amount_cents: Amount to add (in cents, minimum $5 = 500 cents)
    """
    tenant = request.tenant
    if not tenant:
        return Response(
            {'error': 'No business context'},
            status=status.HTTP_400_BAD_REQUEST
        )

    amount_cents = request.data.get('amount_cents')

    if not amount_cents or amount_cents < 500:
        return Response(
            {'error': 'Minimum amount is $5.00 (500 cents)'},
            status=status.HTTP_400_BAD_REQUEST
        )

    credits = get_or_create_credits(tenant)

    try:
        # Get or create a Stripe customer for this tenant
        stripe_customer_id = _get_or_create_stripe_customer(credits, tenant, request.user)

        # Create payment intent
        payment_intent = stripe.PaymentIntent.create(
            amount=amount_cents,
            currency='usd',
            customer=stripe_customer_id,
            automatic_payment_methods={
                'enabled': True,
            },
            description=f'Communication credits for {tenant.name}',
            metadata={
                'tenant_id': str(tenant.id),
                'tenant_name': tenant.name,
                'type': 'communication_credits',
            },
        )

        return Response({
            'client_secret': payment_intent.client_secret,
            'payment_intent_id': payment_intent.id,
        })

    except stripe.error.StripeError as e:
        return Response(
            {'error': 'Failed to create payment. Please try again.'},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def confirm_payment_view(request):
    """
    Confirm a payment after client-side processing.

    Called after Stripe Elements confirms the payment on the client.

    Expects:
    - payment_intent_id: The PaymentIntent ID
    - save_payment_method: Optional, whether to save for auto-reload
    """
    tenant = request.tenant
    if not tenant:
        return Response(
            {'error': 'No business context'},
            status=status.HTTP_400_BAD_REQUEST
        )

    payment_intent_id = request.data.get('payment_intent_id')
    save_payment_method = request.data.get('save_payment_method', False)

    if not payment_intent_id:
        return Response(
            {'error': 'Payment intent ID is required'},
            status=status.HTTP_400_BAD_REQUEST
        )

    try:
        # Retrieve the payment intent
        payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id)

        # Verify the payment is for this tenant
        if payment_intent.metadata.get('tenant_id') != str(tenant.id):
            return Response(
                {'error': 'Invalid payment'},
                status=status.HTTP_403_FORBIDDEN
            )

        if payment_intent.status != 'succeeded':
            return Response(
                {'error': f'Payment not completed: {payment_intent.status}'},
                status=status.HTTP_400_BAD_REQUEST
            )

        credits = get_or_create_credits(tenant)

        # Check if we've already processed this payment
        existing = CreditTransaction.objects.filter(
            credits=credits,
            stripe_charge_id=payment_intent_id
        ).exists()

        if existing:
            return Response({
                'success': True,
                'balance_cents': credits.balance_cents,
                'already_processed': True,
            })

        # Add credits
        with transaction.atomic():
            credits.add_credits(
                amount_cents=payment_intent.amount,
                transaction_type='manual',
                stripe_charge_id=payment_intent_id,
                description=f'Added ${payment_intent.amount/100:.2f} via Stripe'
            )

            # Save payment method for auto-reload if requested
            if save_payment_method and payment_intent.payment_method:
                credits.stripe_payment_method_id = payment_intent.payment_method
                credits.save(update_fields=['stripe_payment_method_id', 'updated_at'])

        return Response({
            'success': True,
            'balance_cents': credits.balance_cents,
        })

    except stripe.error.StripeError as e:
        return Response(
            {'error': 'Failed to confirm payment'},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def setup_payment_method_view(request):
    """
    Create a SetupIntent for saving a payment method for auto-reload.

    Returns the client_secret for Stripe Elements to collect card details
    without charging immediately.
    """
    tenant = request.tenant
    if not tenant:
        return Response(
            {'error': 'No business context'},
            status=status.HTTP_400_BAD_REQUEST
        )

    credits = get_or_create_credits(tenant)

    try:
        stripe_customer_id = _get_or_create_stripe_customer(credits, tenant, request.user)

        setup_intent = stripe.SetupIntent.create(
            customer=stripe_customer_id,
            automatic_payment_methods={
                'enabled': True,
            },
            metadata={
                'tenant_id': str(tenant.id),
                'type': 'communication_credits_auto_reload',
            },
        )

        return Response({
            'client_secret': setup_intent.client_secret,
        })

    except stripe.error.StripeError as e:
        return Response(
            {'error': 'Failed to set up payment method'},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def save_payment_method_view(request):
    """
    Save a payment method for auto-reload after SetupIntent confirmation.

    Expects:
    - payment_method_id: The confirmed payment method ID
    """
    tenant = request.tenant
    if not tenant:
        return Response(
            {'error': 'No business context'},
            status=status.HTTP_400_BAD_REQUEST
        )

    payment_method_id = request.data.get('payment_method_id')

    if not payment_method_id:
        return Response(
            {'error': 'Payment method ID is required'},
            status=status.HTTP_400_BAD_REQUEST
        )

    credits = get_or_create_credits(tenant)
    credits.stripe_payment_method_id = payment_method_id
    credits.save(update_fields=['stripe_payment_method_id', 'updated_at'])

    return Response({
        'success': True,
        'payment_method_id': payment_method_id,
    })


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_transactions_view(request):
    """
    Get credit transaction history with pagination.
    """
    tenant = request.tenant
    if not tenant:
        return Response(
            {'error': 'No business context'},
            status=status.HTTP_400_BAD_REQUEST
        )

    credits = get_or_create_credits(tenant)

    transactions = CreditTransaction.objects.filter(credits=credits)

    paginator = TransactionPagination()
    page = paginator.paginate_queryset(transactions, request)

    results = [{
        'id': t.id,
        'amount_cents': t.amount_cents,
        'balance_after_cents': t.balance_after_cents,
        'transaction_type': t.transaction_type,
        'description': t.description,
        'reference_type': t.reference_type,
        'reference_id': t.reference_id,
        'stripe_charge_id': t.stripe_charge_id,
        'created_at': t.created_at,
    } for t in page]

    return paginator.get_paginated_response(results)


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_usage_stats_view(request):
    """
    Get communication usage statistics for the current month.
    """
    tenant = request.tenant
    if not tenant:
        return Response(
            {'error': 'No business context'},
            status=status.HTTP_400_BAD_REQUEST
        )

    from django.utils import timezone
    from datetime import timedelta
    from .models import ProxyPhoneNumber

    credits = get_or_create_credits(tenant)

    # Get usage for current month from transactions
    now = timezone.now()
    month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)

    month_transactions = CreditTransaction.objects.filter(
        credits=credits,
        transaction_type='usage',
        created_at__gte=month_start
    )

    # Calculate stats
    sms_transactions = month_transactions.filter(reference_type='sms')
    voice_transactions = month_transactions.filter(reference_type='voice')

    sms_count = sms_transactions.count()

    # Estimate voice minutes from cost (rough estimate)
    voice_cost_cents = abs(sum(t.amount_cents for t in voice_transactions))
    voice_minutes = int(voice_cost_cents / 1.3)  # ~$0.013/min Twilio rate

    # Count active proxy numbers
    active_proxy_numbers = ProxyPhoneNumber.objects.filter(
        assigned_tenant=tenant,
        status='assigned',
        is_active=True
    ).count()

    # Total estimated cost this month
    estimated_cost = abs(sum(t.amount_cents for t in month_transactions))

    return Response({
        'sms_sent_this_month': sms_count,
        'voice_minutes_this_month': voice_minutes,
        'proxy_numbers_active': active_proxy_numbers,
        'estimated_cost_cents': estimated_cost,
    })


def _get_or_create_stripe_customer(credits, tenant, user):
    """
    Get or create a Stripe customer for the tenant.

    Stores the Stripe customer ID on the CommunicationCredits model.
    """
    # Check if we already have a Stripe customer
    if credits.stripe_customer_id:
        return credits.stripe_customer_id

    # Create a new Stripe customer
    customer = stripe.Customer.create(
        email=user.email,
        name=tenant.name,
        metadata={
            'tenant_id': str(tenant.id),
            'tenant_name': tenant.name,
        },
    )

    # Store the customer ID on the credits model
    credits.stripe_customer_id = customer.id
    credits.save(update_fields=['stripe_customer_id', 'updated_at'])

    return customer.id


def _get_twilio_client():
    """Get a Twilio client using settings."""
    account_sid = getattr(settings, 'TWILIO_ACCOUNT_SID', None)
    auth_token = getattr(settings, 'TWILIO_AUTH_TOKEN', None)
    if not account_sid or not auth_token:
        raise ValueError("Twilio credentials not configured")
    return TwilioClient(account_sid, auth_token)


# =============================================================================
# Phone Number Management Endpoints
# =============================================================================

@api_view(['GET'])
@permission_classes([IsAuthenticated])
def search_available_numbers_view(request):
    """
    Search for available phone numbers to purchase from Twilio.

    Query params:
    - area_code: Optional 3-digit area code to search within
    - contains: Optional digits the number should contain
    - country: Country code (default: US)
    - limit: Number of results (default: 20, max: 50)
    """
    tenant = request.tenant
    if not tenant:
        return Response(
            {'error': 'No business context'},
            status=status.HTTP_400_BAD_REQUEST
        )

    # Check if tenant has masked calling feature
    if not tenant.has_feature('can_use_masked_phone_numbers'):
        return Response(
            {'error': 'Masked calling feature not available on your plan'},
            status=status.HTTP_403_FORBIDDEN
        )

    area_code = request.query_params.get('area_code', '')
    contains = request.query_params.get('contains', '')
    country = request.query_params.get('country', 'US')
    limit = min(int(request.query_params.get('limit', 20)), 50)

    try:
        client = _get_twilio_client()

        # Build search params
        search_params = {
            'voice_enabled': True,
            'sms_enabled': True,
            'limit': limit,
        }

        if area_code:
            search_params['area_code'] = area_code
        if contains:
            search_params['contains'] = contains

        # Search for available numbers
        available_numbers = client.available_phone_numbers(country).local.list(**search_params)

        results = []
        for number in available_numbers:
            results.append({
                'phone_number': number.phone_number,
                'friendly_name': number.friendly_name,
                'locality': number.locality,
                'region': number.region,
                'postal_code': number.postal_code,
                'capabilities': {
                    'voice': number.capabilities.get('voice', False),
                    'sms': number.capabilities.get('SMS', False),
                    'mms': number.capabilities.get('MMS', False),
                },
                'monthly_cost_cents': 200,  # $2.00/month flat rate
            })

        return Response({
            'numbers': results,
            'count': len(results),
        })

    except (TwilioRestException, TwilioException) as e:
        error_msg = str(e)
        # Check for test credentials error
        if '20008' in error_msg or 'Test Account' in error_msg:
            return Response(
                {'error': 'Phone number search requires live Twilio credentials. Please configure your Twilio account.'},
                status=status.HTTP_503_SERVICE_UNAVAILABLE
            )
        return Response(
            {'error': f'Failed to search phone numbers: {error_msg}'},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )
    except ValueError as e:
        return Response(
            {'error': str(e)},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def purchase_phone_number_view(request):
    """
    Purchase a phone number from Twilio and assign to tenant.

    Expects:
    - phone_number: The E.164 phone number to purchase
    - friendly_name: Optional friendly name for the number

    Charges $2.00 from credits for the purchase fee.
    """
    tenant = request.tenant
    if not tenant:
        return Response(
            {'error': 'No business context'},
            status=status.HTTP_400_BAD_REQUEST
        )

    # Check if tenant has masked calling feature
    if not tenant.has_feature('can_use_masked_phone_numbers'):
        return Response(
            {'error': 'Masked calling feature not available on your plan'},
            status=status.HTTP_403_FORBIDDEN
        )

    phone_number = request.data.get('phone_number')
    friendly_name = request.data.get('friendly_name', '')

    if not phone_number:
        return Response(
            {'error': 'Phone number is required'},
            status=status.HTTP_400_BAD_REQUEST
        )

    # Check if already purchased
    existing = ProxyPhoneNumber.objects.filter(phone_number=phone_number).first()
    if existing:
        if existing.assigned_tenant == tenant:
            return Response(
                {'error': 'You already own this number'},
                status=status.HTTP_400_BAD_REQUEST
            )
        else:
            return Response(
                {'error': 'This number is not available'},
                status=status.HTTP_400_BAD_REQUEST
            )

    credits = get_or_create_credits(tenant)

    # Check if tenant has enough credits for purchase fee ($2.00)
    purchase_fee_cents = 200
    if credits.balance_cents < purchase_fee_cents:
        return Response(
            {'error': f'Insufficient credits. Purchase fee is $2.00, you have ${credits.balance_cents/100:.2f}'},
            status=status.HTTP_400_BAD_REQUEST
        )

    try:
        client = _get_twilio_client()

        with transaction.atomic():
            # Purchase the number from Twilio
            purchased = client.incoming_phone_numbers.create(
                phone_number=phone_number,
                friendly_name=friendly_name or f"SmoothSchedule - {tenant.name}",
                voice_url=settings.TWILIO_VOICE_WEBHOOK_URL if hasattr(settings, 'TWILIO_VOICE_WEBHOOK_URL') else None,
                sms_url=settings.TWILIO_SMS_WEBHOOK_URL if hasattr(settings, 'TWILIO_SMS_WEBHOOK_URL') else None,
            )

            # Create the proxy number record
            proxy_number = ProxyPhoneNumber.objects.create(
                phone_number=phone_number,
                twilio_sid=purchased.sid,
                status=ProxyPhoneNumber.Status.ASSIGNED,
                assigned_tenant=tenant,
                assigned_at=timezone.now(),
                friendly_name=friendly_name,
                capabilities={
                    'voice': purchased.capabilities.get('voice', False),
                    'sms': purchased.capabilities.get('sms', False),
                    'mms': purchased.capabilities.get('mms', False),
                },
            )

            # Charge the purchase fee
            credits.deduct(
                amount_cents=purchase_fee_cents,
                description=f"Phone number purchase: {phone_number}",
                reference_type='phone_purchase',
                reference_id=purchased.sid,
            )

        return Response({
            'success': True,
            'phone_number': {
                'id': proxy_number.id,
                'phone_number': proxy_number.phone_number,
                'friendly_name': proxy_number.friendly_name,
                'status': proxy_number.status,
                'monthly_fee_cents': proxy_number.monthly_fee_cents,
                'assigned_at': proxy_number.assigned_at,
            },
            'balance_cents': credits.balance_cents,
        })

    except (TwilioRestException, TwilioException) as e:
        return Response(
            {'error': f'Failed to purchase number: {str(e)}'},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )
    except ValueError as e:
        return Response(
            {'error': str(e)},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def list_phone_numbers_view(request):
    """
    List phone numbers assigned to the tenant.
    """
    tenant = request.tenant
    if not tenant:
        return Response(
            {'error': 'No business context'},
            status=status.HTTP_400_BAD_REQUEST
        )

    # Check if tenant has masked calling feature
    if not tenant.has_feature('can_use_masked_phone_numbers'):
        return Response(
            {'error': 'Masked calling feature not available on your plan'},
            status=status.HTTP_403_FORBIDDEN
        )

    numbers = ProxyPhoneNumber.objects.filter(
        assigned_tenant=tenant,
        is_active=True,
    ).order_by('-assigned_at')

    results = []
    for num in numbers:
        results.append({
            'id': num.id,
            'phone_number': num.phone_number,
            'friendly_name': num.friendly_name,
            'status': num.status,
            'monthly_fee_cents': num.monthly_fee_cents,
            'capabilities': num.capabilities,
            'assigned_at': num.assigned_at,
            'last_billed_at': num.last_billed_at,
        })

    return Response({
        'numbers': results,
        'count': len(results),
    })


@api_view(['DELETE'])
@permission_classes([IsAuthenticated])
def release_phone_number_view(request, number_id):
    """
    Release a phone number back to Twilio.

    This will delete the number from your Twilio account.
    No charge is applied for releasing.
    """
    tenant = request.tenant
    if not tenant:
        return Response(
            {'error': 'No business context'},
            status=status.HTTP_400_BAD_REQUEST
        )

    try:
        proxy_number = ProxyPhoneNumber.objects.get(
            id=number_id,
            assigned_tenant=tenant,
        )
    except ProxyPhoneNumber.DoesNotExist:
        return Response(
            {'error': 'Phone number not found'},
            status=status.HTTP_404_NOT_FOUND
        )

    try:
        client = _get_twilio_client()

        with transaction.atomic():
            # Delete from Twilio
            if proxy_number.twilio_sid:
                try:
                    client.incoming_phone_numbers(proxy_number.twilio_sid).delete()
                except (TwilioRestException, TwilioException) as e:
                    # If the number doesn't exist in Twilio anymore, continue
                    if hasattr(e, 'code') and e.code != 20404:  # Not found
                        raise

            # Mark as inactive in our system
            proxy_number.status = ProxyPhoneNumber.Status.INACTIVE
            proxy_number.is_active = False
            proxy_number.assigned_tenant = None
            proxy_number.save(update_fields=['status', 'is_active', 'assigned_tenant', 'updated_at'])

        return Response({
            'success': True,
            'message': f'Phone number {proxy_number.phone_number} has been released',
        })

    except (TwilioRestException, TwilioException) as e:
        return Response(
            {'error': f'Failed to release number: {str(e)}'},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )
    except ValueError as e:
        return Response(
            {'error': str(e)},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def change_phone_number_view(request, number_id):
    """
    Change a phone number to a different one.

    Expects:
    - new_phone_number: The new E.164 phone number to purchase
    - friendly_name: Optional friendly name for the new number

    Charges $2.00 from credits for the change fee.
    Releases the old number and purchases the new one.
    """
    tenant = request.tenant
    if not tenant:
        return Response(
            {'error': 'No business context'},
            status=status.HTTP_400_BAD_REQUEST
        )

    try:
        old_number = ProxyPhoneNumber.objects.get(
            id=number_id,
            assigned_tenant=tenant,
        )
    except ProxyPhoneNumber.DoesNotExist:
        return Response(
            {'error': 'Phone number not found'},
            status=status.HTTP_404_NOT_FOUND
        )

    new_phone_number = request.data.get('new_phone_number')
    friendly_name = request.data.get('friendly_name', old_number.friendly_name)

    if not new_phone_number:
        return Response(
            {'error': 'New phone number is required'},
            status=status.HTTP_400_BAD_REQUEST
        )

    # Check if new number is available
    existing = ProxyPhoneNumber.objects.filter(phone_number=new_phone_number).first()
    if existing:
        return Response(
            {'error': 'This number is not available'},
            status=status.HTTP_400_BAD_REQUEST
        )

    credits = get_or_create_credits(tenant)

    # Check if tenant has enough credits for change fee ($2.00)
    change_fee_cents = 200
    if credits.balance_cents < change_fee_cents:
        return Response(
            {'error': f'Insufficient credits. Change fee is $2.00, you have ${credits.balance_cents/100:.2f}'},
            status=status.HTTP_400_BAD_REQUEST
        )

    try:
        client = _get_twilio_client()

        with transaction.atomic():
            # Purchase the new number
            purchased = client.incoming_phone_numbers.create(
                phone_number=new_phone_number,
                friendly_name=friendly_name or f"SmoothSchedule - {tenant.name}",
                voice_url=settings.TWILIO_VOICE_WEBHOOK_URL if hasattr(settings, 'TWILIO_VOICE_WEBHOOK_URL') else None,
                sms_url=settings.TWILIO_SMS_WEBHOOK_URL if hasattr(settings, 'TWILIO_SMS_WEBHOOK_URL') else None,
            )

            # Release the old number from Twilio
            if old_number.twilio_sid:
                try:
                    client.incoming_phone_numbers(old_number.twilio_sid).delete()
                except (TwilioRestException, TwilioException):
                    pass  # If deletion fails, continue anyway

            # Update the proxy number record with new number
            old_number.phone_number = new_phone_number
            old_number.twilio_sid = purchased.sid
            old_number.friendly_name = friendly_name
            old_number.capabilities = {
                'voice': purchased.capabilities.get('voice', False),
                'sms': purchased.capabilities.get('sms', False),
                'mms': purchased.capabilities.get('mms', False),
            }
            old_number.save(update_fields=[
                'phone_number', 'twilio_sid', 'friendly_name', 'capabilities', 'updated_at'
            ])

            # Charge the change fee
            credits.deduct(
                amount_cents=change_fee_cents,
                description=f"Phone number change to {new_phone_number}",
                reference_type='phone_change',
                reference_id=purchased.sid,
            )

        return Response({
            'success': True,
            'phone_number': {
                'id': old_number.id,
                'phone_number': old_number.phone_number,
                'friendly_name': old_number.friendly_name,
                'status': old_number.status,
                'monthly_fee_cents': old_number.monthly_fee_cents,
                'assigned_at': old_number.assigned_at,
            },
            'balance_cents': credits.balance_cents,
        })

    except (TwilioRestException, TwilioException) as e:
        return Response(
            {'error': f'Failed to change number: {str(e)}'},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )
    except ValueError as e:
        return Response(
            {'error': str(e)},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )
