import ujson
from typing import Any, Mapping, List

from zerver.lib.test_helpers import (
    tornado_redirected_to_list,
    queries_captured,
)
from zerver.lib.test_classes import (
    ZulipTestCase,
)
from zerver.models import (
    Huddle,
    get_huddle_hash,
)

class TypingValidateOperatorTest(ZulipTestCase):
    def test_missing_parameter(self) -> None:
        """
        Sending typing notification without op parameter fails
        """
        sender = self.example_user("hamlet")
        params = dict(
            to=ujson.dumps([sender.id]),
        )
        result = self.api_post(sender, '/api/v1/typing', params)
        self.assert_json_error(result, 'Missing \'op\' argument')

    def test_invalid_parameter(self) -> None:
        """
        Sending typing notification with invalid value for op parameter fails
        """
        sender = self.example_user("hamlet")
        params = dict(
            to=ujson.dumps([sender.id]),
            op='foo'
        )
        result = self.api_post(sender, '/api/v1/typing', params)
        self.assert_json_error(result, 'Invalid \'op\' value (should be start or stop)')

class TypingValidateUsersTest(ZulipTestCase):
    def test_empty_array(self) -> None:
        """
        Sending typing notification without recipient fails
        """
        sender = self.example_user("hamlet")
        result = self.api_post(sender, '/api/v1/typing', {'op': 'start', 'to': '[]'})
        self.assert_json_error(result, 'Missing parameter: \'to\' (recipient)')

    def test_missing_recipient(self) -> None:
        """
        Sending typing notification without recipient fails
        """
        sender = self.example_user("hamlet")
        result = self.api_post(sender, '/api/v1/typing', {'op': 'start'})
        self.assert_json_error(result, "Missing parameter: 'to' (recipient)")

    def test_argument_to_is_not_valid_json(self) -> None:
        """
        Sending typing notification to invalid recipient fails
        """
        sender = self.example_user("hamlet")
        invalid = 'bad email'
        result = self.api_post(sender, '/api/v1/typing', {'op': 'start', 'to': invalid})
        self.assert_json_error(result, "Invalid email 'bad email'")

    def test_bogus_user_id(self) -> None:
        """
        Sending typing notification to invalid recipient fails
        """
        sender = self.example_user("hamlet")
        invalid = '[9999999]'
        result = self.api_post(sender, '/api/v1/typing', {'op': 'start', 'to': invalid})
        self.assert_json_error(result, 'Invalid user ID 9999999')

