"""
Field Mobile API Views

REST API endpoints for the field employee mobile app.
"""
import logging
from datetime import timedelta
from django.utils import timezone
from django.shortcuts import get_object_or_404
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse

from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response

from django_tenants.utils import schema_context

from smoothschedule.scheduling.schedule.models import Event, Participant, Resource
from smoothschedule.identity.users.models import User
from smoothschedule.communication.mobile.models import (
    EventStatusHistory,
    EmployeeLocationUpdate,
    FieldCallLog,
)
from smoothschedule.communication.mobile.serializers import (
    JobListSerializer,
    JobDetailSerializer,
    SetStatusSerializer,
    RescheduleJobSerializer,
    StartEnRouteSerializer,
    LocationUpdateSerializer,
    LocationUpdateResponseSerializer,
    InitiateCallSerializer,
    InitiateCallResponseSerializer,
    SendSMSSerializer,
    SendSMSResponseSerializer,
    CallHistorySerializer,
    EmployeeProfileSerializer,
)
from smoothschedule.communication.mobile.services import StatusMachine, TwilioFieldCallService
from smoothschedule.communication.mobile.services.status_machine import StatusTransitionError
from smoothschedule.communication.mobile.services.twilio_calls import TwilioFieldCallError

logger = logging.getLogger(__name__)


def get_tenant_from_user(user):
    """Get the tenant for an authenticated user."""
    if not user.tenant:
        return None
    return user.tenant


def is_field_employee(user):
    """Check if user is a field employee (staff role)."""
    return user.role in [
        User.Role.TENANT_STAFF,
        User.Role.TENANT_MANAGER,
        User.Role.TENANT_OWNER,
    ]


def get_employee_jobs_queryset(user, tenant):
    """
    Get the queryset of jobs assigned to an employee.

    Returns events where the user is a participant with STAFF/RESOURCE role,
    or where a Resource linked to the user is a participant.
    """
    user_ct = ContentType.objects.get_for_model(User)
    resource_ct = ContentType.objects.get_for_model(Resource)

    # Get resource IDs linked to this user
    user_resource_ids = list(
        Resource.objects.filter(user=user).values_list('id', flat=True)
    )

    # Find events where user is directly a participant
    user_event_ids = Participant.objects.filter(
        content_type=user_ct,
        object_id=user.id,
        role__in=[Participant.Role.STAFF, Participant.Role.RESOURCE]
    ).values_list('event_id', flat=True)

    # Find events where user's resource is a participant
    resource_event_ids = []
    if user_resource_ids:
        resource_event_ids = Participant.objects.filter(
            content_type=resource_ct,
            object_id__in=user_resource_ids,
            role__in=[Participant.Role.STAFF, Participant.Role.RESOURCE]
        ).values_list('event_id', flat=True)

    # Combine event IDs
    all_event_ids = set(user_event_ids) | set(resource_event_ids)

    return Event.objects.filter(id__in=all_event_ids)


# =============================================================================
# Employee Profile Endpoint
# =============================================================================

@api_view(['GET'])
@permission_classes([IsAuthenticated])
def employee_profile_view(request):
    """
    Get the current employee's profile.

    GET /api/mobile/me/

    Returns employee info with business context and feature flags.
    """
    user = request.user
    tenant = get_tenant_from_user(user)

    if not tenant:
        return Response(
            {'error': 'No business associated with your account'},
            status=status.HTTP_400_BAD_REQUEST
        )

    if not is_field_employee(user):
        return Response(
            {'error': 'This app is for field employees only'},
            status=status.HTTP_403_FORBIDDEN
        )

    serializer = EmployeeProfileSerializer(user)
    return Response(serializer.data)


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def logout_view(request):
    """
    Logout from the mobile app.

    POST /api/mobile/logout/

    NOTE: We do NOT delete the token because DRF tokens are OneToOne with User,
    meaning web and mobile share the same token. Deleting it here would log
    the user out of the web app too.

    The mobile app should clear its local token storage on logout.
    """
    logger.info(f"User {request.user.id} logged out from mobile app (token preserved)")
    return Response({'success': True, 'message': 'Logged out successfully'})


# =============================================================================
# Job Detail Endpoint
# Note: Job list uses /api/appointments/ with date range filtering (start_date, end_date)
# =============================================================================

