"""
Comprehensive unit tests for commerce.tickets.views module.

Tests all ViewSets, their actions, permissions, and business logic using mocks.
No database access - uses APIRequestFactory with mocked authentication.
"""
from unittest.mock import Mock, patch, MagicMock, PropertyMock
from datetime import datetime, timedelta
import pytest

from rest_framework.test import APIRequestFactory
from rest_framework import status as http_status
from rest_framework.response import Response
from django.utils import timezone
from django.db.models import Q

from smoothschedule.commerce.tickets.views import (
    TicketViewSet,
    TicketCommentViewSet,
    TicketTemplateViewSet,
    CannedResponseViewSet,
    IncomingTicketEmailViewSet,
    EmailProviderDetectView,
    TicketEmailAddressViewSet,
    RefreshTicketEmailsView,
    IsTenantUser,
    IsTicketOwnerOrAssigneeOrPlatformAdmin,
    IsPlatformAdmin,
    is_platform_admin,
    is_customer,
    detect_provider_from_mx,
    EMAIL_PROVIDERS,
)
from smoothschedule.commerce.tickets.models import (
    Ticket,
    TicketComment,
    TicketTemplate,
    CannedResponse,
    IncomingTicketEmail,
    TicketEmailAddress,
)
from smoothschedule.identity.users.models import User


# Helper functions tests
class TestHelperFunctions:
    """Test helper permission functions."""

    def test_is_platform_admin_with_superuser(self):
        user = Mock(role=User.Role.SUPERUSER)
        assert is_platform_admin(user) is True

    def test_is_platform_admin_with_platform_manager(self):
        user = Mock(role=User.Role.PLATFORM_MANAGER)
        assert is_platform_admin(user) is True

    def test_is_platform_admin_with_platform_support(self):
        user = Mock(role=User.Role.PLATFORM_SUPPORT)
        assert is_platform_admin(user) is True

    def test_is_platform_admin_with_regular_user(self):
        user = Mock(role=User.Role.TENANT_OWNER)
        assert is_platform_admin(user) is False

    def test_is_customer_true(self):
        user = Mock(role=User.Role.CUSTOMER)
        assert is_customer(user) is True

    def test_is_customer_false(self):
        user = Mock(role=User.Role.TENANT_OWNER)
        assert is_customer(user) is False


# Permission class tests
class TestIsTenantUser:
    """Test IsTenantUser permission class."""

    def test_has_permission_unauthenticated(self):
        permission = IsTenantUser()
        request = Mock(user=Mock(is_authenticated=False))
        view = Mock()

        assert permission.has_permission(request, view) is False

    def test_has_permission_platform_admin(self):
        permission = IsTenantUser()
        request = Mock(user=Mock(is_authenticated=True, role=User.Role.SUPERUSER))
        view = Mock()

        assert permission.has_permission(request, view) is True

    def test_has_permission_user_without_ticket_access(self):
        permission = IsTenantUser()
        user = Mock(is_authenticated=True, role=User.Role.TENANT_STAFF)
        user.can_access_tickets = Mock(return_value=False)
        request = Mock(user=user)
        view = Mock()

        assert permission.has_permission(request, view) is False

    def test_has_permission_user_without_tenant(self):
        permission = IsTenantUser()
        user = Mock(is_authenticated=True, role=User.Role.TENANT_STAFF, spec=['is_authenticated', 'role'])
        request = Mock(user=user)
        view = Mock()

        assert permission.has_permission(request, view) is False

    def test_has_permission_user_with_tenant(self):
        permission = IsTenantUser()
        user = Mock(is_authenticated=True, role=User.Role.TENANT_OWNER, tenant=Mock(id=1))
        request = Mock(user=user)
        view = Mock()

        assert permission.has_permission(request, view) is True


class TestIsTicketOwnerOrAssigneeOrPlatformAdmin:
    """Test IsTicketOwnerOrAssigneeOrPlatformAdmin permission class."""

    def test_has_object_permission_platform_admin(self):
        permission = IsTicketOwnerOrAssigneeOrPlatformAdmin()
        request = Mock(user=Mock(role=User.Role.SUPERUSER))
        view = Mock()
        ticket = Mock()

        assert permission.has_object_permission(request, view, ticket) is True

    def test_has_object_permission_creator(self):
        permission = IsTicketOwnerOrAssigneeOrPlatformAdmin()
        user = Mock(role=User.Role.CUSTOMER, tenant=Mock(id=1))
        request = Mock(user=user)
        view = Mock()
        ticket = Mock(creator=user, assignee=Mock(id=2), tenant=Mock(id=1))

        assert permission.has_object_permission(request, view, ticket) is True

    def test_has_object_permission_assignee(self):
        permission = IsTicketOwnerOrAssigneeOrPlatformAdmin()
        user = Mock(role=User.Role.TENANT_STAFF, tenant=Mock(id=1))
        request = Mock(user=user)
        view = Mock()
        ticket = Mock(creator=Mock(id=2), assignee=user, tenant=Mock(id=1))

        assert permission.has_object_permission(request, view, ticket) is True

    def test_has_object_permission_same_tenant(self):
        permission = IsTicketOwnerOrAssigneeOrPlatformAdmin()
        tenant = Mock(id=1)
        user = Mock(role=User.Role.TENANT_MANAGER, tenant=tenant)
        request = Mock(user=user)
        view = Mock()
        ticket = Mock(creator=Mock(id=2), assignee=Mock(id=3), tenant=tenant)

        assert permission.has_object_permission(request, view, ticket) is True

    def test_has_object_permission_different_tenant(self):
        permission = IsTicketOwnerOrAssigneeOrPlatformAdmin()
        user = Mock(role=User.Role.TENANT_MANAGER, tenant=Mock(id=1))
        request = Mock(user=user)
        view = Mock()
        ticket = Mock(creator=Mock(id=2), assignee=Mock(id=3), tenant=Mock(id=2))

        assert permission.has_object_permission(request, view, ticket) is False


