import pytest
from tests.support.sync import AsyncPoll
from webdriver.bidi.modules.script import ContextTarget

from ... import any_string, recursive_compare


pytestmark = pytest.mark.asyncio


@pytest.mark.parametrize(
    "channel, expected_data",
    [
        (
            {"type": "channel", "value": {"channel": "channel_name"}},
            {
                "type": "object",
                "value": [
                    ["foo", {"type": "string", "value": "bar"}],
                    [
                        "baz",
                        {
                            "type": "object",
                            "value": [["1", {"type": "number", "value": 2}]],
                        },
                    ],
                ],
            },
        ),
        (
            {
                "type": "channel",
                "value": {
                    "channel": "channel_name",
                    "serializationOptions": {"maxObjectDepth": 0},
                },
            },
            {"type": "object"},
        ),
        (
            {
                "type": "channel",
                "value": {"channel": "channel_name", "ownership": "root"},
            },
            {
                "handle": any_string,
                "type": "object",
                "value": [
                    ["foo", {"type": "string", "value": "bar"}],
                    [
                        "baz",
                        {
                            "type": "object",
                            "value": [["1", {"type": "number", "value": 2}]],
                        },
                    ],
                ],
            },
        ),
    ],
    ids=["default", "with serializationOptions", "with ownership"],
)
async def test_channel(
    bidi_session,
    subscribe_events,
    wait_for_event,
    add_preload_script,
    channel,
    expected_data,
):
    await subscribe_events(["script.message"])

    on_script_message = wait_for_event("script.message")
    await add_preload_script(
        function_declaration="""(channel) => channel({'foo': 'bar', 'baz': {'1': 2}})""",
        arguments=[channel],
    )

    new_tab = await bidi_session.browsing_context.create(type_hint="tab")
    event_data = await on_script_message

    recursive_compare(
        {
            "channel": "channel_name",
            "data": expected_data,
            "source": {
                "realm": any_string,
                "context": new_tab["context"],
            },
        },
        event_data,
    )


async def test_channel_with_multiple_arguments(
    bidi_session, subscribe_events, wait_for_event, add_preload_script
):
    await subscribe_events(["script.message"])

    on_script_message = wait_for_event("script.message")
    await add_preload_script(
        function_declaration="""(channel) => channel('will_be_send', 'will_be_ignored')""",
        arguments=[{"type": "channel", "value": {"channel": "channel_name"}}],
    )

    new_tab = await bidi_session.browsing_context.create(type_hint="tab")
    event_data = await on_script_message

    recursive_compare(
        {
            "channel": "channel_name",
            "data": {"type": "string", "value": "will_be_send"},
            "source": {
                "realm": any_string,
                "context": new_tab["context"],
            },
        },
        event_data,
    )


async def test_mutation_observer(
    bidi_session,
    subscribe_events,
    wait_for_event,
    new_tab,
    inline,
    add_preload_script,
):
    await subscribe_events(["script.message"])

    on_script_message = wait_for_event("script.message")
    await add_preload_script(
        function_declaration="""(channel) => {
            const onMutation = (mutationList) => mutationList.forEach(mutation => {
                const attributeName = mutation.attributeName;
                const newValue = mutation.target.getAttribute(mutation.attributeName);
                channel({ attributeName, newValue });
            });
            const observer = new MutationObserver(onMutation);
            observer.observe(document, { attributes: true, subtree: true });
        }""",
        arguments=[{"type": "channel", "value": {"channel": "channel_name"}}],
    )

    url = inline("<div class='old class name'>foo</div>")
    await bidi_session.browsing_context.navigate(
        context=new_tab["context"],
        url=url,
        wait="complete",
    )

    restult = await bidi_session.script.evaluate(
        raw_result=True,
        expression="document.querySelector('div').setAttribute('class', 'mutated')",
        await_promise=True,
        target=ContextTarget(new_tab["context"]),
    )

    event_data = await on_script_message

    recursive_compare(
        {
            "channel": "channel_name",
            "data": {
                "type": "object",
                "value": [
                    ["attributeName", {"type": "string", "value": "class"}],
                    ["newValue", {"type": "string", "value": "mutated"}],
                ],
            },
            "source": {
                "realm": restult["realm"],
                "context": new_tab["context"],
            },
        },
        event_data,
    )


async def test_two_channels(
    bidi_session,
    subscribe_events,
    add_preload_script,
):
    await subscribe_events(["script.message"])

    # Track all received script.message events in the events array
    events = []

    async def on_event(method, data):
        events.append(data)

    remove_listener = bidi_session.add_event_listener("script.message", on_event)

    await add_preload_script(
        function_declaration="""(channel_1, channel_2) => {
            channel_1('message_from_channel_1');
            channel_2('message_from_channel_2')
        }""",
        arguments=[
            {"type": "channel", "value": {"channel": "channel_name_1"}},
            {"type": "channel", "value": {"channel": "channel_name_2"}},
        ],
    )

    new_tab = await bidi_session.browsing_context.create(type_hint="tab")
    # Wait for both events
    wait = AsyncPoll(bidi_session, timeout=0.5)
    await wait.until(lambda _: len(events) == 2)

    recursive_compare(
        {
            "channel": "channel_name_1",
            "data": {"type": "string", "value": "message_from_channel_1"},
            "source": {
                "realm": any_string,
                "context": new_tab["context"],
            },
        },
        events[0],
    )

    recursive_compare(
        {
            "channel": "channel_name_2",
            "data": {"type": "string", "value": "message_from_channel_2"},
            "source": {
                "realm": any_string,
                "context": new_tab["context"],
            },
        },
        events[1],
    )

    remove_listener()
