"""
Analytics API Tests

Tests for permission gating and endpoint functionality using mocks.
No database access - fast, isolated unit tests.
"""
import pytest
from unittest.mock import Mock, patch, MagicMock, PropertyMock
from django.utils import timezone
from rest_framework.test import APIRequestFactory, force_authenticate
from rest_framework import status
from datetime import timedelta

from smoothschedule.scheduling.analytics.views import AnalyticsViewSet


class TestAnalyticsPermissions:
    """Test permission gating for analytics endpoints"""

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

    def test_analytics_requires_authentication(self):
        """Test that analytics endpoints require authentication"""
        request = self.factory.get('/api/analytics/analytics/dashboard/')
        # Don't set user at all - unauthenticated

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

        # DRF returns 403 when user is not authenticated and permission class is evaluated
        assert response.status_code in [401, 403]

    def test_analytics_denied_without_permission(self):
        """Test that analytics is denied without advanced_analytics permission"""
        request = self.factory.get('/api/analytics/analytics/dashboard/')

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

        # Mock tenant without permission
        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = False
        request.tenant = mock_tenant

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

        assert response.status_code == 403
        mock_tenant.has_feature.assert_called_once_with('advanced_analytics')
        assert 'Advanced Analytics' in str(response.data)
        assert 'upgrade your subscription' in str(response.data).lower()

    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_analytics_allowed_with_permission(self, mock_event):
        """Test that analytics is allowed with advanced_analytics permission"""
        request = self.factory.get('/api/analytics/analytics/dashboard/')

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

        # Mock tenant with permission
        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True
        request.tenant = mock_tenant

        # Mock Event queryset to return empty results
        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 'total_appointments_this_month' in response.data

    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_dashboard_endpoint_structure(self, mock_event):
        """Test dashboard endpoint returns correct data structure"""
        request = self.factory.get('/api/analytics/analytics/dashboard/')

        # Setup mocked user with permission
        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 = 5
        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

        # Check required fields
        required_fields = [
            'total_appointments_this_month',
            'total_appointments_all_time',
            'active_resources_count',
            'active_services_count',
            'upcoming_appointments_count',
            'average_appointment_duration_minutes',
            'peak_booking_day',
            'peak_booking_hour',
            'period'
        ]

        for field in required_fields:
            assert field in response.data, f"Missing field: {field}"

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_endpoint_with_filters(self, mock_event, mock_service):
        """Test appointments endpoint with query parameters"""
        request = self.factory.get('/api/analytics/analytics/appointments/?days=7&service_id=1')

        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 proper chaining
        mock_queryset = Mock()
        mock_queryset.filter.return_value = mock_queryset
        mock_queryset.count.return_value = 10
        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 'total' in response.data
        assert 'by_status' in response.data
        assert 'by_service' in response.data
        assert 'by_resource' in response.data
        assert 'period_days' in response.data
        assert response.data['period_days'] == 7

    def test_revenue_requires_payments_permission(self):
        """Test that revenue analytics requires both permissions"""
        request = self.factory.get('/api/analytics/analytics/revenue/')

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

        # Mock tenant with advanced_analytics but NOT can_accept_payments
        mock_tenant = Mock()
        mock_tenant.has_feature.side_effect = lambda key: key == 'advanced_analytics'
        request.tenant = mock_tenant

        # Mock the Payment model to avoid ImportError
        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 be denied because tenant doesn't have can_accept_payments
        assert response.status_code == 403
        assert 'Payment analytics not available' in str(response.data)

    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_multiple_permission_check(self, mock_event):
        """Test that both IsAuthenticated and HasFeaturePermission are checked"""
        # Test 1: No auth = 403/401
        request = self.factory.get('/api/analytics/analytics/dashboard/')

        view = AnalyticsViewSet.as_view({'get': 'dashboard'})
        response = view(request)
        assert response.status_code in [401, 403]

        # Test 2: With auth but no permission = 403
        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 = False
        request.tenant = mock_tenant

        response = view(request)
        assert response.status_code == 403