class TestIsPlatformAdmin:
    """Test IsPlatformAdmin permission class."""

    def test_has_permission_unauthenticated(self):
        permission = IsPlatformAdmin()
        request = Mock(user=Mock(is_authenticated=False))
        view = Mock()

        assert permission.has_permission(request, view) is False

    def test_has_permission_platform_admin(self):
        permission = IsPlatformAdmin()
        request = Mock(user=Mock(is_authenticated=True, role=User.Role.PLATFORM_SUPPORT))
        view = Mock()

        assert permission.has_permission(request, view) is True

    def test_has_permission_regular_user(self):
        permission = IsPlatformAdmin()
        request = Mock(user=Mock(is_authenticated=True, role=User.Role.TENANT_OWNER))
        view = Mock()

        assert permission.has_permission(request, view) is False


# TicketViewSet tests
class TestTicketViewSet:
    """Test TicketViewSet."""

    def test_get_serializer_class_list(self):
        viewset = TicketViewSet()
        viewset.action = 'list'
        from smoothschedule.commerce.tickets.serializers import TicketListSerializer

        assert viewset.get_serializer_class() == TicketListSerializer

    def test_get_serializer_class_detail(self):
        viewset = TicketViewSet()
        viewset.action = 'retrieve'
        from smoothschedule.commerce.tickets.serializers import TicketSerializer

        assert viewset.get_serializer_class() == TicketSerializer

    def test_get_queryset_platform_admin(self):
        """Platform admins can call get_queryset without errors."""
        viewset = TicketViewSet()
        request = Mock(user=Mock(role=User.Role.SUPERUSER), sandbox_mode=False, query_params={})
        viewset.request = request

        # Mock the queryset to return mock chains
        mock_qs = Mock()
        mock_qs.filter.return_value = mock_qs
        mock_qs.distinct.return_value = mock_qs

        with patch.object(TicketViewSet, 'queryset', mock_qs):
            result = viewset.get_queryset()
            # Just verify it returns something (the mock)
            assert result is not None

    def test_get_queryset_customer(self):
        """Customers can call get_queryset without errors."""
        viewset = TicketViewSet()
        user = Mock(role=User.Role.CUSTOMER, id=1)
        request = Mock(user=user, sandbox_mode=False, query_params={})
        viewset.request = request

        mock_qs = Mock()
        mock_qs.filter.return_value = mock_qs
        mock_qs.distinct.return_value = mock_qs

        with patch.object(TicketViewSet, 'queryset', mock_qs):
            result = viewset.get_queryset()
            assert result is not None

    def test_get_queryset_with_filters(self):
        """Test query parameter filtering."""
        viewset = TicketViewSet()
        user = Mock(role=User.Role.SUPERUSER)
        request = Mock(
            user=user,
            sandbox_mode=False,
            query_params={
                'status': 'OPEN',
                'priority': 'HIGH',
                'category': 'TECHNICAL',
                'ticket_type': 'PLATFORM',
                'assignee': '123',
            }
        )
        viewset.request = request

        mock_qs = Mock()
        mock_qs.filter.return_value = mock_qs
        mock_qs.distinct.return_value = mock_qs

        with patch.object(TicketViewSet, 'queryset', mock_qs):
            result = viewset.get_queryset()
            # Verify filters were applied
            assert mock_qs.filter.call_count >= 1
            assert result is not None

    def test_perform_create_customer_ticket(self):
        """Test creating a customer ticket sets sandbox mode."""
        viewset = TicketViewSet()
        request = Mock(sandbox_mode=True)
        viewset.request = request

        serializer = Mock()
        serializer.validated_data = {'ticket_type': Ticket.TicketType.CUSTOMER}

        viewset.perform_create(serializer)

        # Should save with is_sandbox=True
        serializer.save.assert_called_once_with(is_sandbox=True)

    def test_perform_create_platform_ticket(self):
        """Test creating a platform ticket ignores sandbox mode."""
        viewset = TicketViewSet()
        request = Mock(sandbox_mode=True)
        viewset.request = request

        serializer = Mock()
        serializer.validated_data = {'ticket_type': Ticket.TicketType.PLATFORM}

        viewset.perform_create(serializer)

        # Platform tickets always created in live mode
        serializer.save.assert_called_once_with(is_sandbox=False)

    def test_perform_update_prevents_creator_change(self):
        """Test that updating a ticket cannot change creator."""
        viewset = TicketViewSet()

        serializer = Mock()
        serializer.validated_data = {
            'subject': 'Updated',
            'creator': Mock(id=999),
            'tenant': Mock(id=888),
        }

        viewset.perform_update(serializer)

        # Creator and tenant should be removed
        assert 'creator' not in serializer.validated_data
        assert 'tenant' not in serializer.validated_data
        serializer.save.assert_called_once()

    @patch.object(TicketViewSet, 'get_queryset')
    @patch.object(TicketViewSet, 'filter_queryset')
    @patch.object(TicketViewSet, 'paginate_queryset')
    @patch.object(TicketViewSet, 'get_serializer')
    def test_my_tickets_action(self, mock_serializer, mock_paginate, mock_filter, mock_get_qs):
        """Test my_tickets custom action."""
        factory = APIRequestFactory()
        request = factory.get('/api/tickets/my-tickets/')
        user = Mock(id=1, is_authenticated=True)
        request.user = user

        mock_queryset = Mock()
        mock_queryset.filter = Mock(return_value=mock_queryset)
        mock_queryset.distinct = Mock(return_value=mock_queryset)
        mock_get_qs.return_value = mock_queryset
        mock_filter.return_value = mock_queryset
        mock_paginate.return_value = None

        mock_serializer_instance = Mock()
        mock_serializer_instance.data = [{'id': 1}]
        mock_serializer.return_value = mock_serializer_instance

        viewset = TicketViewSet()
        viewset.request = request
        viewset.format_kwarg = None
        viewset.get_queryset = mock_get_qs
        viewset.filter_queryset = mock_filter
        viewset.paginate_queryset = mock_paginate
        viewset.get_serializer = mock_serializer

        response = viewset.my_tickets(request)

        # Should filter by creator OR assignee
        mock_queryset.filter.assert_called_once()
        assert isinstance(response, Response)
        assert response.status_code == 200

    @patch.object(TicketViewSet, 'get_queryset')
    def test_tenant_tickets_action_no_tenant(self, mock_get_qs):
        """Test tenant_tickets returns 403 for users without tenant."""
        factory = APIRequestFactory()
        request = factory.get('/api/tickets/tenant-tickets/')
        user = Mock(id=1, is_authenticated=True, role=User.Role.TENANT_STAFF, spec=['id', 'is_authenticated', 'role'])
        request.user = user

        viewset = TicketViewSet()
        viewset.request = request

        response = viewset.tenant_tickets(request)

        assert response.status_code == 403
        assert 'error' in response.data

    @patch.object(TicketViewSet, 'get_queryset')
    def test_tenant_tickets_action_customer_denied(self, mock_get_qs):
        """Test tenant_tickets returns 403 for customers."""
        factory = APIRequestFactory()
        request = factory.get('/api/tickets/tenant-tickets/')
        user = Mock(id=1, is_authenticated=True, role=User.Role.CUSTOMER, tenant=Mock(id=1))
        request.user = user

        viewset = TicketViewSet()
        viewset.request = request

        response = viewset.tenant_tickets(request)

        assert response.status_code == 403
        assert 'Customers should use the my-tickets endpoint' in response.data['error']

    @patch.object(TicketViewSet, 'get_queryset')
    @patch.object(TicketViewSet, 'filter_queryset')
    @patch.object(TicketViewSet, 'paginate_queryset')
    @patch.object(TicketViewSet, 'get_serializer')
    def test_tenant_tickets_action_success(self, mock_serializer, mock_paginate, mock_filter, mock_get_qs):
        """Test tenant_tickets returns tickets for tenant."""
        factory = APIRequestFactory()
        request = factory.get('/api/tickets/tenant-tickets/')
        tenant = Mock(id=1)
        user = Mock(id=1, is_authenticated=True, role=User.Role.TENANT_OWNER, tenant=tenant)
        request.user = user

        mock_queryset = Mock()
        mock_queryset.filter = Mock(return_value=mock_queryset)
        mock_get_qs.return_value = mock_queryset
        mock_filter.return_value = mock_queryset
        mock_paginate.return_value = None

        mock_serializer_instance = Mock()
        mock_serializer_instance.data = [{'id': 1}]
        mock_serializer.return_value = mock_serializer_instance

        viewset = TicketViewSet()
        viewset.request = request
        viewset.get_queryset = mock_get_qs
        viewset.filter_queryset = mock_filter
        viewset.paginate_queryset = mock_paginate
        viewset.get_serializer = mock_serializer

        response = viewset.tenant_tickets(request)

        mock_queryset.filter.assert_called_once()
        assert response.status_code == 200