@api_view(['GET'])
@permission_classes([IsAuthenticated])
def job_detail_view(request, job_id):
    """
    Get details of a specific job.

    GET /api/mobile/jobs/{job_id}/

    Returns full job details including customer info, status history, etc.
    """
    user = request.user
    tenant = get_tenant_from_user(user)

    if not tenant:
        return Response(
            {'error': 'No business associated with your account'},
            status=status.HTTP_400_BAD_REQUEST
        )

    with schema_context(tenant.schema_name):
        # Get the job and verify employee is assigned
        queryset = get_employee_jobs_queryset(user, tenant)
        job = get_object_or_404(queryset, id=job_id)

        serializer = JobDetailSerializer(job, context={'tenant': tenant, 'request': request})
        return Response(serializer.data)


# =============================================================================
# Status Management Endpoints
# =============================================================================

@api_view(['POST'])
@permission_classes([IsAuthenticated])
def set_status_view(request, job_id):
    """
    Update a job's status.

    POST /api/mobile/jobs/{job_id}/set_status/

    Body:
    {
        "status": "IN_PROGRESS",
        "notes": "Optional notes",
        "latitude": 40.7128,
        "longitude": -74.0060
    }

    Validates the status transition and records in history.
    """
    user = request.user
    tenant = get_tenant_from_user(user)

    if not tenant:
        return Response(
            {'error': 'No business associated with your account'},
            status=status.HTTP_400_BAD_REQUEST
        )

    serializer = SetStatusSerializer(data=request.data)
    if not serializer.is_valid():
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    with schema_context(tenant.schema_name):
        # Get the job
        queryset = get_employee_jobs_queryset(user, tenant)
        job = get_object_or_404(queryset, id=job_id)

        # Perform the transition
        status_machine = StatusMachine(tenant, user)

        try:
            job = status_machine.transition(
                event=job,
                new_status=serializer.validated_data['status'],
                notes=serializer.validated_data.get('notes', ''),
                latitude=serializer.validated_data.get('latitude'),
                longitude=serializer.validated_data.get('longitude'),
                source='mobile_app',
            )

            # If job is completed, close any masked call sessions
            if job.status == Event.Status.COMPLETED:
                try:
                    call_service = TwilioFieldCallService(tenant)
                    call_service.close_session(job.id)
                except Exception as e:
                    logger.warning(f"Error closing call session: {e}")

            response_serializer = JobDetailSerializer(job, context={'tenant': tenant})
            return Response({
                'success': True,
                'job': response_serializer.data,
            })

        except StatusTransitionError as e:
            return Response(
                {'error': str(e)},
                status=status.HTTP_400_BAD_REQUEST
            )


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def start_en_route_view(request, job_id):
    """
    Start traveling to a job.

    POST /api/mobile/jobs/{job_id}/start_en_route/

    Body:
    {
        "latitude": 40.7128,
        "longitude": -74.0060,
        "send_customer_notification": true
    }

    Changes status to EN_ROUTE and optionally notifies customer.
    """
    user = request.user
    tenant = get_tenant_from_user(user)

    if not tenant:
        return Response(
            {'error': 'No business associated with your account'},
            status=status.HTTP_400_BAD_REQUEST
        )

    serializer = StartEnRouteSerializer(data=request.data)
    if not serializer.is_valid():
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    with schema_context(tenant.schema_name):
        queryset = get_employee_jobs_queryset(user, tenant)
        job = get_object_or_404(queryset, id=job_id)

        status_machine = StatusMachine(tenant, user)

        try:
            skip_notifications = not serializer.validated_data.get(
                'send_customer_notification', True
            )

            job = status_machine.transition(
                event=job,
                new_status=Event.Status.EN_ROUTE,
                latitude=serializer.validated_data.get('latitude'),
                longitude=serializer.validated_data.get('longitude'),
                source='mobile_app',
                skip_notifications=skip_notifications,
            )

            response_serializer = JobDetailSerializer(job, context={'tenant': tenant})
            return Response({
                'success': True,
                'job': response_serializer.data,
                'tracking_enabled': True,
            })

        except StatusTransitionError as e:
            return Response(
                {'error': str(e)},
                status=status.HTTP_400_BAD_REQUEST
            )


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def reschedule_job_view(request, job_id):
    """
    Reschedule a job (change start time and/or duration).

    POST /api/mobile/jobs/{job_id}/reschedule/

    Body:
    {
        "start_time": "2024-01-15T10:30:00Z",  // Optional: new start time
        "end_time": "2024-01-15T11:30:00Z",    // Optional: new end time
        "duration_minutes": 60                  // Optional: new duration
    }

    Requires the user's linked resource to have user_can_edit_schedule=True.
    """
    user = request.user
    tenant = get_tenant_from_user(user)

    if not tenant:
        return Response(
            {'error': 'No business associated with your account'},
            status=status.HTTP_400_BAD_REQUEST
        )

    serializer = RescheduleJobSerializer(data=request.data)
    if not serializer.is_valid():
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    with schema_context(tenant.schema_name):
        # Get the job
        queryset = get_employee_jobs_queryset(user, tenant)
        job = get_object_or_404(queryset, id=job_id)

        # Check if user has permission to edit schedule
        user_resources = Resource.objects.filter(user=user)
        can_edit = any(r.user_can_edit_schedule for r in user_resources)

        if not can_edit:
            return Response(
                {'error': 'You do not have permission to edit your schedule'},
                status=status.HTTP_403_FORBIDDEN
            )

        # Update the job timing
        start_time = serializer.validated_data.get('start_time')
        end_time = serializer.validated_data.get('end_time')
        duration_minutes = serializer.validated_data.get('duration_minutes')

        if start_time:
            job.start_time = start_time

        if end_time:
            job.end_time = end_time
        elif duration_minutes and job.start_time:
            # Calculate end_time from duration
            job.end_time = job.start_time + timedelta(minutes=duration_minutes)

        job.save()

        logger.info(
            f"Job {job_id} rescheduled by user {user.id}: "
            f"start={job.start_time}, end={job.end_time}"
        )

        response_serializer = JobDetailSerializer(job, context={'tenant': tenant, 'request': request})
        return Response({
            'success': True,
            'job': response_serializer.data,
        })


