"""
Tests for Calendar Sync Feature Permission

Tests the can_use_calendar_sync permission checking throughout the calendar sync system.
Uses mocks to avoid database hits and ensure fast, isolated tests.

Includes tests for:
- Permission denied when feature is disabled
- Permission granted when feature is enabled
- OAuth view permission checks
- Calendar sync view permission checks
"""

from unittest.mock import Mock, patch, MagicMock
from rest_framework.test import APIRequestFactory
from rest_framework import status

from smoothschedule.scheduling.schedule.calendar_sync_views import (
    CalendarSyncPermission,
    CalendarListView,
    CalendarSyncView,
    CalendarDeleteView,
    CalendarStatusView,
)


class TestCalendarSyncPermission:
    """Test suite for CalendarSyncPermission permission class"""

    def test_permission_denied_when_not_authenticated(self):
        """Test that unauthenticated users are denied access"""
        permission = CalendarSyncPermission()

        request = Mock()
        request.user = Mock(is_authenticated=False)

        view = Mock()

        result = permission.has_permission(request, view)
        assert result is False

    def test_permission_denied_when_no_tenant(self):
        """Test that permission is denied when request has no tenant"""
        permission = CalendarSyncPermission()

        request = Mock()
        request.user = Mock(is_authenticated=True)
        request.tenant = None

        view = Mock()

        result = permission.has_permission(request, view)
        assert result is False

    def test_permission_denied_when_tenant_lacks_calendar_sync(self):
        """Test that permission is denied when tenant.can_use_calendar_sync is False"""
        permission = CalendarSyncPermission()

        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = False

        request = Mock()
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = Mock()

        result = permission.has_permission(request, view)
        assert result is False
        mock_tenant.has_feature.assert_called_with('can_use_calendar_sync')

    def test_permission_granted_when_tenant_has_calendar_sync(self):
        """Test that permission is granted when tenant has calendar sync feature"""
        permission = CalendarSyncPermission()

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

        request = Mock()
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = Mock()

        result = permission.has_permission(request, view)
        assert result is True
        mock_tenant.has_feature.assert_called_with('can_use_calendar_sync')


class TestCalendarStatusView:
    """Test suite for CalendarStatusView"""

    def test_status_without_permission_shows_disabled(self):
        """Test that users without can_use_calendar_sync see feature as disabled"""
        # Create mock tenant without calendar sync
        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = False

        factory = APIRequestFactory()
        request = factory.get('/api/calendar/status/')
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarStatusView.as_view()
        response = view(request)

        assert response.status_code == 200
        assert response.data['success'] is True
        assert response.data['can_use_calendar_sync'] is False
        assert 'not available' in response.data['message'].lower()

    @patch('smoothschedule.scheduling.schedule.calendar_sync_views.OAuthCredential.objects')
    def test_status_with_permission_shows_enabled(self, mock_oauth_objects):
        """Test that users WITH can_use_calendar_sync see feature as enabled"""
        # Mock the count of connected calendars
        mock_queryset = Mock()
        mock_queryset.count.return_value = 2
        mock_oauth_objects.filter.return_value = mock_queryset

        # Create mock tenant with calendar sync
        mock_tenant = Mock()
        mock_tenant.has_feature.return_value = True

        factory = APIRequestFactory()
        request = factory.get('/api/calendar/status/')
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarStatusView.as_view()
        response = view(request)

        assert response.status_code == 200
        assert response.data['success'] is True
        assert response.data['can_use_calendar_sync'] is True
        assert response.data['feature_enabled'] is True
        assert response.data['total_connected'] == 2

    def test_status_without_tenant_returns_error(self):
        """Test that status view returns error when no tenant is present"""
        factory = APIRequestFactory()
        request = factory.get('/api/calendar/status/')
        request.user = Mock(is_authenticated=True)
        request.tenant = None

        view = CalendarStatusView.as_view()
        response = view(request)

        assert response.status_code == 400
        assert response.data['success'] is False
        assert 'tenant' in response.data['error'].lower()