# TicketCommentViewSet tests
class TestTicketCommentViewSet:
    """Test TicketCommentViewSet."""

    def test_get_queryset_filters_by_ticket_pk(self):
        """Test queryset filters by ticket_pk from URL."""
        viewset = TicketCommentViewSet()
        viewset.kwargs = {'ticket_pk': 123}
        user = Mock(role=User.Role.SUPERUSER, is_authenticated=True)
        viewset.request = Mock(user=user)

        mock_qs = Mock()
        mock_qs.filter.return_value = mock_qs
        mock_qs.distinct.return_value = mock_qs

        with patch.object(TicketCommentViewSet, 'queryset', mock_qs):
            result = viewset.get_queryset()
            assert result is not None
            assert mock_qs.filter.called

    def test_get_queryset_hides_internal_from_customers(self):
        """Test that customers cannot see internal comments."""
        viewset = TicketCommentViewSet()
        viewset.kwargs = {}
        tenant = Mock(id=1)
        user = Mock(role=User.Role.CUSTOMER, is_authenticated=True, tenant=tenant)
        viewset.request = Mock(user=user)

        mock_qs = Mock()
        mock_qs.filter.return_value = mock_qs
        mock_qs.distinct.return_value = mock_qs

        with patch.object(TicketCommentViewSet, 'queryset', mock_qs):
            result = viewset.get_queryset()
            assert result is not None
            # Customers should have filters applied
            assert mock_qs.filter.called

    @patch('smoothschedule.commerce.tickets.views.Ticket.objects.get')
    def test_perform_create_success(self, mock_ticket_get):
        """Test creating a comment associates it with ticket."""
        viewset = TicketCommentViewSet()
        viewset.kwargs = {'ticket_pk': 123}
        user = Mock(id=1)
        viewset.request = Mock(user=user)

        ticket = Mock(id=123)
        mock_ticket_get.return_value = ticket

        serializer = Mock()
        viewset.perform_create(serializer)

        mock_ticket_get.assert_called_once_with(pk=123)
        serializer.save.assert_called_once_with(ticket=ticket, author=user)

    @patch('smoothschedule.commerce.tickets.views.Ticket.objects.get')
    def test_perform_create_ticket_not_found(self, mock_ticket_get):
        """Test creating a comment fails if ticket doesn't exist."""
        from smoothschedule.commerce.tickets.models import Ticket

        viewset = TicketCommentViewSet()
        viewset.kwargs = {'ticket_pk': 999}
        viewset.request = Mock(user=Mock(id=1))

        mock_ticket_get.side_effect = Ticket.DoesNotExist

        serializer = Mock()

        with pytest.raises(Exception):
            viewset.perform_create(serializer)


