from __future__ import annotations

from unittest.mock import AsyncMock, patch

from django.test import TestCase
from django.urls import reverse

from core.models import ChatSession, Message, Person, PersonIdentifier, User


class ComposeReactTests(TestCase):
    def setUp(self):
        self.user = User.objects.create_user("compose-react", "react@example.com", "pw")
        self.client.force_login(self.user)

    def _build_message(
        self, *, service: str, identifier: str, source_message_id: str = ""
    ):
        person = Person.objects.create(user=self.user, name=f"{service} person")
        person_identifier = PersonIdentifier.objects.create(
            user=self.user,
            person=person,
            service=service,
            identifier=identifier,
        )
        session = ChatSession.objects.create(
            user=self.user,
            identifier=person_identifier,
        )
        message = Message.objects.create(
            user=self.user,
            session=session,
            ts=1770000000123,
            sender_uuid="sender-1",
            text="hello",
            source_service=service,
            source_message_id=source_message_id or "",
            source_chat_id=identifier,
            receipt_payload={},
        )
        return person, person_identifier, message

    @patch("core.views.compose.history.apply_reaction", new_callable=AsyncMock)
    @patch("core.views.compose.transport.send_reaction", new_callable=AsyncMock)
    def test_signal_react_uses_numeric_source_message_id_timestamp(
        self, mocked_send_reaction, mocked_apply_reaction
    ):
        person, _, message = self._build_message(
            service="signal",
            identifier="+15551230000",
            source_message_id="1771234567000",
        )
        mocked_send_reaction.return_value = True
        mocked_apply_reaction.return_value = message

        response = self.client.post(
            reverse("compose_react"),
            {
                "service": "signal",
                "identifier": "+15551230000",
                "person": str(person.id),
                "message_id": str(message.id),
                "emoji": "👍",
            },
        )

        self.assertEqual(200, response.status_code)
        payload = response.json()
        self.assertTrue(payload["ok"])
        self.assertEqual("👍", payload["emoji"])
        self.assertFalse(payload["remove"])
        mocked_send_reaction.assert_awaited_once()
        _, kwargs = mocked_send_reaction.await_args
        self.assertEqual("signal", mocked_send_reaction.await_args.args[0])
        self.assertEqual("+15551230000", mocked_send_reaction.await_args.args[1])
        self.assertEqual(1771234567000, kwargs["target_timestamp"])
        self.assertEqual("sender-1", kwargs["target_author"])
        self.assertEqual("", kwargs["target_message_id"])

    @patch("core.views.compose.history.apply_reaction", new_callable=AsyncMock)
    @patch("core.views.compose.transport.send_reaction", new_callable=AsyncMock)
    def test_whatsapp_react_uses_upstream_message_id(
        self, mocked_send_reaction, mocked_apply_reaction
    ):
        person, _, message = self._build_message(
            service="whatsapp",
            identifier="12345@s.whatsapp.net",
            source_message_id="wamid.ABC123",
        )
        mocked_send_reaction.return_value = True
        mocked_apply_reaction.return_value = message

        response = self.client.post(
            reverse("compose_react"),
            {
                "service": "whatsapp",
                "identifier": "12345@s.whatsapp.net",
                "person": str(person.id),
                "message_id": str(message.id),
                "emoji": "❤️",
            },
        )

        self.assertEqual(200, response.status_code)
        payload = response.json()
        self.assertTrue(payload["ok"])
        self.assertEqual("wamid.ABC123", payload["target_upstream_id"])
        mocked_send_reaction.assert_awaited_once()
        _, kwargs = mocked_send_reaction.await_args
        self.assertEqual("wamid.ABC123", kwargs["target_message_id"])

    @patch("core.views.compose.transport.send_reaction", new_callable=AsyncMock)
    def test_toggle_removes_existing_actor_reaction(self, mocked_send_reaction):
        person, _, message = self._build_message(
            service="signal",
            identifier="+15551230000",
            source_message_id="1771234567000",
        )
        message.receipt_payload = {
            "reactions": [
                {
                    "emoji": "👍",
                    "actor": f"web:{self.user.id}:signal",
                    "source_service": "signal",
                    "removed": False,
                }
            ]
        }
        message.save(update_fields=["receipt_payload"])
        mocked_send_reaction.return_value = True

        with patch(
            "core.views.compose.history.apply_reaction",
            new=AsyncMock(return_value=message),
        ):
            response = self.client.post(
                reverse("compose_react"),
                {
                    "service": "signal",
                    "identifier": "+15551230000",
                    "person": str(person.id),
                    "message_id": str(message.id),
                    "emoji": "👍",
                },
            )

        self.assertEqual(200, response.status_code)
        payload = response.json()
        self.assertTrue(payload["ok"])
        self.assertTrue(payload["remove"])
        _, kwargs = mocked_send_reaction.await_args
        self.assertTrue(kwargs["remove"])

    def test_unsupported_service_returns_error(self):
        response = self.client.post(
            reverse("compose_react"),
            {
                "service": "xmpp",
                "identifier": "someone@example.com",
                "message_id": "does-not-matter",
                "emoji": "👍",
            },
        )
        self.assertEqual(200, response.status_code)
        self.assertEqual(
            {"ok": False, "error": "service_not_supported"},
            response.json(),
        )

    def test_missing_whatsapp_target_returns_error(self):
        person, _, message = self._build_message(
            service="whatsapp",
            identifier="12345@s.whatsapp.net",
            source_message_id="",
        )
        response = self.client.post(
            reverse("compose_react"),
            {
                "service": "whatsapp",
                "identifier": "12345@s.whatsapp.net",
                "person": str(person.id),
                "message_id": str(message.id),
                "emoji": "😂",
            },
        )
        self.assertEqual(200, response.status_code)
        self.assertEqual(
            {"ok": False, "error": "whatsapp_target_unresolvable"},
            response.json(),
        )

    @patch("core.views.compose.transport.send_reaction", new_callable=AsyncMock)
    def test_whatsapp_web_local_message_without_bridge_is_unresolvable(
        self, mocked_send_reaction
    ):
        person, _, message = self._build_message(
            service="whatsapp",
            identifier="12345@s.whatsapp.net",
            source_message_id="1771234567000",
        )
        message.source_service = "web"
        message.save(update_fields=["source_service"])

        response = self.client.post(
            reverse("compose_react"),
            {
                "service": "whatsapp",
                "identifier": "12345@s.whatsapp.net",
                "person": str(person.id),
                "message_id": str(message.id),
                "emoji": "😮",
            },
        )

        self.assertEqual(200, response.status_code)
        self.assertEqual(
            {"ok": False, "error": "whatsapp_target_unresolvable"},
            response.json(),
        )
        mocked_send_reaction.assert_not_awaited()

    def test_compose_page_hides_reaction_actions_for_signal_but_keeps_reaction_chips(
        self,
    ):
        person, _, message = self._build_message(
            service="signal",
            identifier="+15551230000",
            source_message_id="1771234567000",
        )
        message.receipt_payload = {
            "reactions": [
                {
                    "emoji": "👍",
                    "actor": "remote:signal",
                    "source_service": "signal",
                }
            ]
        }
        message.save(update_fields=["receipt_payload"])
        response = self.client.get(
            reverse("compose_page"),
            {
                "service": "signal",
                "identifier": "+15551230000",
                "person": str(person.id),
            },
        )
        self.assertEqual(200, response.status_code)
        content = response.content.decode("utf-8")
        self.assertNotIn("data-react-url=", content)
        self.assertNotIn("compose-reaction-actions", content)
        self.assertIn("compose-reaction-chip", content)
        self.assertIn("👍", content)

    def test_compose_page_hides_reaction_actions_for_unsupported_service(self):
        person, _, _ = self._build_message(
            service="xmpp",
            identifier="person@example.com",
            source_message_id="msg-1",
        )
        response = self.client.get(
            reverse("compose_page"),
            {
                "service": "xmpp",
                "identifier": "person@example.com",
                "person": str(person.id),
            },
        )
        self.assertEqual(200, response.status_code)
        content = response.content.decode("utf-8")
        self.assertNotIn("data-react-url=", content)
        self.assertNotIn(
            'class="compose-reaction-actions"',
            content,
        )