class TestCalendarListView:
    """Test suite for CalendarListView"""

    @patch('smoothschedule.scheduling.schedule.calendar_sync_views.OAuthCredential.objects')
    def test_list_returns_empty_when_no_credentials(self, mock_oauth_objects):
        """Test that list returns empty array when tenant has no calendar credentials"""
        # Mock empty queryset
        mock_queryset = Mock()
        mock_queryset.order_by.return_value = []
        mock_oauth_objects.filter.return_value = mock_queryset

        mock_tenant = Mock()

        factory = APIRequestFactory()
        request = factory.get('/api/calendar/list/')
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarListView.as_view()
        response = view(request)

        assert response.status_code == 200
        assert response.data['success'] is True
        assert response.data['calendars'] == []

    @patch('smoothschedule.scheduling.schedule.calendar_sync_views.OAuthCredential.objects')
    def test_list_returns_calendar_credentials(self, mock_oauth_objects):
        """Test that list returns connected calendar credentials"""
        # Create mock credential
        mock_credential = Mock()
        mock_credential.id = 1
        mock_credential.get_provider_display.return_value = 'Google'
        mock_credential.email = 'user@gmail.com'
        mock_credential.is_valid = True
        mock_credential.is_expired.return_value = False
        mock_credential.last_used_at = None
        mock_credential.created_at = '2024-01-01T00:00:00Z'

        # Mock queryset
        mock_queryset = Mock()
        mock_queryset.order_by.return_value = [mock_credential]
        mock_oauth_objects.filter.return_value = mock_queryset

        mock_tenant = Mock()

        factory = APIRequestFactory()
        request = factory.get('/api/calendar/list/')
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarListView.as_view()
        response = view(request)

        assert response.status_code == 200
        assert response.data['success'] is True
        assert len(response.data['calendars']) == 1

        calendar = response.data['calendars'][0]
        assert calendar['id'] == 1
        assert calendar['email'] == 'user@gmail.com'
        assert calendar['provider'] == 'Google'
        assert calendar['is_valid'] is True

    def test_list_without_tenant_returns_error(self):
        """Test that list view returns error when no tenant is present"""
        factory = APIRequestFactory()
        request = factory.get('/api/calendar/list/')
        request.user = Mock(is_authenticated=True)
        request.tenant = None

        view = CalendarListView.as_view()
        response = view(request)

        # DRF permission classes return 403 before view code can return 400
        assert response.status_code in [400, 403]


class TestCalendarSyncView:
    """Test suite for CalendarSyncView"""

    @patch('smoothschedule.scheduling.schedule.calendar_sync_views.OAuthCredential.objects')
    def test_sync_requires_credential_id(self, mock_oauth_objects):
        """Test that sync endpoint requires credential_id parameter"""
        mock_tenant = Mock()

        factory = APIRequestFactory()
        request = factory.post('/api/calendar/sync/', {})
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarSyncView.as_view()
        response = view(request)

        assert response.status_code == 400
        assert response.data['success'] is False
        assert 'credential_id is required' in response.data['error']

    @patch('smoothschedule.scheduling.schedule.calendar_sync_views.OAuthCredential.objects')
    def test_sync_succeeds_with_valid_credential(self, mock_oauth_objects):
        """Test that sync succeeds when credential exists and is valid"""
        # Mock credential
        mock_credential = Mock()
        mock_credential.id = 1
        mock_credential.email = 'user@gmail.com'
        mock_credential.get_provider_display.return_value = 'Google'
        mock_credential.is_valid = True

        mock_oauth_objects.get.return_value = mock_credential

        mock_tenant = Mock()
        mock_tenant.name = 'Test Business'

        factory = APIRequestFactory()
        request = factory.post('/api/calendar/sync/', {
            'credential_id': 1,
            'calendar_id': 'primary',
            'start_date': '2025-01-01',
            'end_date': '2025-12-31',
        })
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarSyncView.as_view()
        response = view(request)

        assert response.status_code == 200
        assert response.data['success'] is True
        assert 'user@gmail.com' in response.data['message']

    @patch('smoothschedule.scheduling.schedule.calendar_sync_views.OAuthCredential.objects')
    def test_sync_fails_when_credential_not_found(self, mock_oauth_objects):
        """Test that sync fails when credential doesn't exist"""
        # Mock DoesNotExist exception
        from smoothschedule.identity.core.models import OAuthCredential
        mock_oauth_objects.get.side_effect = OAuthCredential.DoesNotExist

        mock_tenant = Mock()

        factory = APIRequestFactory()
        request = factory.post('/api/calendar/sync/', {
            'credential_id': 999,
        })
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarSyncView.as_view()
        response = view(request)

        assert response.status_code == 404
        assert response.data['success'] is False
        assert 'not found' in response.data['error'].lower()

    @patch('smoothschedule.scheduling.schedule.calendar_sync_views.OAuthCredential.objects')
    def test_sync_fails_when_credential_invalid(self, mock_oauth_objects):
        """Test that sync fails when credential is no longer valid"""
        # Mock invalid credential
        mock_credential = Mock()
        mock_credential.id = 1
        mock_credential.is_valid = False

        mock_oauth_objects.get.return_value = mock_credential

        mock_tenant = Mock()

        factory = APIRequestFactory()
        request = factory.post('/api/calendar/sync/', {
            'credential_id': 1,
        })
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarSyncView.as_view()
        response = view(request)

        assert response.status_code == 400
        assert response.data['success'] is False
        assert 'no longer valid' in response.data['error'].lower()

    def test_sync_without_tenant_returns_error(self):
        """Test that sync view returns error when no tenant is present"""
        factory = APIRequestFactory()
        request = factory.post('/api/calendar/sync/', {
            'credential_id': 1,
        })
        request.user = Mock(is_authenticated=True)
        request.tenant = None

        view = CalendarSyncView.as_view()
        response = view(request)

        # DRF permission classes return 403 before view code can return 400
        assert response.status_code in [400, 403]