class TypingHappyPathTest(ZulipTestCase):
    def test_start_to_single_recipient(self) -> None:
        sender = self.example_user('hamlet')
        recipient_user = self.example_user('othello')
        expected_recipients = {sender, recipient_user}
        expected_recipient_emails = {user.email for user in expected_recipients}
        expected_recipient_ids = {user.id for user in expected_recipients}

        params = dict(
            to=ujson.dumps([recipient_user.id]),
            op='start',
        )

        events: List[Mapping[str, Any]] = []
        with queries_captured() as queries:
            with tornado_redirected_to_list(events):
                result = self.api_post(sender, '/api/v1/typing', params)

        self.assert_json_success(result)
        self.assertEqual(len(events), 1)
        self.assertEqual(len(queries), 4)

        event = events[0]['event']
        event_recipient_emails = {user['email'] for user in event['recipients']}
        event_user_ids = set(events[0]['users'])
        event_recipient_user_ids = {user['user_id'] for user in event['recipients']}

        self.assertEqual(expected_recipient_ids, event_recipient_user_ids)
        self.assertEqual(expected_recipient_ids, event_user_ids)
        self.assertEqual(event['sender']['email'], sender.email)
        self.assertEqual(event_recipient_emails, expected_recipient_emails)
        self.assertEqual(event['type'], 'typing')
        self.assertEqual(event['op'], 'start')

    def test_start_to_multiple_recipients(self) -> None:
        sender = self.example_user('hamlet')
        recipient_users = [self.example_user('othello'), self.example_user('cordelia')]
        expected_recipients = set(recipient_users) | {sender}
        expected_recipient_emails = {user.email for user in expected_recipients}
        expected_recipient_ids = {user.id for user in expected_recipients}

        huddle_hash = get_huddle_hash(list(expected_recipient_ids))
        self.assertFalse(Huddle.objects.filter(huddle_hash=huddle_hash).exists())

        events: List[Mapping[str, Any]] = []

        params = dict(
            to=ujson.dumps([user.id for user in recipient_users]),
            op='start',
        )

        with queries_captured() as queries:
            with tornado_redirected_to_list(events):
                result = self.api_post(sender, '/api/v1/typing', params)
        self.assert_json_success(result)
        self.assertEqual(len(events), 1)
        self.assertEqual(len(queries), 5)

        # We should not be adding new Huddles just because
        # a user started typing in the compose box.  Let's
        # wait till they send an actual message.
        self.assertFalse(Huddle.objects.filter(huddle_hash=huddle_hash).exists())

        event = events[0]['event']
        event_recipient_emails = {user['email'] for user in event['recipients']}
        event_user_ids = set(events[0]['users'])
        event_recipient_user_ids = {user['user_id'] for user in event['recipients']}

        self.assertEqual(expected_recipient_ids, event_recipient_user_ids)
        self.assertEqual(expected_recipient_ids, event_user_ids)
        self.assertEqual(event['sender']['email'], sender.email)
        self.assertEqual(event_recipient_emails, expected_recipient_emails)
        self.assertEqual(event['type'], 'typing')
        self.assertEqual(event['op'], 'start')

    def test_start_to_self(self) -> None:
        """
        Sending typing notification to yourself (using user IDs)
        is successful.
        """
        user = self.example_user('hamlet')
        email = user.email
        expected_recipient_emails = {email}
        expected_recipient_ids = {user.id}
        events: List[Mapping[str, Any]] = []
        with tornado_redirected_to_list(events):
            result = self.api_post(
                user,
                '/api/v1/typing',
                {
                    'to': ujson.dumps([user.id]),
                    'op': 'start'
                }
            )
        self.assert_json_success(result)
        self.assertEqual(len(events), 1)

        event = events[0]['event']
        event_recipient_emails = {user['email'] for user in event['recipients']}
        event_user_ids = set(events[0]['users'])
        event_recipient_user_ids = {user['user_id'] for user in event['recipients']}

        self.assertEqual(expected_recipient_ids, event_recipient_user_ids)
        self.assertEqual(expected_recipient_ids, event_user_ids)
        self.assertEqual(event_recipient_emails, expected_recipient_emails)
        self.assertEqual(event['sender']['email'], email)
        self.assertEqual(event['type'], 'typing')
        self.assertEqual(event['op'], 'start')

    def test_start_to_another_user(self) -> None:
        """
        Sending typing notification to another user
        is successful.
        """
        sender = self.example_user('hamlet')
        recipient = self.example_user('othello')
        expected_recipients = {sender, recipient}
        expected_recipient_emails = {user.email for user in expected_recipients}
        expected_recipient_ids = {user.id for user in expected_recipients}

        params = dict(
            to=ujson.dumps([recipient.id]),
            op='start'
        )

        events: List[Mapping[str, Any]] = []
        with tornado_redirected_to_list(events):
            result = self.api_post(sender, '/api/v1/typing', params)

        self.assert_json_success(result)
        self.assertEqual(len(events), 1)

        event = events[0]['event']
        event_recipient_emails = {user['email'] for user in event['recipients']}
        event_user_ids = set(events[0]['users'])
        event_recipient_user_ids = {user['user_id'] for user in event['recipients']}

        self.assertEqual(expected_recipient_ids, event_recipient_user_ids)
        self.assertEqual(expected_recipient_ids, event_user_ids)
        self.assertEqual(event_recipient_emails, expected_recipient_emails)
        self.assertEqual(event['sender']['email'], sender.email)
        self.assertEqual(event['type'], 'typing')
        self.assertEqual(event['op'], 'start')

    def test_stop_to_self(self) -> None:
        """
        Sending stopped typing notification to yourself
        is successful.
        """
        user = self.example_user('hamlet')
        email = user.email
        expected_recipient_emails = {email}
        expected_recipient_ids = {user.id}

        events: List[Mapping[str, Any]] = []
        with tornado_redirected_to_list(events):
            params = dict(
                to=ujson.dumps([user.id]),
                op='stop'
            )
            result = self.api_post(user, '/api/v1/typing', params)

        self.assert_json_success(result)
        self.assertEqual(len(events), 1)

        event = events[0]['event']
        event_recipient_emails = {user['email'] for user in event['recipients']}
        event_user_ids = set(events[0]['users'])
        event_recipient_user_ids = {user['user_id'] for user in event['recipients']}

        self.assertEqual(expected_recipient_ids, event_recipient_user_ids)
        self.assertEqual(expected_recipient_ids, event_user_ids)
        self.assertEqual(event_recipient_emails, expected_recipient_emails)
        self.assertEqual(event['sender']['email'], email)
        self.assertEqual(event['type'], 'typing')
        self.assertEqual(event['op'], 'stop')

    def test_stop_to_another_user(self) -> None:
        """
        Sending stopped typing notification to another user
        is successful.
        """
        sender = self.example_user('hamlet')
        recipient = self.example_user('othello')
        expected_recipients = {sender, recipient}
        expected_recipient_emails = {user.email for user in expected_recipients}
        expected_recipient_ids = {user.id for user in expected_recipients}

        events: List[Mapping[str, Any]] = []
        with tornado_redirected_to_list(events):
            params = dict(
                to=ujson.dumps([recipient.id]),
                op='stop'
            )
            result = self.api_post(sender, '/api/v1/typing', params)

        self.assert_json_success(result)
        self.assertEqual(len(events), 1)

        event = events[0]['event']
        event_recipient_emails = {user['email'] for user in event['recipients']}
        event_user_ids = set(events[0]['users'])
        event_recipient_user_ids = {user['user_id'] for user in event['recipients']}

        self.assertEqual(expected_recipient_ids, event_recipient_user_ids)
        self.assertEqual(expected_recipient_ids, event_user_ids)
        self.assertEqual(event_recipient_emails, expected_recipient_emails)
        self.assertEqual(event['sender']['email'], sender.email)
        self.assertEqual(event['type'], 'typing')
        self.assertEqual(event['op'], 'stop')

class TypingLegacyMobileSupportTest(ZulipTestCase):
    def test_legacy_email_interface(self) -> None:
        '''
        We are keeping the email interface on life support
        for a couple months until we get some of our
        mobile users upgraded.
        '''
        sender = self.example_user('hamlet')
        othello = self.example_user('othello')
        cordelia = self.example_user('cordelia')

        emails = [othello.email, cordelia.email]

        params = dict(
            to=ujson.dumps(emails),
            op='start',
        )

        events: List[Mapping[str, Any]] = []
        with tornado_redirected_to_list(events):
            result = self.api_post(sender, '/api/v1/typing', params)

        self.assert_json_success(result)
        event = events[0]['event']

        event_recipient_user_ids = {
            user['user_id']
            for user in event['recipients']
        }

        self.assertEqual(
            event_recipient_user_ids,
            {sender.id, othello.id, cordelia.id}
        )