# TicketTemplateViewSet tests
class TestTicketTemplateViewSet:
    """Test TicketTemplateViewSet."""

    def test_get_queryset_platform_admin_sees_all(self):
        """Platform admins see all templates."""
        viewset = TicketTemplateViewSet()
        user = Mock(role=User.Role.SUPERUSER, is_authenticated=True)
        viewset.request = Mock(user=user)

        mock_qs = Mock()

        with patch.object(TicketTemplateViewSet, 'queryset', mock_qs):
            result = viewset.get_queryset()
            # Platform admins get unfiltered queryset
            assert result == mock_qs

    def test_get_queryset_tenant_user_sees_own_and_platform(self):
        """Tenant users see their templates and platform-wide ones."""
        viewset = TicketTemplateViewSet()
        tenant = Mock(id=1)
        user = Mock(role=User.Role.TENANT_OWNER, is_authenticated=True, tenant=tenant)
        viewset.request = Mock(user=user)

        mock_qs = Mock()
        mock_qs.filter.return_value = mock_qs

        with patch.object(TicketTemplateViewSet, 'queryset', mock_qs):
            result = viewset.get_queryset()
            assert result is not None
            assert mock_qs.filter.called

    def test_get_queryset_user_without_tenant(self):
        """Users without tenant see only platform-wide templates."""
        viewset = TicketTemplateViewSet()
        user = Mock(role=User.Role.TENANT_STAFF, is_authenticated=True, spec=['role', 'is_authenticated'])
        viewset.request = Mock(user=user)

        mock_qs = Mock()
        mock_qs.filter.return_value = mock_qs

        with patch.object(TicketTemplateViewSet, 'queryset', mock_qs):
            result = viewset.get_queryset()
            assert result is not None
            assert mock_qs.filter.called

    def test_perform_create(self):
        """Test template creation."""
        viewset = TicketTemplateViewSet()
        serializer = Mock()

        viewset.perform_create(serializer)

        serializer.save.assert_called_once()


# CannedResponseViewSet tests
class TestCannedResponseViewSet:
    """Test CannedResponseViewSet."""

    def test_get_queryset_platform_admin(self):
        """Platform admins see all canned responses."""
        viewset = CannedResponseViewSet()
        user = Mock(role=User.Role.PLATFORM_MANAGER, is_authenticated=True)
        viewset.request = Mock(user=user)

        mock_qs = Mock()

        with patch.object(CannedResponseViewSet, 'queryset', mock_qs):
            result = viewset.get_queryset()
            assert result == mock_qs

    def test_get_queryset_tenant_user(self):
        """Tenant users see their responses and platform-wide ones."""
        viewset = CannedResponseViewSet()
        tenant = Mock(id=1)
        user = Mock(role=User.Role.TENANT_MANAGER, is_authenticated=True, tenant=tenant)
        viewset.request = Mock(user=user)

        mock_qs = Mock()
        mock_qs.filter.return_value = mock_qs

        with patch.object(CannedResponseViewSet, 'queryset', mock_qs):
            result = viewset.get_queryset()
            assert result is not None
            assert mock_qs.filter.called

    def test_perform_create(self):
        """Test canned response creation."""
        viewset = CannedResponseViewSet()
        serializer = Mock()

        viewset.perform_create(serializer)

        serializer.save.assert_called_once()

    @patch.object(CannedResponseViewSet, 'get_object')
    @patch.object(CannedResponseViewSet, 'get_serializer')
    def test_use_action_increments_count(self, mock_get_serializer, mock_get_object):
        """Test use action increments use_count."""
        factory = APIRequestFactory()
        request = factory.post('/api/canned-responses/1/use/')

        canned_response = Mock(use_count=5)
        mock_get_object.return_value = canned_response

        mock_serializer = Mock()
        mock_serializer.data = {'id': 1, 'use_count': 6}
        mock_get_serializer.return_value = mock_serializer

        viewset = CannedResponseViewSet()
        viewset.get_object = mock_get_object
        viewset.get_serializer = mock_get_serializer

        response = viewset.use(request, pk=1)

        assert canned_response.use_count == 6
        canned_response.save.assert_called_once_with(update_fields=['use_count'])
        assert response.status_code == 200