# =============================================================================
# Location Tracking Endpoints
# =============================================================================

@api_view(['POST'])
@permission_classes([IsAuthenticated])
def location_update_view(request, job_id):
    """
    Send a location update while en-route or in-progress.

    POST /api/mobile/jobs/{job_id}/location_update/

    Body:
    {
        "latitude": 40.7128,
        "longitude": -74.0060,
        "accuracy": 10.5,
        "altitude": 50.0,
        "heading": 180.0,
        "speed": 15.5,
        "timestamp": "2024-01-15T10:30:00Z",
        "battery_level": 0.75
    }

    Returns whether to continue tracking (stops on job completion).
    """
    user = request.user
    tenant = get_tenant_from_user(user)

    if not tenant:
        return Response(
            {'error': 'No business associated with your account'},
            status=status.HTTP_400_BAD_REQUEST
        )

    serializer = LocationUpdateSerializer(data=request.data)
    if not serializer.is_valid():
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    with schema_context(tenant.schema_name):
        # Verify job exists and user is assigned
        queryset = get_employee_jobs_queryset(user, tenant)
        job = get_object_or_404(queryset, id=job_id)

        # Check if tracking should continue
        should_track = job.status in StatusMachine.TRACKING_STATUSES

        if not should_track:
            return Response({
                'success': False,
                'should_continue_tracking': False,
                'message': f'Location tracking not needed for status: {job.status}',
            })

        # Record the location update
        location = EmployeeLocationUpdate.objects.create(
            tenant=tenant,
            employee=user,
            event_id=job.id,
            latitude=serializer.validated_data['latitude'],
            longitude=serializer.validated_data['longitude'],
            accuracy=serializer.validated_data.get('accuracy'),
            altitude=serializer.validated_data.get('altitude'),
            heading=serializer.validated_data.get('heading'),
            speed=serializer.validated_data.get('speed'),
            timestamp=serializer.validated_data['timestamp'],
            battery_level=serializer.validated_data.get('battery_level'),
        )

        # Broadcast location update via WebSocket
        # Find the resource linked to this user and broadcast to watchers
        from smoothschedule.scheduling.schedule.models import Resource
        from smoothschedule.scheduling.schedule.consumers import broadcast_resource_location_update
        from asgiref.sync import async_to_sync

        user_resources = Resource.objects.filter(user=user)
        for resource in user_resources:
            location_data = {
                'latitude': float(location.latitude),
                'longitude': float(location.longitude),
                'accuracy': location.accuracy,
                'heading': location.heading,
                'speed': location.speed,
                'timestamp': location.timestamp.isoformat(),
            }
            active_job_data = {
                'id': job.id,
                'title': job.title,
                'status': job.status,
                'status_display': job.get_status_display(),
            }
            try:
                async_to_sync(broadcast_resource_location_update)(
                    resource_id=resource.id,
                    location_data=location_data,
                    active_job=active_job_data
                )
            except Exception as e:
                logger.warning(f"Failed to broadcast location update: {e}")

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


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def location_route_view(request, job_id):
    """
    Get the location history (route) for a job.

    GET /api/mobile/jobs/{job_id}/route/

    Returns a list of locations for drawing the route on a map.
    """
    user = request.user
    tenant = get_tenant_from_user(user)

    if not tenant:
        return Response(
            {'error': 'No business associated with your account'},
            status=status.HTTP_400_BAD_REQUEST
        )

    with schema_context(tenant.schema_name):
        # Verify job exists
        queryset = get_employee_jobs_queryset(user, tenant)
        job = get_object_or_404(queryset, id=job_id)

        # Get route data
        route = EmployeeLocationUpdate.get_route_for_event(
            tenant_id=tenant.id,
            event_id=job.id,
            limit=200  # More points for detailed route
        )

        # Convert Decimal to float for JSON serialization
        for point in route:
            point['latitude'] = float(point['latitude'])
            point['longitude'] = float(point['longitude'])

        return Response({
            'job_id': job.id,
            'route': route,
            'point_count': len(route),
        })


