from __future__ import annotations

import asyncio

from asgiref.sync import async_to_sync
from unittest.mock import patch

from django.core.management import call_command
from django.test import TestCase

from core.clients.whatsapp import WhatsAppClient
from core.messaging import history, media_bridge
from core.models import (
    ChatSession,
    Message,
    Person,
    PersonIdentifier,
    User,
)
from core.presence.inference import now_ms


class _DummyXMPPClient:
    async def apply_external_reaction(self, *args, **kwargs):
        return None


class _DummyDownloadClient:
    def __init__(self, payload: bytes):
        self.payload = payload
        self.calls = []

    async def download_any(self, message):
        self.calls.append(message)
        return self.payload


class _DummyUR:
    def __init__(self, loop):
        self.loop = loop
        self.xmpp = type("X", (), {"client": _DummyXMPPClient()})()
        self.presence_calls = []
        self.stopped_typing_calls = []

    async def presence_changed(self, protocol, *args, **kwargs):
        self.presence_calls.append((protocol, kwargs))

    async def stopped_typing(self, protocol, *args, **kwargs):
        self.stopped_typing_calls.append((protocol, kwargs))


class WhatsAppReactionHandlingTests(TestCase):
    def setUp(self):
        self.user = User.objects.create_user("wa-rx-user", "wa-rx@example.com", "x")
        self.person = Person.objects.create(user=self.user, name="WA Person")
        self.identifier = PersonIdentifier.objects.create(
            user=self.user,
            person=self.person,
            service="whatsapp",
            identifier="15551234567@s.whatsapp.net",
        )
        self.session = ChatSession.objects.create(
            user=self.user, identifier=self.identifier
        )
        self.base_ts = now_ms()
        self.target = Message.objects.create(
            user=self.user,
            session=self.session,
            ts=self.base_ts,
            text="hello",
            source_service="whatsapp",
            source_message_id="wa-target-1",
            source_chat_id="120363402761690215@g.us",
            sender_uuid="15551234567@s.whatsapp.net",
        )
        self.loop = asyncio.new_event_loop()
        self.ur = _DummyUR(self.loop)
        self.client = WhatsAppClient(self.ur, self.loop, "whatsapp")

    def tearDown(self):
        try:
            self.loop.close()
        except Exception:
            pass

    def test_reaction_event_extract_and_apply_by_source_message_id(self):
        message_obj = {
            "reactionMessage": {
                "text": "👍",
                "targetMessageKey": {
                    "id": "wa-target-1",
                    "messageTimestamp": int(self.base_ts / 1000),
                },
            }
        }
        parsed = self.client._extract_reaction_event(message_obj)
        self.assertIsNotNone(parsed)
        self.assertEqual("wa-target-1", str(parsed.get("target_message_id") or ""))
        before_count = Message.objects.filter(
            user=self.user, session=self.session
        ).count()
        async_to_sync(history.apply_reaction)(
            self.user,
            self.identifier,
            target_message_id="wa-target-1",
            target_ts=int(parsed.get("target_ts") or 0),
            emoji="👍",
            source_service="whatsapp",
            actor="15551234567@s.whatsapp.net",
            remove=False,
            payload={"event": "reaction"},
        )
        after_count = Message.objects.filter(
            user=self.user, session=self.session
        ).count()
        self.assertEqual(before_count, after_count)

        self.target.refresh_from_db()
        reactions = list((self.target.receipt_payload or {}).get("reactions") or [])
        self.assertEqual(1, len(reactions))
        self.assertEqual("👍", str(reactions[0].get("emoji") or ""))

    def test_presence_event_emits_native_presence_with_last_seen(self):
        event = {
            "From": {"User": "15551234567@s.whatsapp.net"},
            "Unavailable": True,
            "LastSeen": int(self.base_ts / 1000),
        }
        async_to_sync(self.client._handle_presence_event)(event)
        self.assertTrue(self.ur.presence_calls)
        payload = self.ur.presence_calls[-1][1].get("payload") or {}
        self.assertEqual("offline", payload.get("presence"))
        self.assertTrue(int(payload.get("last_seen_ts") or 0) > 0)

    def test_download_event_media_unwraps_device_sent_image(self):
        downloader = _DummyDownloadClient(b"png-bytes")
        self.client._client = downloader
        event = {
            "message": {
                "deviceSentMessage": {
                    "message": {
                        "imageMessage": {
                            "caption": "wrapped image",
                            "mimetype": "image/png",
                        }
                    }
                }
            }
        }

        attachments = async_to_sync(self.client._download_event_media)(event)

        self.assertEqual(1, len(attachments))
        self.assertEqual(1, len(downloader.calls))
        self.assertEqual(
            {"imageMessage": {"caption": "wrapped image", "mimetype": "image/png"}},
            downloader.calls[0],
        )
        blob = media_bridge.get_blob(attachments[0]["blob_key"])
        self.assertIsNotNone(blob)
        self.assertEqual(b"png-bytes", blob["content"])
        self.assertEqual("image/png", blob["content_type"])

    def test_message_text_unwraps_device_sent_caption(self):
        text = self.client._message_text(
            {
                "deviceSentMessage": {
                    "message": {
                        "imageMessage": {
                            "caption": "caption from wrapper",
                        }
                    }
                }
            }
        )
        self.assertEqual("caption from wrapper", text)

    def test_message_identifier_candidates_use_chat_for_direct_outbound(self):
        values = self.client._message_identifier_candidates(
            sender="441234567890@s.whatsapp.net",
            chat="447356114729@s.whatsapp.net",
            is_from_me=True,
        )
        self.assertIn("447356114729@s.whatsapp.net", values)
        self.assertIn("447356114729", values)
        self.assertNotIn("441234567890@s.whatsapp.net", values)

    def test_message_identifier_candidates_use_sender_for_direct_inbound(self):
        values = self.client._message_identifier_candidates(
            sender="447356114729@s.whatsapp.net",
            chat="441234567890@s.whatsapp.net",
            is_from_me=False,
        )
        self.assertIn("447356114729@s.whatsapp.net", values)
        self.assertIn("447356114729", values)
        self.assertNotIn("441234567890@s.whatsapp.net", values)

    def test_message_identifier_candidates_use_group_chat_for_group_events(self):
        values = self.client._message_identifier_candidates(
            sender="447356114729@s.whatsapp.net",
            chat="120363402761690215@g.us",
            is_from_me=True,
        )
        self.assertIn("120363402761690215@g.us", values)
        self.assertIn("120363402761690215", values)
        self.assertNotIn("447356114729@s.whatsapp.net", values)


