import json
from typing import Any
from unittest import mock

import requests
from typing_extensions import override

from zerver.lib.avatar import get_gravatar_url
from zerver.lib.exceptions import JsonableError
from zerver.lib.message_cache import MessageDict
from zerver.lib.outgoing_webhook import get_service_interface_class, process_success_response
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.timestamp import datetime_to_timestamp
from zerver.lib.topic import TOPIC_NAME
from zerver.models import Message
from zerver.models.bots import SLACK_INTERFACE
from zerver.models.realms import get_realm
from zerver.models.scheduled_jobs import NotificationTriggers
from zerver.models.streams import get_stream
from zerver.models.users import get_user
from zerver.openapi.openapi import validate_against_openapi_schema


class TestGenericOutgoingWebhookService(ZulipTestCase):
    @override
    def setUp(self) -> None:
        super().setUp()

        self.bot_user = get_user("outgoing-webhook@zulip.com", get_realm("zulip"))
        service_class = get_service_interface_class("whatever")  # GenericOutgoingWebhookService
        self.handler = service_class(
            service_name="test-service", token="abcdef", user_profile=self.bot_user
        )

    def test_process_success_response(self) -> None:
        event = dict(
            user_profile_id=99,
            message=dict(type="private"),
        )
        service_handler = self.handler

        response = mock.Mock(spec=requests.Response)
        response.status_code = 200
        response.text = json.dumps(dict(content="whatever"))

        with mock.patch("zerver.lib.outgoing_webhook.send_response_message") as m:
            process_success_response(
                event=event,
                service_handler=service_handler,
                response=response,
            )
        self.assertTrue(m.called)

        response = mock.Mock(spec=requests.Response)
        response.status_code = 200
        response.text = "unparsable text"

        with self.assertRaisesRegex(JsonableError, "Invalid JSON in response"):
            process_success_response(
                event=event,
                service_handler=service_handler,
                response=response,
            )

    def test_make_request(self) -> None:
        othello = self.example_user("othello")
        stream = get_stream("Denmark", othello.realm)
        message_id = self.send_stream_message(
            othello,
            stream.name,
            content="@**test**",
        )

        message = Message.objects.get(id=message_id)

        gravatar_url = get_gravatar_url(
            othello.delivery_email,
            othello.avatar_version,
            get_realm("zulip").id,
        )

        expected_message_data = {
            "avatar_url": gravatar_url,
            "client": "test suite",
            "content": "@**test**",
            "content_type": "text/x-markdown",
            "display_recipient": "Denmark",
            "id": message.id,
            "is_me_message": False,
            "reactions": [],
            "recipient_id": message.recipient_id,
            "rendered_content": "<p>@<strong>test</strong></p>",
            "sender_email": othello.email,
            "sender_full_name": "Othello, the Moor of Venice",
            "sender_id": othello.id,
            "sender_realm_str": "zulip",
            "stream_id": stream.id,
            TOPIC_NAME: "test",
            "submessages": [],
            "timestamp": datetime_to_timestamp(message.date_sent),
            "topic_links": [],
            "type": "stream",
        }

        wide_message_dict = MessageDict.wide_dict(message)

        event = {
            "command": "@**test**",
            "message": wide_message_dict,
            "trigger": "mention",
        }

        test_url = "https://example.com/example"
        with mock.patch.object(self.handler, "session") as session:
            self.handler.make_request(
                test_url,
                event,
                othello.realm,
            )
            session.post.assert_called_once()
            self.assertEqual(session.post.call_args[0], (test_url,))
            request_data = session.post.call_args[1]["json"]

        validate_against_openapi_schema(request_data, "/zulip-outgoing-webhook", "post", "200")
        self.assertEqual(request_data["bot_full_name"], self.bot_user.full_name)
        self.assertEqual(request_data["data"], "@**test**")
        self.assertEqual(request_data["token"], "abcdef")
        self.assertEqual(request_data["message"], expected_message_data)

        # Make sure we didn't accidentally mutate wide_message_dict.
        self.assertEqual(wide_message_dict["sender_realm_id"], othello.realm_id)

    def test_process_success(self) -> None:
        response: dict[str, Any] = dict(response_not_required=True)
        success_response = self.handler.process_success(response)
        self.assertEqual(success_response, None)

        response = dict(response_string="test_content")
        success_response = self.handler.process_success(response)
        self.assertEqual(success_response, dict(content="test_content"))

        response = dict(
            content="test_content",
            widget_content="test_widget_content",
            red_herring="whatever",
        )
        success_response = self.handler.process_success(response)
        expected_response = dict(
            content="test_content",
            widget_content="test_widget_content",
        )
        self.assertEqual(success_response, expected_response)

        response = {}
        success_response = self.handler.process_success(response)
        self.assertEqual(success_response, None)