# IncomingTicketEmailViewSet tests
class TestIncomingTicketEmailViewSet:
    """Test IncomingTicketEmailViewSet."""

    def test_get_serializer_class_list(self):
        """Test list action uses list serializer."""
        viewset = IncomingTicketEmailViewSet()
        viewset.action = 'list'
        from smoothschedule.commerce.tickets.serializers import IncomingTicketEmailListSerializer

        assert viewset.get_serializer_class() == IncomingTicketEmailListSerializer

    def test_get_serializer_class_detail(self):
        """Test detail action uses full serializer."""
        viewset = IncomingTicketEmailViewSet()
        viewset.action = 'retrieve'
        from smoothschedule.commerce.tickets.serializers import IncomingTicketEmailSerializer

        assert viewset.get_serializer_class() == IncomingTicketEmailSerializer

    def test_get_queryset_with_filters(self):
        """Test queryset filtering by status and ticket."""
        viewset = IncomingTicketEmailViewSet()
        viewset.request = Mock(
            query_params={
                'status': 'PROCESSED',
                'ticket': '123',
            }
        )

        mock_qs = Mock()
        mock_qs.filter.return_value = mock_qs

        with patch.object(IncomingTicketEmailViewSet, 'queryset', mock_qs):
            result = viewset.get_queryset()
            assert result is not None
            # Should apply filters
            assert mock_qs.filter.called

    @patch.object(IncomingTicketEmailViewSet, 'get_object')
    def test_reprocess_already_processed(self, mock_get_object):
        """Test reprocess returns error for already processed email."""
        factory = APIRequestFactory()
        request = factory.post('/api/incoming-emails/1/reprocess/')

        incoming_email = Mock(processing_status=IncomingTicketEmail.ProcessingStatus.PROCESSED)
        mock_get_object.return_value = incoming_email

        viewset = IncomingTicketEmailViewSet()
        viewset.get_object = mock_get_object

        response = viewset.reprocess(request, pk=1)

        assert response.status_code == 400
        assert response.data['success'] is False

    @patch.object(IncomingTicketEmailViewSet, 'get_object')
    @patch('smoothschedule.commerce.tickets.email_receiver.TicketEmailReceiver')
    def test_reprocess_no_matching_ticket(self, mock_receiver_class, mock_get_object):
        """Test reprocess returns error when ticket not found."""
        factory = APIRequestFactory()
        request = factory.post('/api/incoming-emails/1/reprocess/', {}, format='json')

        incoming_email = Mock(
            processing_status=IncomingTicketEmail.ProcessingStatus.FAILED,
            from_address='user@example.com',
            raw_headers={},
            ticket_id_from_email=None,
        )
        mock_get_object.return_value = incoming_email

        mock_receiver = Mock()
        mock_receiver._find_matching_ticket.return_value = None
        mock_receiver_class.return_value = mock_receiver

        viewset = IncomingTicketEmailViewSet()
        viewset.get_object = mock_get_object

        response = viewset.reprocess(request, pk=1)

        assert response.status_code == 200
        assert response.data['success'] is False
        incoming_email.mark_no_match.assert_called_once()

    @patch.object(IncomingTicketEmailViewSet, 'get_object')
    @patch('smoothschedule.commerce.tickets.email_receiver.TicketEmailReceiver')
    @patch('smoothschedule.commerce.tickets.models.TicketComment.objects.create')
    def test_reprocess_success(self, mock_comment_create, mock_receiver_class, mock_get_object):
        """Test successful reprocess creates comment."""
        factory = APIRequestFactory()
        request = factory.post('/api/incoming-emails/1/reprocess/', {}, format='json')

        incoming_email = Mock(
            processing_status=IncomingTicketEmail.ProcessingStatus.FAILED,
            from_address='user@example.com',
            raw_headers={},
            ticket_id_from_email=None,
            extracted_reply='Test reply',
            body_text='Test body',
        )
        mock_get_object.return_value = incoming_email

        ticket = Mock(id=123, creator=Mock(email='user@example.com'))
        user = Mock(email='user@example.com')

        mock_receiver = Mock()
        mock_receiver._find_matching_ticket.return_value = ticket
        mock_receiver._find_user_by_email.return_value = user
        mock_receiver_class.return_value = mock_receiver

        comment = Mock(id=456)
        mock_comment_create.return_value = comment

        viewset = IncomingTicketEmailViewSet()
        viewset.get_object = mock_get_object

        response = viewset.reprocess(request, pk=1)

        assert response.status_code == 200
        assert response.data['success'] is True
        assert response.data['comment_id'] == 456
        incoming_email.mark_processed.assert_called_once()

    @patch.object(IncomingTicketEmailViewSet, 'get_object')
    @patch('smoothschedule.commerce.tickets.email_receiver.TicketEmailReceiver')
    def test_reprocess_exception_handling(self, mock_receiver_class, mock_get_object):
        """Test reprocess handles exceptions."""
        factory = APIRequestFactory()
        request = factory.post('/api/incoming-emails/1/reprocess/', {}, format='json')

        incoming_email = Mock(
            processing_status=IncomingTicketEmail.ProcessingStatus.FAILED,
            from_address='user@example.com',
            raw_headers={},
            ticket_id_from_email=None,
        )
        mock_get_object.return_value = incoming_email

        # Make the receiver method raise an exception
        mock_receiver = Mock()
        mock_receiver._find_matching_ticket.side_effect = Exception('Test error')
        mock_receiver_class.return_value = mock_receiver

        viewset = IncomingTicketEmailViewSet()
        viewset.get_object = mock_get_object

        response = viewset.reprocess(request, pk=1)

        assert response.status_code == 500
        assert response.data['success'] is False
        incoming_email.mark_failed.assert_called_once()


