"""
Twilio Field Call Service

Handles masked calling and SMS between field employees and customers.
Bi-directional: both employee→customer and customer→employee are supported.
"""
import logging
from datetime import timedelta
from typing import Optional, Tuple
from django.conf import settings
from django.utils import timezone
from django.db import transaction

logger = logging.getLogger(__name__)


class TwilioFieldCallError(Exception):
    """Raised when a Twilio call operation fails."""
    pass


class TwilioFieldCallService:
    """
    Service for managing masked calls and SMS for field employees.

    Uses Twilio Proxy-like functionality where:
    - A proxy number is assigned to each job session
    - Employee and customer both call/text the proxy number
    - Twilio webhooks route calls to the appropriate party
    """

    # How long a masked session lasts (extends past job end)
    SESSION_DURATION_HOURS = 4

    def __init__(self, tenant):
        """
        Initialize the service for a tenant.

        Args:
            tenant: The tenant (business) making the call
        """
        self.tenant = tenant
        self._client = None

    @property
    def client(self):
        """Lazy-load Twilio client using tenant's subaccount credentials."""
        if self._client is None:
            from twilio.rest import Client

            # Check for tenant subaccount first
            if self.tenant.twilio_subaccount_sid and self.tenant.twilio_subaccount_auth_token:
                self._client = Client(
                    self.tenant.twilio_subaccount_sid,
                    self.tenant.twilio_subaccount_auth_token
                )
            else:
                # Fall back to master account (not recommended for production)
                master_sid = getattr(settings, 'TWILIO_ACCOUNT_SID', '')
                master_token = getattr(settings, 'TWILIO_AUTH_TOKEN', '')

                if not master_sid or not master_token:
                    raise TwilioFieldCallError(
                        "Twilio is not configured for this business. "
                        "Please contact support."
                    )

                self._client = Client(master_sid, master_token)
                logger.warning(
                    f"Using master Twilio account for tenant {self.tenant.name}. "
                    "This should be avoided in production."
                )

        return self._client

    def _get_or_create_session(
        self,
        event_id: int,
        employee_phone: str,
        customer_phone: str
    ):
        """
        Get existing masked session for an event, or create a new one.

        Args:
            event_id: The job/event ID
            employee_phone: Employee's real phone number
            customer_phone: Customer's real phone number

        Returns:
            MaskedSession instance
        """
        from smoothschedule.communication.credits.models import MaskedSession, ProxyPhoneNumber

        # Check for existing active session
        existing = MaskedSession.objects.filter(
            tenant=self.tenant,
            event_id=event_id,
            status=MaskedSession.Status.ACTIVE,
            expires_at__gt=timezone.now()
        ).first()

        if existing:
            # Update phone numbers if changed (shouldn't happen but be safe)
            if existing.staff_phone != employee_phone or existing.customer_phone != customer_phone:
                existing.staff_phone = employee_phone
                existing.customer_phone = customer_phone
                existing.save(update_fields=['staff_phone', 'customer_phone', 'updated_at'])
            return existing

        # Need to create a new session - get a proxy number
        proxy_number = self._get_available_proxy_number()
        if not proxy_number:
            raise TwilioFieldCallError(
                "No proxy numbers available. Please contact support."
            )

        # Create the session
        session = MaskedSession.objects.create(
            tenant=self.tenant,
            event_id=event_id,
            proxy_number=proxy_number,
            customer_phone=customer_phone,
            staff_phone=employee_phone,
            expires_at=timezone.now() + timedelta(hours=self.SESSION_DURATION_HOURS),
            status=MaskedSession.Status.ACTIVE,
        )

        # Mark the proxy number as reserved for this session
        proxy_number.status = ProxyPhoneNumber.Status.RESERVED
        proxy_number.save(update_fields=['status', 'updated_at'])

        logger.info(
            f"Created masked session {session.id} for event {event_id} "
            f"using proxy {proxy_number.phone_number}"
        )

        return session

    def _get_available_proxy_number(self):
        """
        Get an available proxy number from the pool.

        Prefers:
        1. Numbers already assigned to this tenant
        2. Numbers in the shared pool (AVAILABLE status)
        """
        from smoothschedule.communication.credits.models import ProxyPhoneNumber

        # First, try tenant's assigned numbers
        tenant_number = ProxyPhoneNumber.objects.filter(
            assigned_tenant=self.tenant,
            status=ProxyPhoneNumber.Status.ASSIGNED,
            is_active=True,
        ).first()

        if tenant_number:
            return tenant_number

        # Fall back to shared pool
        available = ProxyPhoneNumber.objects.filter(
            status=ProxyPhoneNumber.Status.AVAILABLE,
            is_active=True,
        ).first()

        return available

    def _check_feature_permission(self):
        """Check if tenant has masked calling feature enabled."""
        if not self.tenant.has_feature('can_use_masked_phone_numbers'):
            raise TwilioFieldCallError(
                "Masked calling is not available on your current plan. "
                "Please upgrade to access this feature."
            )

    def _check_credits(self, estimated_cost_cents: int = 50):
        """
        Check if tenant has sufficient communication credits.

        Args:
            estimated_cost_cents: Estimated cost of the call/SMS
        """
        from smoothschedule.communication.credits.models import CommunicationCredits

        try:
            credits = CommunicationCredits.objects.get(tenant=self.tenant)
            if credits.balance_cents < estimated_cost_cents:
                raise TwilioFieldCallError(
                    "Insufficient communication credits. "
                    f"Current balance: ${credits.balance_cents/100:.2f}"
                )
        except CommunicationCredits.DoesNotExist:
            raise TwilioFieldCallError(
                "Communication credits not set up. "
                "Please add credits to use calling features."
            )

    def _get_customer_phone_for_event(self, event_id: int) -> Optional[str]:
        """
        Get the customer's phone number for an event.

        Looks up the customer participant and returns their phone.
        """
        from django.contrib.contenttypes.models import ContentType
        from smoothschedule.scheduling.schedule.models import Event, Participant
        from smoothschedule.identity.users.models import User
        from django_tenants.utils import schema_context

        with schema_context(self.tenant.schema_name):
            try:
                event = Event.objects.get(id=event_id)
            except Event.DoesNotExist:
                return None

            # Find customer participant
            user_ct = ContentType.objects.get_for_model(User)
            customer_participant = Participant.objects.filter(
                event=event,
                role=Participant.Role.CUSTOMER,
                content_type=user_ct
            ).first()

            if customer_participant:
                customer = customer_participant.content_object
                if customer and hasattr(customer, 'phone') and customer.phone:
                    return customer.phone

        return None

    @transaction.atomic
    def initiate_call(
        self,
        event_id: int,
        employee,
        customer_phone: Optional[str] = None,
    ) -> dict:
        """
        Initiate a masked call from employee to customer.

        The employee's phone will ring first. When they answer,
        the customer's phone will be connected.

        Args:
            event_id: The job/event ID
            employee: The employee User making the call
            customer_phone: Customer's phone (optional, will look up from event)

        Returns:
            Dict with call_sid, proxy_number, status
        """
        from smoothschedule.communication.mobile.models import FieldCallLog

        # Check permissions and credits
        self._check_feature_permission()
        self._check_credits(50)  # Voice calls cost more

        # Get customer phone if not provided
        if not customer_phone:
            customer_phone = self._get_customer_phone_for_event(event_id)
            if not customer_phone:
                raise TwilioFieldCallError(
                    "Customer phone number not found for this job."
                )

        # Get employee phone
        if not employee.phone:
            raise TwilioFieldCallError(
                "Your phone number is not set. Please update your profile."
            )

        employee_phone = employee.phone

        # Get or create masked session
        session = self._get_or_create_session(
            event_id=event_id,
            employee_phone=employee_phone,
            customer_phone=customer_phone,
        )

        # Build callback URL for Twilio
        callback_url = self._get_callback_url('voice', session.id)

        try:
            # Create the call - connect employee first, then bridge to customer
            call = self.client.calls.create(
                to=employee_phone,  # Call employee first
                from_=session.proxy_number.phone_number,
                url=callback_url,
                status_callback=self._get_status_callback_url(session.id),
                status_callback_event=['initiated', 'ringing', 'answered', 'completed'],
                machine_detection='Enable',  # Detect voicemail
            )

            # Log the call
            call_log = FieldCallLog.objects.create(
                tenant=self.tenant,
                event_id=event_id,
                call_type=FieldCallLog.CallType.VOICE,
                direction=FieldCallLog.Direction.OUTBOUND,
                status=FieldCallLog.Status.INITIATED,
                employee=employee,
                customer_phone=customer_phone,
                proxy_number=session.proxy_number.phone_number,
                twilio_call_sid=call.sid,
                masked_session=session,
            )

            logger.info(
                f"Initiated call {call.sid} from employee {employee.id} "
                f"to customer via proxy {session.proxy_number.phone_number}"
            )

            return {
                'call_sid': call.sid,
                'call_log_id': call_log.id,
                'proxy_number': session.proxy_number.phone_number,
                'status': 'initiated',
                'message': 'Your phone will ring shortly. Answer to connect to the customer.',
            }

        except Exception as e:
            logger.error(f"Error initiating call: {e}")
            raise TwilioFieldCallError(f"Failed to initiate call: {str(e)}")

    @transaction.atomic
    def send_sms(
        self,
        event_id: int,
        employee,
        message: str,
        customer_phone: Optional[str] = None,
    ) -> dict:
        """
        Send a masked SMS from employee to customer.

        The customer will see the proxy number as the sender.

        Args:
            event_id: The job/event ID
            employee: The employee User sending the SMS
            message: The message to send
            customer_phone: Customer's phone (optional)

        Returns:
            Dict with message_sid, status
        """
        from smoothschedule.communication.mobile.models import FieldCallLog

        # Check permissions and credits
        self._check_feature_permission()
        self._check_credits(5)  # SMS costs less

        # Validate message
        if not message or len(message.strip()) == 0:
            raise TwilioFieldCallError("Message cannot be empty")

        if len(message) > 1600:  # Twilio limit
            raise TwilioFieldCallError("Message too long (max 1600 characters)")

        # Get customer phone if not provided
        if not customer_phone:
            customer_phone = self._get_customer_phone_for_event(event_id)
            if not customer_phone:
                raise TwilioFieldCallError(
                    "Customer phone number not found for this job."
                )

        if not employee.phone:
            raise TwilioFieldCallError(
                "Your phone number is not set. Please update your profile."
            )

        # Get or create masked session
        session = self._get_or_create_session(
            event_id=event_id,
            employee_phone=employee.phone,
            customer_phone=customer_phone,
        )

        try:
            # Send the SMS
            sms = self.client.messages.create(
                to=customer_phone,
                from_=session.proxy_number.phone_number,
                body=message,
                status_callback=self._get_sms_status_callback_url(session.id),
            )

            # Log the message
            call_log = FieldCallLog.objects.create(
                tenant=self.tenant,
                event_id=event_id,
                call_type=FieldCallLog.CallType.SMS,
                direction=FieldCallLog.Direction.OUTBOUND,
                status=FieldCallLog.Status.COMPLETED,  # SMS is instant
                employee=employee,
                customer_phone=customer_phone,
                proxy_number=session.proxy_number.phone_number,
                twilio_message_sid=sms.sid,
                masked_session=session,
            )

            # Update session SMS count
            session.sms_count += 1
            session.save(update_fields=['sms_count', 'updated_at'])

            logger.info(
                f"Sent SMS {sms.sid} from employee {employee.id} "
                f"to customer via proxy {session.proxy_number.phone_number}"
            )

            return {
                'message_sid': sms.sid,
                'call_log_id': call_log.id,
                'status': 'sent',
            }

        except Exception as e:
            logger.error(f"Error sending SMS: {e}")
            raise TwilioFieldCallError(f"Failed to send SMS: {str(e)}")

    def get_session_for_event(self, event_id: int):
        """
        Get the active masked session for an event.

        Returns None if no active session exists.
        """
        from smoothschedule.communication.credits.models import MaskedSession

        return MaskedSession.objects.filter(
            tenant=self.tenant,
            event_id=event_id,
            status=MaskedSession.Status.ACTIVE,
            expires_at__gt=timezone.now()
        ).first()

    def close_session(self, event_id: int):
        """
        Close the masked session for an event.

        Called when a job is completed to stop allowing calls/SMS.
        """
        from smoothschedule.communication.credits.models import MaskedSession

        session = self.get_session_for_event(event_id)
        if session:
            session.close()
            logger.info(f"Closed masked session {session.id} for event {event_id}")

    def get_call_history(self, event_id: int, limit: int = 20) -> list:
        """
        Get call/SMS history for an event.

        Args:
            event_id: The job/event ID
            limit: Maximum records to return

        Returns:
            List of FieldCallLog records
        """
        from smoothschedule.communication.mobile.models import FieldCallLog

        return list(
            FieldCallLog.objects.filter(
                tenant=self.tenant,
                event_id=event_id
            ).select_related('employee')[:limit]
        )

    def _get_callback_url(self, call_type: str, session_id: int) -> str:
        """Build Twilio webhook callback URL for voice calls."""
        base_url = getattr(settings, 'TWILIO_WEBHOOK_BASE_URL', '')
        if not base_url:
            # Fall back to site URL
            base_url = getattr(settings, 'SITE_URL', 'https://api.smoothschedule.com')

        return f"{base_url}/api/mobile/twilio/voice/{session_id}/"

    def _get_status_callback_url(self, session_id: int) -> str:
        """Build Twilio status callback URL."""
        base_url = getattr(settings, 'TWILIO_WEBHOOK_BASE_URL', '')
        if not base_url:
            base_url = getattr(settings, 'SITE_URL', 'https://api.smoothschedule.com')

        return f"{base_url}/api/mobile/twilio/voice-status/{session_id}/"

    def _get_sms_status_callback_url(self, session_id: int) -> str:
        """Build Twilio SMS status callback URL."""
        base_url = getattr(settings, 'TWILIO_WEBHOOK_BASE_URL', '')
        if not base_url:
            base_url = getattr(settings, 'SITE_URL', 'https://api.smoothschedule.com')

        return f"{base_url}/api/mobile/twilio/sms-status/{session_id}/"


