#
# Copyright (c) 2026, Daily
#
# SPDX-License-Identifier: BSD 2-Clause License
#

"""Smoke tests for the UI Agent Protocol wire format.

The module under test is data only (constants, payload models, and
envelope classes), so the goal is to pin the shapes: any accidental
rename or type change to a wire-format field would break compatibility
with existing client code, and we want a test that fails loudly.
"""

import unittest

from pipecat.processors.frameworks.rtvi.frames import (
    RTVIUICommandFrame,
    RTVIUIEventFrame,
)
from pipecat.processors.frameworks.rtvi.models import (
    A11yNode,
    A11ySelection,
    A11ySnapshot,
    Click,
    Focus,
    Highlight,
    Navigate,
    ScrollTo,
    SelectText,
    SetInputValue,
    Toast,
    UICancelTaskData,
    UICancelTaskMessage,
    UICommandData,
    UICommandMessage,
    UIEventData,
    UIEventMessage,
    UISnapshotData,
    UISnapshotMessage,
    UITaskCompletedData,
    UITaskGroupCompletedData,
    UITaskGroupStartedData,
    UITaskMessage,
    UITaskUpdateData,
)


class TestEnvelopeMessages(unittest.TestCase):
    """Pin the on-the-wire envelope shapes for each first-class UI message."""

    def test_ui_event_envelope(self):
        msg = UIEventMessage(id="m1", data=UIEventData(event="nav_click", payload={"view": "home"}))
        self.assertEqual(
            msg.model_dump(),
            {
                "label": "rtvi-ai",
                "type": "ui-event",
                "id": "m1",
                "data": {"event": "nav_click", "payload": {"view": "home"}},
            },
        )

    def test_ui_command_envelope_no_id(self):
        # Server-to-client push: no id field on the envelope (matches
        # ServerMessage / LLMFunctionCallMessage shape).
        msg = UICommandMessage(data=UICommandData(command="toast", payload={"title": "Saved"}))
        self.assertEqual(
            msg.model_dump(),
            {
                "label": "rtvi-ai",
                "type": "ui-command",
                "data": {"command": "toast", "payload": {"title": "Saved"}},
            },
        )

    def test_ui_snapshot_envelope(self):
        msg = UISnapshotMessage(
            id="m2",
            data=UISnapshotData(
                tree=A11ySnapshot(
                    root=A11yNode(
                        ref="e1",
                        role="main",
                        children=[A11yNode(ref="e2", role="button", name="Save")],
                    ),
                    captured_at=42,
                    selection=A11ySelection(ref="e2", text="Save", start_offset=0, end_offset=4),
                )
            ),
        )
        self.assertEqual(
            msg.model_dump(),
            {
                "label": "rtvi-ai",
                "type": "ui-snapshot",
                "id": "m2",
                "data": {
                    "tree": {
                        "root": {
                            "ref": "e1",
                            "role": "main",
                            "name": None,
                            "value": None,
                            "state": None,
                            "level": None,
                            "colcount": None,
                            "rowcount": None,
                            "children": [
                                {
                                    "ref": "e2",
                                    "role": "button",
                                    "name": "Save",
                                    "value": None,
                                    "state": None,
                                    "level": None,
                                    "colcount": None,
                                    "rowcount": None,
                                    "children": None,
                                }
                            ],
                        },
                        "captured_at": 42,
                        "selection": {
                            "ref": "e2",
                            "text": "Save",
                            "start_offset": 0,
                            "end_offset": 4,
                        },
                    }
                },
            },
        )

    def test_ui_snapshot_allows_future_client_fields(self):
        msg = UISnapshotMessage.model_validate(
            {
                "id": "m2",
                "data": {
                    "tree": {
                        "root": {
                            "ref": "e1",
                            "role": "main",
                            "bounds": {"x": 1, "y": 2},
                            "children": [
                                {
                                    "ref": "e2",
                                    "role": "button",
                                    "name": "Save",
                                    "platform_state": {"pressed": False},
                                }
                            ],
                        },
                        "captured_at": 42,
                        "selection": {
                            "ref": "e2",
                            "text": "Save",
                            "direction": "forward",
                        },
                        "viewport": {"width": 1024, "height": 768},
                    }
                },
            }
        )

        dumped = msg.model_dump()
        tree = dumped["data"]["tree"]
        self.assertEqual(tree["root"]["bounds"], {"x": 1, "y": 2})
        self.assertEqual(tree["root"]["children"][0]["platform_state"], {"pressed": False})
        self.assertEqual(tree["selection"]["direction"], "forward")
        self.assertEqual(tree["viewport"], {"width": 1024, "height": 768})

    def test_ui_cancel_task_envelope(self):
        msg = UICancelTaskMessage(id="m3", data=UICancelTaskData(task_id="t-99", reason="user"))
        self.assertEqual(
            msg.model_dump(),
            {
                "label": "rtvi-ai",
                "type": "ui-cancel-task",
                "id": "m3",
                "data": {"task_id": "t-99", "reason": "user"},
            },
        )

    def test_ui_task_group_started(self):
        msg = UITaskMessage(
            data=UITaskGroupStartedData(task_id="t-1", agents=["a", "b"], label="Search", at=42)
        )
        self.assertEqual(msg.type, "ui-task")
        self.assertEqual(msg.data.kind, "group_started")
        self.assertEqual(msg.data.task_id, "t-1")

    def test_ui_task_update(self):
        msg = UITaskMessage(
            data=UITaskUpdateData(task_id="t-1", agent_name="a", data={"progress": 0.5}, at=43)
        )
        self.assertEqual(msg.data.kind, "task_update")
        self.assertEqual(msg.data.agent_name, "a")

    def test_ui_task_completed(self):
        msg = UITaskMessage(
            data=UITaskCompletedData(
                task_id="t-1", agent_name="a", status="completed", response={"ok": True}, at=44
            )
        )
        self.assertEqual(msg.data.kind, "task_completed")
        self.assertEqual(msg.data.status, "completed")

    def test_ui_task_group_completed(self):
        msg = UITaskMessage(data=UITaskGroupCompletedData(task_id="t-1", at=45))
        self.assertEqual(msg.data.kind, "group_completed")