# EmailProviderDetectView tests
class TestEmailProviderDetectView:
    """Test EmailProviderDetectView."""

    def test_post_missing_email(self):
        """Test returns error when email is missing."""
        factory = APIRequestFactory()
        request = factory.post('/api/tickets/email-settings/detect/', {}, format='json')
        # Mock the data attribute
        request.data = {}

        view = EmailProviderDetectView()
        response = view.post(request)

        assert response.status_code == 400
        assert response.data['success'] is False

    def test_post_invalid_email(self):
        """Test returns error for invalid email format."""
        factory = APIRequestFactory()
        request = factory.post('/api/tickets/email-settings/detect/', {'email': 'invalid'}, format='json')
        request.data = {'email': 'invalid'}

        view = EmailProviderDetectView()
        response = view.post(request)

        assert response.status_code == 400
        assert response.data['success'] is False

    def test_post_known_provider(self):
        """Test detects known email provider."""
        factory = APIRequestFactory()
        request = factory.post('/api/tickets/email-settings/detect/', {'email': 'user@gmail.com'}, format='json')
        request.data = {'email': 'user@gmail.com'}

        view = EmailProviderDetectView()
        response = view.post(request)

        assert response.status_code == 200
        assert response.data['success'] is True
        assert response.data['detected'] is True
        assert response.data['provider'] == 'google'
        assert response.data['detected_via'] == 'domain_lookup'

    @patch('smoothschedule.commerce.tickets.views.detect_provider_from_mx')
    def test_post_custom_domain_mx_detected(self, mock_detect_mx):
        """Test detects provider from MX records for custom domain."""
        factory = APIRequestFactory()
        request = factory.post('/api/tickets/email-settings/detect/', {'email': 'user@company.com'}, format='json')
        request.data = {'email': 'user@company.com'}

        mock_detect_mx.return_value = {
            'provider': 'google',
            'display_name': 'Google Workspace',
            'detected_via': 'mx_record',
        }

        view = EmailProviderDetectView()
        response = view.post(request)

        assert response.status_code == 200
        assert response.data['success'] is True
        assert response.data['detected'] is True
        assert response.data['provider'] == 'google'
        assert response.data['detected_via'] == 'mx_record'

    @patch('smoothschedule.commerce.tickets.views.detect_provider_from_mx')
    def test_post_unknown_provider(self, mock_detect_mx):
        """Test returns generic settings for unknown provider."""
        factory = APIRequestFactory()
        request = factory.post('/api/tickets/email-settings/detect/', {'email': 'user@unknown.com'}, format='json')
        request.data = {'email': 'user@unknown.com'}

        mock_detect_mx.return_value = None

        view = EmailProviderDetectView()
        response = view.post(request)

        assert response.status_code == 200
        assert response.data['success'] is True
        assert response.data['detected'] is False
        assert response.data['provider'] == 'unknown'


# detect_provider_from_mx tests
class TestDetectProviderFromMx:
    """Test detect_provider_from_mx function."""

    @patch('dns.resolver.resolve')
    def test_detect_google_workspace(self, mock_resolve):
        """Test detects Google Workspace from MX records."""
        mock_record = Mock()
        mock_record.exchange = 'aspmx.l.google.com.'
        mock_resolve.return_value = [mock_record]

        result = detect_provider_from_mx('company.com')

        assert result is not None
        assert result['provider'] == 'google'
        assert result['display_name'] == 'Google Workspace'

    @patch('dns.resolver.resolve')
    def test_detect_microsoft_365(self, mock_resolve):
        """Test detects Microsoft 365 from MX records."""
        mock_record = Mock()
        mock_record.exchange = 'company-com.mail.protection.outlook.com.'
        mock_resolve.return_value = [mock_record]

        result = detect_provider_from_mx('company.com')

        assert result is not None
        assert result['provider'] == 'microsoft'

    @patch('dns.resolver.resolve')
    def test_detect_zoho(self, mock_resolve):
        """Test detects Zoho from MX records."""
        mock_record = Mock()
        mock_record.exchange = 'mx.zoho.com.'
        mock_resolve.return_value = [mock_record]

        result = detect_provider_from_mx('company.com')

        assert result is not None
        assert result['provider'] == 'zoho'

    @patch('dns.resolver.resolve')
    def test_detect_yahoo(self, mock_resolve):
        """Test detects Yahoo from MX records."""
        mock_record = Mock()
        mock_record.exchange = 'mta5.am0.yahoodns.net.'
        mock_resolve.return_value = [mock_record]

        result = detect_provider_from_mx('company.com')

        assert result is not None
        assert result['provider'] == 'yahoo'

    @patch('dns.resolver.resolve')
    def test_no_mx_records(self, mock_resolve):
        """Test returns None when no MX records found."""
        import dns.resolver
        mock_resolve.side_effect = dns.resolver.NoAnswer

        result = detect_provider_from_mx('invalid.com')

        assert result is None

    @patch('dns.resolver.resolve')
    def test_dns_exception(self, mock_resolve):
        """Test returns None on DNS exception."""
        mock_resolve.side_effect = Exception('DNS error')

        result = detect_provider_from_mx('error.com')

        assert result is None