def handle_incoming_call(session_id: int, from_number: str) -> str:
    """
    Handle an incoming call to a proxy number.

    This is called by the Twilio webhook when someone calls the proxy.
    Routes the call to the appropriate party.

    Args:
        session_id: The MaskedSession ID
        from_number: The caller's phone number

    Returns:
        TwiML response string
    """
    from smoothschedule.communication.credits.models import MaskedSession
    from twilio.twiml.voice_response import VoiceResponse

    response = VoiceResponse()

    try:
        session = MaskedSession.objects.select_related('proxy_number').get(id=session_id)

        if not session.is_active():
            response.say("This number is no longer in service for this appointment.")
            response.hangup()
            return str(response)

        # Determine who to connect to
        destination = session.get_destination_for_caller(from_number)

        if not destination:
            response.say("Unable to connect your call. Please try again later.")
            response.hangup()
            return str(response)

        # Connect the call
        response.dial(
            destination,
            caller_id=session.proxy_number.phone_number,
            timeout=30,
        )

        return str(response)

    except MaskedSession.DoesNotExist:
        response.say("This number is not currently in service.")
        response.hangup()
        return str(response)


def handle_incoming_sms(session_id: int, from_number: str, body: str) -> str:
    """
    Handle an incoming SMS to a proxy number.

    Routes the SMS to the appropriate party.

    Args:
        session_id: The MaskedSession ID
        from_number: The sender's phone number
        body: The SMS body

    Returns:
        TwiML response string (empty for SMS)
    """
    from smoothschedule.communication.credits.models import MaskedSession
    from twilio.rest import Client
    from django.conf import settings as django_settings

    try:
        session = MaskedSession.objects.select_related(
            'proxy_number', 'tenant'
        ).get(id=session_id)

        if not session.is_active():
            # Session expired - don't forward
            return ""

        # Determine where to forward
        destination = session.get_destination_for_caller(from_number)

        if not destination:
            return ""

        # Get Twilio client for tenant
        tenant = session.tenant
        if tenant.twilio_subaccount_sid and tenant.twilio_subaccount_auth_token:
            client = Client(
                tenant.twilio_subaccount_sid,
                tenant.twilio_subaccount_auth_token
            )
        else:
            client = Client(
                django_settings.TWILIO_ACCOUNT_SID,
                django_settings.TWILIO_AUTH_TOKEN
            )

        # Forward the SMS
        client.messages.create(
            to=destination,
            from_=session.proxy_number.phone_number,
            body=body,
        )

        # Update session SMS count
        session.sms_count += 1
        session.save(update_fields=['sms_count', 'updated_at'])

        return ""

    except MaskedSession.DoesNotExist:
        return ""
    except Exception as e:
        logger.error(f"Error forwarding SMS: {e}")
        return ""