class TestCalendarDeleteView:
    """Test suite for CalendarDeleteView (disconnect)"""

    @patch('smoothschedule.scheduling.schedule.calendar_sync_views.OAuthCredential.objects')
    def test_delete_requires_credential_id(self, mock_oauth_objects):
        """Test that delete endpoint requires credential_id parameter"""
        mock_tenant = Mock()

        factory = APIRequestFactory()
        request = factory.delete('/api/calendar/disconnect/', {})
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarDeleteView.as_view()
        response = view(request)

        assert response.status_code == 400
        assert response.data['success'] is False
        assert 'credential_id is required' in response.data['error']

    @patch('smoothschedule.scheduling.schedule.calendar_sync_views.OAuthCredential.objects')
    def test_delete_succeeds_with_valid_credential(self, mock_oauth_objects):
        """Test that delete succeeds when credential exists"""
        # Mock credential
        mock_credential = Mock()
        mock_credential.id = 1
        mock_credential.email = 'user@gmail.com'
        mock_credential.get_provider_display.return_value = 'Google'
        mock_credential.delete = Mock()

        mock_oauth_objects.get.return_value = mock_credential

        mock_tenant = Mock()
        mock_tenant.name = 'Test Business'

        factory = APIRequestFactory()
        request = factory.delete('/api/calendar/disconnect/', {
            'credential_id': 1,
        })
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarDeleteView.as_view()
        response = view(request)

        assert response.status_code == 200
        assert response.data['success'] is True
        assert 'disconnected' in response.data['message'].lower()
        assert 'user@gmail.com' in response.data['message']
        # Verify delete was called
        mock_credential.delete.assert_called_once()

    @patch('smoothschedule.scheduling.schedule.calendar_sync_views.OAuthCredential.objects')
    def test_delete_fails_when_credential_not_found(self, mock_oauth_objects):
        """Test that delete fails when credential doesn't exist"""
        # Mock DoesNotExist exception
        from smoothschedule.identity.core.models import OAuthCredential
        mock_oauth_objects.get.side_effect = OAuthCredential.DoesNotExist

        mock_tenant = Mock()

        factory = APIRequestFactory()
        request = factory.delete('/api/calendar/disconnect/', {
            'credential_id': 999,
        })
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarDeleteView.as_view()
        response = view(request)

        assert response.status_code == 404
        assert response.data['success'] is False
        assert 'not found' in response.data['error'].lower()

    def test_delete_without_tenant_returns_error(self):
        """Test that delete view returns error when no tenant is present"""
        factory = APIRequestFactory()
        request = factory.delete('/api/calendar/disconnect/', {
            'credential_id': 1,
        })
        request.user = Mock(is_authenticated=True)
        request.tenant = None

        view = CalendarDeleteView.as_view()
        response = view(request)

        # DRF permission classes return 403 before view code can return 400
        assert response.status_code in [400, 403]