# TicketEmailAddressViewSet tests
class TestTicketEmailAddressViewSet:
    """Test TicketEmailAddressViewSet."""

    def test_get_serializer_class_list(self):
        """Test list action uses list serializer."""
        viewset = TicketEmailAddressViewSet()
        viewset.action = 'list'
        from smoothschedule.commerce.tickets.serializers import TicketEmailAddressListSerializer

        assert viewset.get_serializer_class() == TicketEmailAddressListSerializer

    def test_get_serializer_class_detail(self):
        """Test detail action uses full serializer."""
        viewset = TicketEmailAddressViewSet()
        viewset.action = 'retrieve'
        from smoothschedule.commerce.tickets.serializers import TicketEmailAddressSerializer

        assert viewset.get_serializer_class() == TicketEmailAddressSerializer

    def test_get_queryset_platform_admin(self):
        """Platform admins see platform-wide email addresses."""
        viewset = TicketEmailAddressViewSet()
        user = Mock(role=User.Role.SUPERUSER, is_authenticated=True)
        viewset.request = Mock(user=user)

        mock_qs = Mock()
        mock_qs.filter.return_value = mock_qs
        mock_qs.select_related.return_value = mock_qs

        with patch('smoothschedule.commerce.tickets.views.TicketEmailAddress.objects', mock_qs):
            result = viewset.get_queryset()
            assert result is not None

    def test_get_queryset_tenant_owner(self):
        """Tenant owners see their email addresses."""
        viewset = TicketEmailAddressViewSet()
        tenant = Mock(id=1)
        user = Mock(role=User.Role.TENANT_OWNER, is_authenticated=True, tenant=tenant)
        viewset.request = Mock(user=user)

        mock_qs = Mock()
        mock_qs.filter.return_value = mock_qs

        with patch('smoothschedule.commerce.tickets.views.TicketEmailAddress.objects', mock_qs):
            result = viewset.get_queryset()
            assert result is not None

    def test_get_queryset_staff_denied(self):
        """Staff users cannot access email addresses."""
        viewset = TicketEmailAddressViewSet()
        tenant = Mock(id=1)
        user = Mock(role=User.Role.TENANT_STAFF, is_authenticated=True, tenant=tenant)
        viewset.request = Mock(user=user)

        mock_empty_qs = Mock()
        mock_qs = Mock()
        mock_qs.none.return_value = mock_empty_qs

        with patch('smoothschedule.commerce.tickets.views.TicketEmailAddress.objects', mock_qs):
            result = viewset.get_queryset()
            assert mock_qs.none.called

    def test_perform_create_platform_admin(self):
        """Test platform admin creates platform-wide email address."""
        viewset = TicketEmailAddressViewSet()
        user = Mock(role=User.Role.SUPERUSER)
        viewset.request = Mock(user=user)

        serializer = Mock()
        viewset.perform_create(serializer)

        serializer.save.assert_called_once_with(tenant=None)

    def test_perform_create_tenant_user(self):
        """Test tenant user creates email address for their tenant."""
        viewset = TicketEmailAddressViewSet()
        tenant = Mock(id=1)
        user = Mock(role=User.Role.TENANT_OWNER, tenant=tenant)
        viewset.request = Mock(user=user)

        serializer = Mock()
        viewset.perform_create(serializer)

        serializer.save.assert_called_once_with(tenant=tenant)

    @patch.object(TicketEmailAddressViewSet, 'get_object')
    @patch('smoothschedule.commerce.tickets.email_receiver.TicketEmailReceiver')
    def test_test_imap_success(self, mock_receiver_class, mock_get_object):
        """Test IMAP connection test success."""
        factory = APIRequestFactory()
        request = factory.post('/api/email-addresses/1/test_imap/', {}, format='json')

        email_address = Mock()
        mock_get_object.return_value = email_address

        mock_receiver = Mock()
        mock_receiver.connect.return_value = True
        mock_receiver_class.return_value = mock_receiver

        viewset = TicketEmailAddressViewSet()
        viewset.get_object = mock_get_object

        response = viewset.test_imap(request, pk=1)

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

    @patch.object(TicketEmailAddressViewSet, 'get_object')
    @patch('smoothschedule.commerce.tickets.email_receiver.TicketEmailReceiver')
    def test_test_imap_failure(self, mock_receiver_class, mock_get_object):
        """Test IMAP connection test failure."""
        factory = APIRequestFactory()
        request = factory.post('/api/email-addresses/1/test_imap/', {}, format='json')

        email_address = Mock()
        mock_get_object.return_value = email_address

        mock_receiver = Mock()
        mock_receiver.connect.return_value = False
        mock_receiver_class.return_value = mock_receiver

        viewset = TicketEmailAddressViewSet()
        viewset.get_object = mock_get_object

        response = viewset.test_imap(request, pk=1)

        assert response.status_code == 400
        assert response.data['success'] is False

    @patch.object(TicketEmailAddressViewSet, 'get_object')
    @patch('smoothschedule.commerce.tickets.email_notifications.TicketEmailService')
    def test_test_smtp_success(self, mock_service_class, mock_get_object):
        """Test SMTP connection test success."""
        factory = APIRequestFactory()
        request = factory.post('/api/email-addresses/1/test_smtp/', {}, format='json')

        email_address = Mock()
        mock_get_object.return_value = email_address

        mock_service = Mock()
        mock_service._test_smtp_connection.return_value = True
        mock_service_class.return_value = mock_service

        viewset = TicketEmailAddressViewSet()
        viewset.get_object = mock_get_object

        response = viewset.test_smtp(request, pk=1)

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

    @patch.object(TicketEmailAddressViewSet, 'get_object')
    @patch('smoothschedule.commerce.tickets.email_receiver.TicketEmailReceiver')
    def test_fetch_now_success(self, mock_receiver_class, mock_get_object):
        """Test manual email fetch success."""
        factory = APIRequestFactory()
        request = factory.post('/api/email-addresses/1/fetch_now/', {}, format='json')

        email_address = Mock()
        mock_get_object.return_value = email_address

        mock_receiver = Mock()
        mock_receiver.fetch_and_process_emails.return_value = 5
        mock_receiver_class.return_value = mock_receiver

        viewset = TicketEmailAddressViewSet()
        viewset.get_object = mock_get_object

        response = viewset.fetch_now(request, pk=1)

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

    @patch.object(TicketEmailAddressViewSet, 'get_object')
    @patch('smoothschedule.commerce.tickets.views.TicketEmailAddress.objects.filter')
    def test_set_as_default(self, mock_filter, mock_get_object):
        """Test setting email address as default."""
        factory = APIRequestFactory()
        request = factory.post('/api/email-addresses/1/set_as_default/')

        tenant = Mock(id=1)
        email_address = Mock(pk=1, tenant=tenant, display_name='Support')
        mock_get_object.return_value = email_address

        mock_others = Mock()
        mock_others.exclude.return_value = mock_others
        mock_filter.return_value = mock_others

        viewset = TicketEmailAddressViewSet()
        viewset.get_object = mock_get_object

        response = viewset.set_as_default(request, pk=1)

        assert response.status_code == 200
        assert response.data['success'] is True
        assert email_address.is_default is True
        email_address.save.assert_called_once()