class TestAnalyticsData:
    """Test analytics data calculation using mocks"""

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

    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_dashboard_counts_appointments_correctly(self, mock_event):
        """Test that dashboard counts appointments accurately"""
        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.objects to return different counts for different filters
        mock_queryset = Mock()

        def filter_side_effect(*args, **kwargs):
            mock_filtered = Mock()
            mock_filtered.filter.return_value = mock_filtered
            mock_filtered.values.return_value = mock_filtered
            mock_filtered.distinct.return_value = mock_filtered
            mock_filtered.extra.return_value = mock_filtered
            mock_filtered.annotate.return_value = mock_filtered
            mock_filtered.order_by.return_value = mock_filtered
            mock_filtered.exists.return_value = False

            # Return different counts based on the filter
            # This month: 5, All time: 6
            if 'start_time__gte' in kwargs and 'start_time__lt' in kwargs:
                mock_filtered.count.return_value = 5  # This month
            elif 'start_time__gte' in kwargs and 'start_time__lt' not in kwargs:
                mock_filtered.count.return_value = 3  # Upcoming
            else:
                mock_filtered.count.return_value = 6  # All time

            return mock_filtered

        mock_queryset.filter.side_effect = filter_side_effect
        mock_event.objects = mock_queryset

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

        assert response.status_code == 200
        assert 'total_appointments_this_month' in response.data
        assert 'total_appointments_all_time' in response.data

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_appointments_counts_by_status(self, mock_event, mock_service):
        """Test that appointments are counted by status"""
        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 base query mock that tracks filter chains
        base_query = Mock()
        base_query.select_related.return_value = base_query
        base_query.distinct.return_value = base_query
        base_query.__iter__ = Mock(return_value=iter([]))

        # Mock different counts for different statuses
        def filter_side_effect(*args, **kwargs):
            # Return a new mock for each filter call
            filtered_mock = Mock()
            filtered_mock.filter.side_effect = filter_side_effect  # Allow further chaining
            filtered_mock.select_related.return_value = filtered_mock
            filtered_mock.distinct.return_value = filtered_mock
            filtered_mock.__iter__ = Mock(return_value=iter([]))

            # Determine count based on status filter
            if 'status' in kwargs:
                status_value = kwargs['status']
                if status_value == 'confirmed':
                    filtered_mock.count.return_value = 7
                elif status_value == 'cancelled':
                    filtered_mock.count.return_value = 2
                elif status_value == 'no_show':
                    filtered_mock.count.return_value = 1
                else:
                    filtered_mock.count.return_value = 0
            elif 'start_time__gte' in kwargs:
                # Initial filter - return base query
                filtered_mock.count.return_value = 10
            else:
                filtered_mock.count.return_value = 10  # Total

            return filtered_mock

        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)

        assert response.status_code == 200
        assert 'by_status' in response.data
        assert response.data['by_status']['confirmed'] == 7
        assert response.data['by_status']['cancelled'] == 2
        assert response.data['by_status']['no_show'] == 1
        assert response.data['total'] == 10

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_cancellation_rate_calculation(self, mock_event, mock_service):
        """Test cancellation rate is calculated correctly"""
        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: 100 total appointments, 20 cancelled
        def filter_side_effect(*args, **kwargs):
            # Return a new mock for each filter call
            filtered_mock = Mock()
            filtered_mock.filter.side_effect = filter_side_effect  # Allow further chaining
            filtered_mock.select_related.return_value = filtered_mock
            filtered_mock.distinct.return_value = filtered_mock
            filtered_mock.__iter__ = Mock(return_value=iter([]))

            if 'status' in kwargs:
                if kwargs['status'] == 'cancelled':
                    filtered_mock.count.return_value = 20
                elif kwargs['status'] == 'confirmed':
                    filtered_mock.count.return_value = 80
                elif kwargs['status'] == 'no_show':
                    filtered_mock.count.return_value = 0
                else:
                    filtered_mock.count.return_value = 0
            elif 'start_time__gte' in kwargs:
                # Initial filter - return base query
                filtered_mock.count.return_value = 100
            else:
                filtered_mock.count.return_value = 100  # Total

            return filtered_mock

        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)

        assert response.status_code == 200
        # 20 cancelled / 100 total = 20%
        assert response.data['cancellation_rate_percent'] == 20.0

    def test_revenue_endpoint_with_payment_permission(self):
        """Test revenue endpoint when both permissions are granted"""
        request = self.factory.get('/api/analytics/analytics/revenue/')

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

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

        # Mock Payment model and queryset
        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 aggregate for revenue sum
        mock_queryset.aggregate.return_value = {'amount_cents__sum': 10000}
        mock_queryset.count.return_value = 5

        mock_payment.objects = mock_queryset

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

        assert response.status_code == 200
        assert 'total_revenue_cents' in response.data
        assert 'transaction_count' in response.data
        assert 'average_transaction_value_cents' in response.data
        assert response.data['total_revenue_cents'] == 10000
        assert response.data['transaction_count'] == 5

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_daily_breakdown_structure(self, mock_event, mock_service):
        """Test that daily breakdown is properly structured"""
        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 with dates
        now = timezone.now()
        mock_events = [
            Mock(
                start_time=now,
                status='confirmed',
                resource=None
            ),
            Mock(
                start_time=now + timedelta(days=1),
                status='cancelled',
                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))

            if 'status' in kwargs:
                status_value = kwargs['status']
                if status_value == 'confirmed':
                    mock_filtered.count.return_value = 1
                elif status_value == 'cancelled':
                    mock_filtered.count.return_value = 1
                else:
                    mock_filtered.count.return_value = 0
            else:
                mock_filtered.count.return_value = 2  # Total

            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)

        assert response.status_code == 200
        assert 'daily_breakdown' in response.data
        assert isinstance(response.data['daily_breakdown'], list)
        # Each item should have date, count, and status_breakdown
        if len(response.data['daily_breakdown']) > 0:
            item = response.data['daily_breakdown'][0]
            assert 'date' in item
            assert 'count' in item
            assert 'status_breakdown' in item

    @patch('smoothschedule.scheduling.analytics.views.Service')
    @patch('smoothschedule.scheduling.analytics.views.Event')
    def test_booking_trend_calculation(self, mock_event, mock_service):
        """Test booking trend percentage calculation"""
        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: Current period has 120 appointments, previous had 100
        # Trend should be +20%
        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
                mock_filtered.count.return_value = 100
            elif 'status' in kwargs:
                # Status filter - divide evenly
                mock_filtered.count.return_value = 40
            else:
                # Current period total
                mock_filtered.count.return_value = 120

            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)

        assert response.status_code == 200
        assert 'booking_trend_percent' in response.data
        # (120 - 100) / 100 * 100 = 20%
        assert response.data['booking_trend_percent'] == 20.0