class TestPayloadShapes(unittest.TestCase):
    """Pin the on-the-wire dict shape of each command payload."""

    def test_toast_required_only(self):
        self.assertEqual(
            dict(Toast(title="Saved")),
            {
                "title": "Saved",
                "subtitle": None,
                "description": None,
                "image_url": None,
                "duration_ms": None,
            },
        )

    def test_navigate(self):
        self.assertEqual(
            dict(Navigate(view="home", params={"id": "42"})),
            {"view": "home", "params": {"id": "42"}},
        )

    def test_scroll_to_with_ref(self):
        self.assertEqual(
            dict(ScrollTo(ref="e42", behavior="smooth")),
            {"ref": "e42", "target_id": None, "behavior": "smooth"},
        )

    def test_highlight_with_target_id(self):
        self.assertEqual(
            dict(Highlight(target_id="cta", duration_ms=2000)),
            {"ref": None, "target_id": "cta", "duration_ms": 2000},
        )

    def test_focus(self):
        self.assertEqual(
            dict(Focus(ref="e7")),
            {"ref": "e7", "target_id": None},
        )

    def test_click(self):
        self.assertEqual(
            dict(Click(ref="e9")),
            {"ref": "e9", "target_id": None},
        )

    def test_set_input_value_default_replace_true(self):
        self.assertEqual(
            dict(SetInputValue(ref="e3", value="Marie Curie")),
            {"value": "Marie Curie", "ref": "e3", "target_id": None, "replace": True},
        )

    def test_set_input_value_append(self):
        payload = dict(SetInputValue(ref="e3", value="more", replace=False))
        self.assertFalse(payload["replace"])

    def test_select_text_full(self):
        self.assertEqual(
            dict(SelectText(ref="e15", start_offset=4, end_offset=12)),
            {"ref": "e15", "target_id": None, "start_offset": 4, "end_offset": 12},
        )

    def test_select_text_whole_target_when_offsets_omitted(self):
        payload = dict(SelectText(ref="e15"))
        self.assertIsNone(payload["start_offset"])
        self.assertIsNone(payload["end_offset"])


class TestFrames(unittest.TestCase):
    """Pin the frame API for named UI messages."""

    def test_ui_command_frame_names_command_and_payload(self):
        frame = RTVIUICommandFrame(command="toast", payload={"title": "Saved"})
        self.assertEqual(frame.command, "toast")
        self.assertEqual(frame.payload, {"title": "Saved"})

    def test_ui_event_frame_names_event_and_payload(self):
        frame = RTVIUIEventFrame(msg_id="m1", event="nav_click", payload={"view": "home"})
        self.assertEqual(frame.msg_id, "m1")
        self.assertEqual(frame.event, "nav_click")
        self.assertEqual(frame.payload, {"view": "home"})


if __name__ == "__main__":
    unittest.main()