# RefreshTicketEmailsView tests
class TestRefreshTicketEmailsView:
    """Test RefreshTicketEmailsView."""

    def test_post_non_platform_admin(self):
        """Test returns 403 for non-platform admin."""
        factory = APIRequestFactory()
        request = factory.post('/api/tickets/refresh-emails/')
        request.user = Mock(role=User.Role.TENANT_OWNER)

        view = RefreshTicketEmailsView()
        response = view.post(request)

        assert response.status_code == 403
        assert 'error' in response.data

    @patch('smoothschedule.platform.admin.models.PlatformEmailAddress.objects.filter')
    @patch('smoothschedule.commerce.tickets.email_receiver.PlatformEmailReceiver')
    def test_post_success(self, mock_receiver_class, mock_filter):
        """Test successful email refresh."""
        factory = APIRequestFactory()
        request = factory.post('/api/tickets/refresh-emails/')
        request.user = Mock(role=User.Role.SUPERUSER)

        email_address = Mock(
            email_address='support@platform.com',
            display_name='Platform Support',
            last_check_at=timezone.now(),
        )
        mock_default = Mock()
        mock_default.first.return_value = email_address
        mock_filter.return_value = mock_default

        mock_receiver = Mock()
        mock_receiver.fetch_and_process_emails.return_value = 10
        mock_receiver_class.return_value = mock_receiver

        view = RefreshTicketEmailsView()
        response = view.post(request)

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

    @patch('smoothschedule.platform.admin.models.PlatformEmailAddress.objects.filter')
    def test_post_no_default_email(self, mock_filter):
        """Test handles missing default email address."""
        factory = APIRequestFactory()
        request = factory.post('/api/tickets/refresh-emails/')
        request.user = Mock(role=User.Role.SUPERUSER)

        mock_default = Mock()
        mock_default.first.return_value = None
        mock_filter.return_value = mock_default

        view = RefreshTicketEmailsView()
        response = view.post(request)

        assert response.status_code == 200
        assert response.data['success'] is True
        assert 'no_default' in response.data['results'][0]['status']

    @patch('smoothschedule.platform.admin.models.PlatformEmailAddress.objects.filter')
    @patch('smoothschedule.commerce.tickets.email_receiver.PlatformEmailReceiver')
    def test_post_handles_exception(self, mock_receiver_class, mock_filter):
        """Test handles exception during email fetch."""
        factory = APIRequestFactory()
        request = factory.post('/api/tickets/refresh-emails/')
        request.user = Mock(role=User.Role.SUPERUSER)

        mock_filter.side_effect = Exception('Database error')

        view = RefreshTicketEmailsView()
        response = view.post(request)

        assert response.status_code == 200
        assert 'error' in response.data['results'][0]['status']