class TestSlackOutgoingWebhookService(ZulipTestCase):
    @override
    def setUp(self) -> None:
        super().setUp()
        self.bot_user = get_user("outgoing-webhook@zulip.com", get_realm("zulip"))
        self.stream_message_event = {
            "command": "@**test**",
            "user_profile_id": 12,
            "service_name": "test-service",
            "trigger": "mention",
            "message": {
                "content": "test_content",
                "type": "stream",
                "sender_realm_str": "zulip",
                "sender_email": "sampleuser@zulip.com",
                "stream_id": "123",
                "display_recipient": "integrations",
                "timestamp": 123456,
                "sender_id": 21,
                "sender_full_name": "Sample User",
            },
        }

        self.private_message_event = {
            "user_profile_id": 24,
            "service_name": "test-service",
            "command": "test content",
            "trigger": NotificationTriggers.DIRECT_MESSAGE,
            "message": {
                "sender_id": 3,
                "sender_realm_str": "zulip",
                "timestamp": 1529821610,
                "sender_email": "cordelia@zulip.com",
                "type": "private",
                "sender_realm_id": 1,
                "id": 219,
                TOPIC_NAME: "test",
                "content": "test content",
            },
        }

        service_class = get_service_interface_class(SLACK_INTERFACE)
        self.handler = service_class(
            token="abcdef", user_profile=self.bot_user, service_name="test-service"
        )

    def test_make_request_stream_message(self) -> None:
        test_url = "https://example.com/example"
        with mock.patch.object(self.handler, "session") as session:
            self.handler.make_request(
                test_url,
                self.stream_message_event,
                self.bot_user.realm,
            )
            session.post.assert_called_once()
            self.assertEqual(session.post.call_args[0], (test_url,))
            request_data = session.post.call_args[1]["data"]

        self.assertEqual(request_data[0][1], "abcdef")  # token
        self.assertEqual(request_data[1][1], "T2")  # team_id
        self.assertEqual(request_data[2][1], "zulip.testserver")  # team_domain
        self.assertEqual(request_data[3][1], "C123")  # channel_id
        self.assertEqual(request_data[4][1], "integrations")  # channel_name
        self.assertEqual(request_data[5][1], 123456)  # thread_id
        self.assertEqual(request_data[6][1], 123456)  # timestamp
        self.assertEqual(request_data[7][1], "U21")  # user_id
        self.assertEqual(request_data[8][1], "Sample User")  # user_name
        self.assertEqual(request_data[9][1], "@**test**")  # text
        self.assertEqual(request_data[10][1], "mention")  # trigger_word
        self.assertEqual(request_data[11][1], 12)  # user_profile_id

    @mock.patch("zerver.lib.outgoing_webhook.fail_with_message")
    def test_make_request_private_message(self, mock_fail_with_message: mock.Mock) -> None:
        test_url = "https://example.com/example"
        with mock.patch.object(self.handler, "session") as session:
            response = self.handler.make_request(
                test_url,
                self.private_message_event,
                self.bot_user.realm,
            )
            session.post.assert_not_called()
        self.assertIsNone(response)
        self.assertTrue(mock_fail_with_message.called)

    def test_process_success(self) -> None:
        response: dict[str, Any] = dict(response_not_required=True)
        success_response = self.handler.process_success(response)
        self.assertEqual(success_response, None)

        response = dict(text="test_content")
        success_response = self.handler.process_success(response)
        self.assertEqual(success_response, dict(content="test_content"))