class RecalculateContactAvailabilityTests(TestCase):
    def setUp(self):
        self.user = User.objects.create_user("recalc-user", "recalc@example.com", "x")
        self.person = Person.objects.create(user=self.user, name="Recalc Person")
        self.identifier = PersonIdentifier.objects.create(
            user=self.user,
            person=self.person,
            service="whatsapp",
            identifier="15557654321@s.whatsapp.net",
        )
        self.session = ChatSession.objects.create(
            user=self.user, identifier=self.identifier
        )
        self.base_ts = now_ms()

        Message.objects.create(
            user=self.user,
            session=self.session,
            ts=self.base_ts,
            text="task",
            source_service="whatsapp",
            source_message_id="m-1",
            source_chat_id="120363402761690215@g.us",
            sender_uuid="15557654321@s.whatsapp.net",
            custom_author="OTHER",
            read_ts=self.base_ts + 20_000,
            receipt_payload={
                "reactions": [
                    {
                        "emoji": "🔥",
                        "actor": "15557654321@s.whatsapp.net",
                        "source_service": "whatsapp",
                        "removed": False,
                        "updated_at": self.base_ts + 10_000,
                    }
                ]
            },
        )

    @patch("core.management.commands.recalculate_contact_availability.append_event_sync")
    def test_recalculate_replays_message_read_and_reaction_events(self, mocked_append):
        call_command(
            "recalculate_contact_availability", "--days", "36500", "--limit", "500"
        )
        event_types = [call.kwargs["event_type"] for call in mocked_append.call_args_list]
        self.assertEqual(
            ["message_created", "read_receipt", "presence_available"],
            event_types,
        )

    @patch("core.management.commands.recalculate_contact_availability.append_event_sync")
    def test_recalculate_no_reset_remains_idempotent_at_command_interface(
        self, mocked_append
    ):
        call_command(
            "recalculate_contact_availability",
            "--days",
            "36500",
            "--limit",
            "500",
            "--no-reset",
        )
        self.assertEqual(3, mocked_append.call_count)