class TestCalendarIntegrationWorkflow:
    """Integration-style tests that test the workflow without database hits"""

    @patch('smoothschedule.scheduling.schedule.calendar_sync_views.OAuthCredential.objects')
    def test_full_calendar_workflow_mocked(self, mock_oauth_objects):
        """
        Test complete workflow: Check status -> List -> Add -> Sync -> Remove

        Uses mocks to simulate the workflow without hitting the database.
        """
        mock_tenant = Mock()
        mock_tenant.name = 'Test Business'
        mock_tenant.has_feature.return_value = True

        # Step 1: Check status
        factory = APIRequestFactory()
        request = factory.get('/api/calendar/status/')
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        # Mock initial count (no credentials)
        mock_queryset = Mock()
        mock_queryset.count.return_value = 0
        mock_oauth_objects.filter.return_value = mock_queryset

        view = CalendarStatusView.as_view()
        response = view(request)

        assert response.status_code == 200
        assert response.data['can_use_calendar_sync'] is True
        assert response.data['total_connected'] == 0

        # Step 2: List calendars (empty initially)
        mock_queryset.order_by.return_value = []

        request = factory.get('/api/calendar/list/')
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarListView.as_view()
        response = view(request)

        assert response.status_code == 200
        assert len(response.data['calendars']) == 0

        # Step 3: Simulate adding a credential (would happen via OAuth callback)
        mock_credential = Mock()
        mock_credential.id = 1
        mock_credential.email = 'calendar@gmail.com'
        mock_credential.get_provider_display.return_value = 'Google'
        mock_credential.is_valid = True
        mock_credential.is_expired.return_value = False
        mock_credential.last_used_at = None
        mock_credential.created_at = '2025-01-01T00:00:00Z'

        # Step 4: List again (should see the credential)
        mock_queryset.order_by.return_value = [mock_credential]

        request = factory.get('/api/calendar/list/')
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarListView.as_view()
        response = view(request)

        assert response.status_code == 200
        assert len(response.data['calendars']) == 1
        assert response.data['calendars'][0]['email'] == 'calendar@gmail.com'

        # Step 5: Sync from the calendar
        mock_oauth_objects.get.return_value = mock_credential

        request = factory.post('/api/calendar/sync/', {
            'credential_id': 1,
            'calendar_id': 'primary',
            'start_date': '2025-01-01',
            'end_date': '2025-12-31',
        })
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarSyncView.as_view()
        response = view(request)

        assert response.status_code == 200
        assert response.data['success'] is True

        # Step 6: Disconnect the calendar
        mock_credential.delete = Mock()

        request = factory.delete('/api/calendar/disconnect/', {
            'credential_id': 1,
        })
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarDeleteView.as_view()
        response = view(request)

        assert response.status_code == 200
        assert response.data['success'] is True
        mock_credential.delete.assert_called_once()

        # Step 7: List again (should be empty)
        mock_queryset.order_by.return_value = []

        request = factory.get('/api/calendar/list/')
        request.user = Mock(is_authenticated=True)
        request.tenant = mock_tenant

        view = CalendarListView.as_view()
        response = view(request)

        assert response.status_code == 200
        assert len(response.data['calendars']) == 0


class TestTenantPermissionChecking:
    """Test suite for Tenant model's has_feature method behavior"""

    def test_tenant_has_feature_returns_false_by_default(self):
        """Test that has_feature returns False when feature is not enabled"""
        mock_tenant = Mock()
        mock_tenant.can_use_calendar_sync = False
        mock_tenant.has_feature = Mock(side_effect=lambda key: getattr(mock_tenant, key, False))

        result = mock_tenant.has_feature('can_use_calendar_sync')
        assert result is False

    def test_tenant_has_feature_returns_true_when_enabled(self):
        """Test that has_feature returns True when feature is enabled"""
        mock_tenant = Mock()
        mock_tenant.can_use_calendar_sync = True
        mock_tenant.has_feature = Mock(side_effect=lambda key: getattr(mock_tenant, key, False))

        result = mock_tenant.has_feature('can_use_calendar_sync')
        assert result is True

    def test_tenant_has_feature_checks_multiple_permissions(self):
        """Test that has_feature can check different permissions"""
        mock_tenant = Mock()
        mock_tenant.can_use_calendar_sync = True
        mock_tenant.can_use_webhooks = False

        def has_feature_impl(key):
            return getattr(mock_tenant, key, False)

        mock_tenant.has_feature = Mock(side_effect=has_feature_impl)

        # Calendar sync should be True
        result1 = mock_tenant.has_feature('can_use_calendar_sync')
        assert result1 is True

        # Webhooks should be False
        result2 = mock_tenant.has_feature('can_use_webhooks')
        assert result2 is False
