"""
Analytics Edge Cases Tests

Comprehensive tests for edge cases in the analytics module:
- Empty data scenarios (no events, no revenue)
- Boundary date ranges
- Invalid date formats
- Missing required parameters
- Division by zero scenarios in calculations
- Large dataset handling
"""
import pytest
from unittest.mock import Mock, patch, MagicMock
from django.utils import timezone
from rest_framework.test import APIRequestFactory, force_authenticate
from rest_framework import status
from datetime import timedelta, datetime, UTC
from decimal import Decimal

from smoothschedule.scheduling.analytics.views import AnalyticsViewSet


class TestDashboardEdgeCases:
    """Test dashboard endpoint edge cases"""

    def setup_method(self):
        """Setup test data"""
        self.factory = APIRequestFactory()
        self.viewset = AnalyticsViewSet()

    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_dashboard_with_no_events(self, mock_event):
        """Test dashboard when there are no events at all"""
        request = self.factory.get('/api/analytics/analytics/dashboard/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset to return no events
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 0
        mock_queryset.values.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.exists.return_value = False
        mock_queryset.extra.return_value = mock_queryset
        mock_queryset.annotate.return_value = mock_queryset
        mock_queryset.order_by.return_value = mock_queryset
        mock_event.objects = mock_queryset

        view = AnalyticsViewSet.as_view({'get': 'dashboard'})
        response = view(request)

        assert response.status_code == 200
        assert response.data['total_appointments_this_month'] == 0
        assert response.data['total_appointments_all_time'] == 0
        assert response.data['active_resources_count'] == 0
        assert response.data['active_services_count'] == 0
        assert response.data['upcoming_appointments_count'] == 0
        assert response.data['average_appointment_duration_minutes'] == 0.0
        assert response.data['peak_booking_day'] == 'Unknown'
        assert response.data['peak_booking_hour'] == 0

    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_dashboard_with_events_missing_end_time(self, mock_event):
        """Test dashboard when events have None end_time"""
        request = self.factory.get('/api/analytics/analytics/dashboard/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock events with missing end_time
        now = timezone.now()
        mock_events_data = [
            {'start_time': now, 'end_time': None},
            {'start_time': now, 'end_time': None},
            {'start_time': now, 'end_time': now + timedelta(hours=1)},
        ]

        # Create a proper mock for the aggregation chain
        mock_values_queryset = Mock()
        mock_values_queryset.exists.return_value = True
        mock_values_queryset.__iter__ = Mock(return_value=iter(mock_events_data))

        # Mock for peak times - set up a complete chain for .extra().values().annotate().order_by()
        mock_peak_queryset = Mock()
        mock_peak_queryset.exists.return_value = False

        # Create proper chain: extra -> values -> annotate -> order_by -> result
        mock_extra_chain = Mock()
        mock_values_chain = Mock()
        mock_annotate_chain = Mock()

        mock_extra_chain.values.return_value = mock_values_chain
        mock_values_chain.annotate.return_value = mock_annotate_chain
        mock_annotate_chain.order_by.return_value = mock_peak_queryset

        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 3
        mock_queryset.values.return_value = mock_values_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.exists.return_value = False
        mock_queryset.extra.return_value = mock_extra_chain
        mock_event.objects = mock_queryset

        view = AnalyticsViewSet.as_view({'get': 'dashboard'})
        response = view(request)

        # Should handle None end_time gracefully (only count valid durations)
        assert response.status_code == 200
        assert response.data['average_appointment_duration_minutes'] == 60.0

    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_dashboard_with_events_missing_start_time(self, mock_event):
        """Test dashboard when events have None start_time"""
        request = self.factory.get('/api/analytics/analytics/dashboard/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock events with missing start_time
        now = timezone.now()
        mock_events_data = [
            {'start_time': None, 'end_time': now},
            {'start_time': now, 'end_time': now + timedelta(hours=1)},
        ]

        # Create a proper mock for the aggregation chain
        mock_values_queryset = Mock()
        mock_values_queryset.exists.return_value = True
        mock_values_queryset.__iter__ = Mock(return_value=iter(mock_events_data))

        # Mock for peak times - set up a complete chain for .extra().values().annotate().order_by()
        mock_peak_queryset = Mock()
        mock_peak_queryset.exists.return_value = False

        # Create proper chain: extra -> values -> annotate -> order_by -> result
        mock_extra_chain = Mock()
        mock_values_chain = Mock()
        mock_annotate_chain = Mock()

        mock_extra_chain.values.return_value = mock_values_chain
        mock_values_chain.annotate.return_value = mock_annotate_chain
        mock_annotate_chain.order_by.return_value = mock_peak_queryset

        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 2
        mock_queryset.values.return_value = mock_values_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.exists.return_value = False
        mock_queryset.extra.return_value = mock_extra_chain
        mock_event.objects = mock_queryset

        view = AnalyticsViewSet.as_view({'get': 'dashboard'})
        response = view(request)

        # Should handle None start_time gracefully
        assert response.status_code == 200
        assert response.data['average_appointment_duration_minutes'] == 60.0

    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_dashboard_average_duration_with_zero_events(self, mock_event):
        """Test average duration calculation doesn't divide by zero"""
        request = self.factory.get('/api/analytics/analytics/dashboard/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock events with all None times
        mock_events_data = [
            {'start_time': None, 'end_time': None},
            {'start_time': None, 'end_time': None},
        ]

        # Create a proper mock for the aggregation chain
        mock_values_queryset = Mock()
        mock_values_queryset.exists.return_value = True
        mock_values_queryset.__iter__ = Mock(return_value=iter(mock_events_data))

        # Mock for peak times - set up a complete chain for .extra().values().annotate().order_by()
        mock_peak_queryset = Mock()
        mock_peak_queryset.exists.return_value = False

        # Create proper chain: extra -> values -> annotate -> order_by -> result
        mock_extra_chain = Mock()
        mock_values_chain = Mock()
        mock_annotate_chain = Mock()

        mock_extra_chain.values.return_value = mock_values_chain
        mock_values_chain.annotate.return_value = mock_annotate_chain
        mock_annotate_chain.order_by.return_value = mock_peak_queryset

        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 2
        mock_queryset.values.return_value = mock_values_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.exists.return_value = False
        mock_queryset.extra.return_value = mock_extra_chain
        mock_event.objects = mock_queryset

        view = AnalyticsViewSet.as_view({'get': 'dashboard'})
        response = view(request)

        # Should not raise division by zero error
        assert response.status_code == 200
        assert response.data['average_appointment_duration_minutes'] == 0.0

    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_dashboard_peak_times_with_empty_results(self, mock_event):
        """Test peak day/hour calculation when no events exist"""
        request = self.factory.get('/api/analytics/analytics/dashboard/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset to return no events
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 0
        mock_queryset.values.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.exists.return_value = False
        mock_queryset.extra.return_value = mock_queryset
        mock_queryset.annotate.return_value = mock_queryset
        mock_queryset.order_by.return_value = mock_queryset
        mock_event.objects = mock_queryset

        view = AnalyticsViewSet.as_view({'get': 'dashboard'})
        response = view(request)

        assert response.status_code == 200
        # Should handle empty results gracefully
        assert response.data['peak_booking_day'] == 'Unknown'
        assert response.data['peak_booking_hour'] == 0


class TestAppointmentsEdgeCases:
    """Test appointments endpoint edge cases"""

    def setup_method(self):
        """Setup test data"""
        self.factory = APIRequestFactory()
        self.viewset = AnalyticsViewSet()

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_with_no_events(self, mock_event, mock_service):
        """Test appointments endpoint with no events"""
        request = self.factory.get('/api/analytics/analytics/appointments/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset with no events
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 0
        mock_queryset.select_related.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.__iter__ = Mock(return_value=iter([]))
        mock_event.objects = mock_queryset

        # Mock Service queryset
        mock_service_queryset = Mock()
        mock_service_queryset.filter.return_value = mock_service_queryset
        mock_service_queryset.distinct.return_value = []
        mock_service.objects = mock_service_queryset

        view = AnalyticsViewSet.as_view({'get': 'appointments'})
        response = view(request)

        assert response.status_code == 200
        assert response.data['total'] == 0
        assert response.data['by_status']['confirmed'] == 0
        assert response.data['by_status']['cancelled'] == 0
        assert response.data['by_status']['no_show'] == 0
        assert response.data['cancellation_rate_percent'] == 0.0
        assert response.data['no_show_rate_percent'] == 0.0
        assert response.data['booking_trend_percent'] == 0.0

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_with_invalid_days_parameter(self, mock_event, mock_service):
        """Test appointments endpoint with invalid days parameter"""
        request = self.factory.get('/api/analytics/analytics/appointments/?days=abc')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Should raise ValueError when trying to convert 'abc' to int
        view = AnalyticsViewSet.as_view({'get': 'appointments'})

        # Expect ValueError to be raised
        with pytest.raises(ValueError):
            response = view(request)

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_with_negative_days(self, mock_event, mock_service):
        """Test appointments endpoint with negative days parameter"""
        request = self.factory.get('/api/analytics/analytics/appointments/?days=-10')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 0
        mock_queryset.select_related.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.__iter__ = Mock(return_value=iter([]))
        mock_event.objects = mock_queryset

        # Mock Service queryset
        mock_service_queryset = Mock()
        mock_service_queryset.filter.return_value = mock_service_queryset
        mock_service_queryset.distinct.return_value = []
        mock_service.objects = mock_service_queryset

        view = AnalyticsViewSet.as_view({'get': 'appointments'})
        response = view(request)

        # Should still work (negative days results in future date range)
        assert response.status_code == 200
        assert response.data['period_days'] == -10

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_with_zero_days(self, mock_event, mock_service):
        """Test appointments endpoint with zero days parameter"""
        request = self.factory.get('/api/analytics/analytics/appointments/?days=0')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 0
        mock_queryset.select_related.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.__iter__ = Mock(return_value=iter([]))
        mock_event.objects = mock_queryset

        # Mock Service queryset
        mock_service_queryset = Mock()
        mock_service_queryset.filter.return_value = mock_service_queryset
        mock_service_queryset.distinct.return_value = []
        mock_service.objects = mock_service_queryset

        view = AnalyticsViewSet.as_view({'get': 'appointments'})
        response = view(request)

        # Should work with zero days
        assert response.status_code == 200
        assert response.data['period_days'] == 0

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_with_very_large_days(self, mock_event, mock_service):
        """Test appointments endpoint with very large days parameter causes OverflowError"""
        request = self.factory.get('/api/analytics/analytics/appointments/?days=999999999')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset
        mock_queryset = Mock()
        mock_event.objects = mock_queryset

        view = AnalyticsViewSet.as_view({'get': 'appointments'})

        # Very large days value causes OverflowError in timedelta
        with pytest.raises(OverflowError):
            response = view(request)

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_with_large_but_valid_days(self, mock_event, mock_service):
        """Test appointments endpoint with large but valid days parameter"""
        request = self.factory.get('/api/analytics/analytics/appointments/?days=3650')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 0
        mock_queryset.select_related.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.__iter__ = Mock(return_value=iter([]))
        mock_event.objects = mock_queryset

        # Mock Service queryset
        mock_service_queryset = Mock()
        mock_service_queryset.filter.return_value = mock_service_queryset
        mock_service_queryset.distinct.return_value = []
        mock_service.objects = mock_service_queryset

        view = AnalyticsViewSet.as_view({'get': 'appointments'})
        response = view(request)

        # Should handle large numbers (10 years)
        assert response.status_code == 200
        assert response.data['period_days'] == 3650

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_cancellation_rate_division_by_zero(self, mock_event, mock_service):
        """Test cancellation rate when total is zero"""
        request = self.factory.get('/api/analytics/analytics/appointments/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset with zero total
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 0
        mock_queryset.select_related.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.__iter__ = Mock(return_value=iter([]))
        mock_event.objects = mock_queryset

        # Mock Service queryset
        mock_service_queryset = Mock()
        mock_service_queryset.filter.return_value = mock_service_queryset
        mock_service_queryset.distinct.return_value = []
        mock_service.objects = mock_service_queryset

        view = AnalyticsViewSet.as_view({'get': 'appointments'})
        response = view(request)

        # Should not divide by zero
        assert response.status_code == 200
        assert response.data['cancellation_rate_percent'] == 0.0
        assert response.data['no_show_rate_percent'] == 0.0

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_booking_trend_division_by_zero(self, mock_event, mock_service):
        """Test booking trend when previous period has zero appointments"""
        request = self.factory.get('/api/analytics/analytics/appointments/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset with current period having events but previous having 0
        def filter_side_effect(*args, **kwargs):
            mock_filtered = Mock()
            mock_filtered.filter.return_value = mock_filtered
            mock_filtered.select_related.return_value = mock_filtered
            mock_filtered.distinct.return_value = mock_filtered
            mock_filtered.__iter__ = Mock(return_value=iter([]))

            # Check if this is the previous period filter
            if 'start_time__lt' in kwargs and len(args) == 0:
                # Previous period query - return 0
                mock_filtered.count.return_value = 0
            else:
                # Current period - return 10
                mock_filtered.count.return_value = 10

            return mock_filtered

        mock_queryset = Mock()
        mock_queryset.filter.side_effect = filter_side_effect
        mock_event.objects = mock_queryset

        # Mock Service queryset
        mock_service_queryset = Mock()
        mock_service_queryset.filter.return_value = mock_service_queryset
        mock_service_queryset.distinct.return_value = []
        mock_service.objects = mock_service_queryset

        view = AnalyticsViewSet.as_view({'get': 'appointments'})
        response = view(request)

        # Should not divide by zero - trend should be 0.0
        assert response.status_code == 200
        assert response.data['booking_trend_percent'] == 0.0

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_with_nonexistent_service_filter(self, mock_event, mock_service):
        """Test appointments endpoint with non-existent service_id filter"""
        request = self.factory.get('/api/analytics/analytics/appointments/?service_id=99999')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset - should return empty when filtered by non-existent service
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 0
        mock_queryset.select_related.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.__iter__ = Mock(return_value=iter([]))
        mock_event.objects = mock_queryset

        # Mock Service queryset
        mock_service_queryset = Mock()
        mock_service_queryset.filter.return_value = mock_service_queryset
        mock_service_queryset.distinct.return_value = []
        mock_service.objects = mock_service_queryset

        view = AnalyticsViewSet.as_view({'get': 'appointments'})
        response = view(request)

        # Should return empty results
        assert response.status_code == 200
        assert response.data['total'] == 0

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_with_invalid_status_filter(self, mock_event, mock_service):
        """Test appointments endpoint with invalid status filter"""
        request = self.factory.get('/api/analytics/analytics/appointments/?status=invalid_status')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 0
        mock_queryset.select_related.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.__iter__ = Mock(return_value=iter([]))
        mock_event.objects = mock_queryset

        # Mock Service queryset
        mock_service_queryset = Mock()
        mock_service_queryset.filter.return_value = mock_service_queryset
        mock_service_queryset.distinct.return_value = []
        mock_service.objects = mock_service_queryset

        view = AnalyticsViewSet.as_view({'get': 'appointments'})
        response = view(request)

        # Should still work (filter will just return empty)
        assert response.status_code == 200

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_with_events_without_resources(self, mock_event, mock_service):
        """Test appointments endpoint when events have no resources"""
        request = self.factory.get('/api/analytics/analytics/appointments/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Create mock events without resources
        now = timezone.now()
        mock_events = [
            Mock(start_time=now, status='confirmed', resource=None),
            Mock(start_time=now, status='confirmed', resource=None),
        ]

        # Mock queryset
        def filter_side_effect(*args, **kwargs):
            mock_filtered = Mock()
            mock_filtered.filter.return_value = mock_filtered
            mock_filtered.select_related.return_value = mock_filtered
            mock_filtered.distinct.return_value = mock_filtered
            mock_filtered.__iter__ = Mock(return_value=iter(mock_events))
            mock_filtered.count.return_value = 2

            return mock_filtered

        mock_queryset = Mock()
        mock_queryset.filter.side_effect = filter_side_effect
        mock_event.objects = mock_queryset

        # Mock Service queryset
        mock_service_queryset = Mock()
        mock_service_queryset.filter.return_value = mock_service_queryset
        mock_service_queryset.distinct.return_value = []
        mock_service.objects = mock_service_queryset

        view = AnalyticsViewSet.as_view({'get': 'appointments'})
        response = view(request)

        # Should handle events without resources
        assert response.status_code == 200
        assert response.data['by_resource'] == []


class TestRevenueEdgeCases:
    """Test revenue endpoint edge cases"""

    def setup_method(self):
        """Setup test data"""
        self.factory = APIRequestFactory()
        self.viewset = AnalyticsViewSet()

    def test_revenue_without_tenant(self):
        """Test revenue endpoint when tenant is missing"""
        request = self.factory.get('/api/analytics/analytics/revenue/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        # No tenant on request
        request.tenant = None

        # Mock Payment model
        mock_payment = Mock()
        with patch.dict('sys.modules', {'smoothschedule.commerce.payments.models': Mock(Payment=mock_payment)}):
            view = AnalyticsViewSet.as_view({'get': 'revenue'})
            response = view(request)

        # Should return 403 when tenant is None
        assert response.status_code == 403
        assert 'Payment analytics not available' in str(response.data)

    def test_revenue_without_payment_permission(self):
        """Test revenue endpoint when tenant lacks payment permission"""
        request = self.factory.get('/api/analytics/analytics/revenue/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        # Tenant exists but doesn't have payment permission
        mock_tenant = Mock()
        mock_tenant.has_feature.side_effect = lambda key: key == 'advanced_analytics'
        request.tenant = mock_tenant

        # Mock Payment model
        mock_payment = Mock()
        with patch.dict('sys.modules', {'smoothschedule.commerce.payments.models': Mock(Payment=mock_payment)}):
            view = AnalyticsViewSet.as_view({'get': 'revenue'})
            response = view(request)

        # Should return 403 without payment permission
        assert response.status_code == 403
        assert 'Payment analytics not available' in str(response.data)

    def test_revenue_with_no_payments(self):
        """Test revenue endpoint when there are no payments"""
        request = self.factory.get('/api/analytics/analytics/revenue/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        # Tenant with both permissions
        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Payment model with no payments
        mock_payment = Mock()
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.select_related.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.__iter__ = Mock(return_value=iter([]))
        mock_queryset.aggregate.return_value = {'amount_cents__sum': None}
        mock_queryset.count.return_value = 0
        mock_payment.objects = mock_queryset

        with patch.dict('sys.modules', {'smoothschedule.commerce.payments.models': Mock(Payment=mock_payment)}):
            view = AnalyticsViewSet.as_view({'get': 'revenue'})
            response = view(request)

        # Should handle None revenue gracefully
        assert response.status_code == 200
        assert response.data['total_revenue_cents'] == 0
        assert response.data['transaction_count'] == 0
        assert response.data['average_transaction_value_cents'] == 0

    def test_revenue_average_transaction_division_by_zero(self):
        """Test average transaction calculation when count is zero"""
        request = self.factory.get('/api/analytics/analytics/revenue/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Payment with 0 transactions
        mock_payment = Mock()
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.select_related.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.__iter__ = Mock(return_value=iter([]))
        mock_queryset.aggregate.return_value = {'amount_cents__sum': 0}
        mock_queryset.count.return_value = 0
        mock_payment.objects = mock_queryset

        with patch.dict('sys.modules', {'smoothschedule.commerce.payments.models': Mock(Payment=mock_payment)}):
            view = AnalyticsViewSet.as_view({'get': 'revenue'})
            response = view(request)

        # Should not divide by zero
        assert response.status_code == 200
        assert response.data['average_transaction_value_cents'] == 0

    def test_revenue_with_invalid_days_parameter(self):
        """Test revenue endpoint with invalid days parameter"""
        request = self.factory.get('/api/analytics/analytics/revenue/?days=invalid')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Payment model
        mock_payment = Mock()
        with patch.dict('sys.modules', {'smoothschedule.commerce.payments.models': Mock(Payment=mock_payment)}):
            view = AnalyticsViewSet.as_view({'get': 'revenue'})

            # Should raise ValueError
            with pytest.raises(ValueError):
                response = view(request)

    def test_revenue_with_negative_days(self):
        """Test revenue endpoint with negative days parameter"""
        request = self.factory.get('/api/analytics/analytics/revenue/?days=-30')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Payment model
        mock_payment = Mock()
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.select_related.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.__iter__ = Mock(return_value=iter([]))
        mock_queryset.aggregate.return_value = {'amount_cents__sum': 0}
        mock_queryset.count.return_value = 0
        mock_payment.objects = mock_queryset

        with patch.dict('sys.modules', {'smoothschedule.commerce.payments.models': Mock(Payment=mock_payment)}):
            view = AnalyticsViewSet.as_view({'get': 'revenue'})
            response = view(request)

        # Should still work
        assert response.status_code == 200
        assert response.data['period_days'] == -30

    def test_revenue_with_payments_without_service(self):
        """Test revenue endpoint when payments have no service"""
        request = self.factory.get('/api/analytics/analytics/revenue/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Create mock payments without service
        now = timezone.now()
        mock_payments = [
            Mock(created_at=now, amount_cents=1000, service=None),
            Mock(created_at=now, amount_cents=2000, service=None),
        ]

        # Mock Payment model
        mock_payment = Mock()
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.select_related.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.__iter__ = Mock(return_value=iter(mock_payments))
        mock_queryset.aggregate.return_value = {'amount_cents__sum': 3000}
        mock_queryset.count.return_value = 2
        mock_payment.objects = mock_queryset

        with patch.dict('sys.modules', {'smoothschedule.commerce.payments.models': Mock(Payment=mock_payment)}):
            view = AnalyticsViewSet.as_view({'get': 'revenue'})
            response = view(request)

        # Should handle payments without services
        assert response.status_code == 200
        assert response.data['total_revenue_cents'] == 3000
        assert response.data['by_service'] == []

    def test_revenue_with_large_amounts(self):
        """Test revenue endpoint with very large payment amounts"""
        request = self.factory.get('/api/analytics/analytics/revenue/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Payment with very large amounts
        mock_payment = Mock()
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.select_related.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.__iter__ = Mock(return_value=iter([]))
        mock_queryset.aggregate.return_value = {'amount_cents__sum': 999999999999}
        mock_queryset.count.return_value = 1
        mock_payment.objects = mock_queryset

        with patch.dict('sys.modules', {'smoothschedule.commerce.payments.models': Mock(Payment=mock_payment)}):
            view = AnalyticsViewSet.as_view({'get': 'revenue'})
            response = view(request)

        # Should handle large amounts
        assert response.status_code == 200
        assert response.data['total_revenue_cents'] == 999999999999
        assert response.data['average_transaction_value_cents'] == 999999999999


class TestDateRangeBoundaries:
    """Test date range boundary conditions"""

    def setup_method(self):
        """Setup test data"""
        self.factory = APIRequestFactory()

    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_dashboard_month_boundary(self, mock_event):
        """Test dashboard at month boundary (last day of month)"""
        request = self.factory.get('/api/analytics/analytics/dashboard/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 0
        mock_queryset.values.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.exists.return_value = False
        mock_queryset.extra.return_value = mock_queryset
        mock_queryset.annotate.return_value = mock_queryset
        mock_queryset.order_by.return_value = mock_queryset
        mock_event.objects = mock_queryset

        # Mock timezone.now() to be last day of month
        with patch('smoothschedule.scheduling.analytics.views.timezone') as mock_timezone:
            mock_timezone.now.return_value = datetime(2024, 1, 31, 23, 59, 59, tzinfo=UTC)

            view = AnalyticsViewSet.as_view({'get': 'dashboard'})
            response = view(request)

        # Should handle month boundary correctly
        assert response.status_code == 200
        assert 'period' in response.data

    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_dashboard_year_boundary(self, mock_event):
        """Test dashboard at year boundary (Dec 31)"""
        request = self.factory.get('/api/analytics/analytics/dashboard/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 0
        mock_queryset.values.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.exists.return_value = False
        mock_queryset.extra.return_value = mock_queryset
        mock_queryset.annotate.return_value = mock_queryset
        mock_queryset.order_by.return_value = mock_queryset
        mock_event.objects = mock_queryset

        # Mock timezone.now() to be last day of year
        with patch('smoothschedule.scheduling.analytics.views.timezone') as mock_timezone:
            mock_timezone.now.return_value = datetime(2024, 12, 31, 23, 59, 59, tzinfo=UTC)

            view = AnalyticsViewSet.as_view({'get': 'dashboard'})
            response = view(request)

        # Should handle year boundary correctly
        assert response.status_code == 200


class TestLargeDatasetHandling:
    """Test handling of large datasets"""

    def setup_method(self):
        """Setup test data"""
        self.factory = APIRequestFactory()

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_with_large_event_count(self, mock_event, mock_service):
        """Test appointments endpoint with very large number of events"""
        request = self.factory.get('/api/analytics/analytics/appointments/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset with large count
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 1000000  # 1 million events
        mock_queryset.select_related.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.__iter__ = Mock(return_value=iter([]))
        mock_event.objects = mock_queryset

        # Mock Service queryset
        mock_service_queryset = Mock()
        mock_service_queryset.filter.return_value = mock_service_queryset
        mock_service_queryset.distinct.return_value = []
        mock_service.objects = mock_service_queryset

        view = AnalyticsViewSet.as_view({'get': 'appointments'})
        response = view(request)

        # Should handle large counts
        assert response.status_code == 200
        assert response.data['total'] == 1000000

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_with_many_services(self, mock_event, mock_service):
        """Test appointments endpoint with many different services"""
        request = self.factory.get('/api/analytics/analytics/appointments/')

        mock_user = Mock()
        mock_user.is_authenticated = True
        force_authenticate(request, user=mock_user)

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Create many mock services
        mock_services = [Mock(id=i, name=f'Service {i}') for i in range(1000)]

        # Mock Event queryset
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 1000
        mock_queryset.select_related.return_value = mock_queryset
        mock_queryset.distinct.return_value = mock_queryset
        mock_queryset.__iter__ = Mock(return_value=iter([]))
        mock_event.objects = mock_queryset

        # Mock Service queryset
        mock_service_queryset = Mock()
        mock_service_queryset.filter.return_value = mock_service_queryset
        mock_service_queryset.distinct.return_value = mock_services
        mock_service.objects = mock_service_queryset

        view = AnalyticsViewSet.as_view({'get': 'appointments'})
        response = view(request)

        # Should handle many services
        assert response.status_code == 200
        assert len(response.data['by_service']) == 1000