# =============================================================================
# Calling and SMS Endpoints
# =============================================================================

@api_view(['POST'])
@permission_classes([IsAuthenticated])
def call_customer_view(request, job_id):
    """
    Initiate a masked call to the customer.

    POST /api/mobile/jobs/{job_id}/call_customer/

    The employee's phone will ring. When answered, they'll be connected
    to the customer. Both parties see only the proxy number.
    """
    user = request.user
    tenant = get_tenant_from_user(user)

    if not tenant:
        return Response(
            {'error': 'No business associated with your account'},
            status=status.HTTP_400_BAD_REQUEST
        )

    with schema_context(tenant.schema_name):
        # Verify job exists and user is assigned
        queryset = get_employee_jobs_queryset(user, tenant)
        job = get_object_or_404(queryset, id=job_id)

        try:
            call_service = TwilioFieldCallService(tenant)
            result = call_service.initiate_call(
                event_id=job.id,
                employee=user,
            )

            return Response(result)

        except TwilioFieldCallError as e:
            return Response(
                {'error': str(e)},
                status=status.HTTP_400_BAD_REQUEST
            )
        except Exception as e:
            logger.exception(f"Error initiating call: {e}")
            return Response(
                {'error': 'Failed to initiate call. Please try again.'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def send_sms_view(request, job_id):
    """
    Send a masked SMS to the customer.

    POST /api/mobile/jobs/{job_id}/send_sms/

    Body:
    {
        "message": "I'll be there in 10 minutes!"
    }

    The customer sees the SMS from the proxy number.
    """
    user = request.user
    tenant = get_tenant_from_user(user)

    if not tenant:
        return Response(
            {'error': 'No business associated with your account'},
            status=status.HTTP_400_BAD_REQUEST
        )

    serializer = SendSMSSerializer(data=request.data)
    if not serializer.is_valid():
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    with schema_context(tenant.schema_name):
        queryset = get_employee_jobs_queryset(user, tenant)
        job = get_object_or_404(queryset, id=job_id)

        try:
            call_service = TwilioFieldCallService(tenant)
            result = call_service.send_sms(
                event_id=job.id,
                employee=user,
                message=serializer.validated_data['message'],
            )

            return Response(result)

        except TwilioFieldCallError as e:
            return Response(
                {'error': str(e)},
                status=status.HTTP_400_BAD_REQUEST
            )
        except Exception as e:
            logger.exception(f"Error sending SMS: {e}")
            return Response(
                {'error': 'Failed to send SMS. Please try again.'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def call_history_view(request, job_id):
    """
    Get call and SMS history for a job.

    GET /api/mobile/jobs/{job_id}/call_history/

    Returns all calls and messages for this job.
    """
    user = request.user
    tenant = get_tenant_from_user(user)

    if not tenant:
        return Response(
            {'error': 'No business associated with your account'},
            status=status.HTTP_400_BAD_REQUEST
        )

    with schema_context(tenant.schema_name):
        queryset = get_employee_jobs_queryset(user, tenant)
        job = get_object_or_404(queryset, id=job_id)

        history = FieldCallLog.objects.filter(
            tenant=tenant,
            event_id=job.id
        ).select_related('employee').order_by('-initiated_at')[:50]

        serializer = CallHistorySerializer(history, many=True)
        return Response({
            'job_id': job.id,
            'history': serializer.data,
        })


# =============================================================================
# Twilio Webhook Endpoints
# =============================================================================

@csrf_exempt
@api_view(['POST'])
@permission_classes([AllowAny])
def twilio_voice_webhook(request, session_id):
    """
    Twilio webhook for handling voice calls.

    POST /api/mobile/twilio/voice/{session_id}/

    Called by Twilio when a call is initiated or received.
    Returns TwiML to route the call.
    """
    from smoothschedule.communication.mobile.services.twilio_calls import handle_incoming_call

    from_number = request.data.get('From', '')
    twiml = handle_incoming_call(session_id, from_number)

    return HttpResponse(twiml, content_type='application/xml')


@csrf_exempt
@api_view(['POST'])
@permission_classes([AllowAny])
def twilio_voice_status_webhook(request, session_id):
    """
    Twilio webhook for call status updates.

    POST /api/mobile/twilio/voice-status/{session_id}/

    Updates the FieldCallLog with call status.
    """
    call_sid = request.data.get('CallSid', '')
    call_status = request.data.get('CallStatus', '')
    duration = request.data.get('CallDuration', 0)

    if call_sid:
        try:
            call_log = FieldCallLog.objects.get(twilio_call_sid=call_sid)

            # Map Twilio status to our status
            status_map = {
                'queued': FieldCallLog.Status.INITIATED,
                'ringing': FieldCallLog.Status.RINGING,
                'in-progress': FieldCallLog.Status.IN_PROGRESS,
                'completed': FieldCallLog.Status.COMPLETED,
                'busy': FieldCallLog.Status.BUSY,
                'no-answer': FieldCallLog.Status.NO_ANSWER,
                'failed': FieldCallLog.Status.FAILED,
                'canceled': FieldCallLog.Status.CANCELED,
            }

            call_log.status = status_map.get(call_status, FieldCallLog.Status.COMPLETED)

            if call_status == 'completed':
                call_log.ended_at = timezone.now()
                call_log.duration_seconds = int(duration)
            elif call_status == 'in-progress':
                call_log.answered_at = timezone.now()

            call_log.save()

            # Update session voice usage
            if call_log.masked_session and duration:
                session = call_log.masked_session
                session.voice_seconds += int(duration)
                session.save(update_fields=['voice_seconds', 'updated_at'])

        except FieldCallLog.DoesNotExist:
            logger.warning(f"FieldCallLog not found for call SID: {call_sid}")

    return HttpResponse('', status=200)


@csrf_exempt
@api_view(['POST'])
@permission_classes([AllowAny])
def twilio_sms_webhook(request, session_id):
    """
    Twilio webhook for incoming SMS.

    POST /api/mobile/twilio/sms/{session_id}/

    Forwards the SMS to the appropriate party.
    """
    from smoothschedule.communication.mobile.services.twilio_calls import handle_incoming_sms

    from_number = request.data.get('From', '')
    body = request.data.get('Body', '')

    handle_incoming_sms(session_id, from_number, body)

    # Return empty TwiML response
    return HttpResponse('<?xml version="1.0" encoding="UTF-8"?><Response></Response>',
                       content_type='application/xml')


@csrf_exempt
@api_view(['POST'])
@permission_classes([AllowAny])
def twilio_sms_status_webhook(request, session_id):
    """
    Twilio webhook for SMS status updates.

    POST /api/mobile/twilio/sms-status/{session_id}/
    """
    # SMS status updates are less critical, just log them
    message_sid = request.data.get('MessageSid', '')
    message_status = request.data.get('MessageStatus', '')

    if message_sid and message_status:
        logger.debug(f"SMS {message_sid} status: {message_status}")

    return HttpResponse('', status=200)
