import pytest

from ... import any_string, recursive_compare

pytestmark = pytest.mark.asyncio


@pytest.mark.parametrize(
    "type,value",
    [
        ("css", "div"),
        ("xpath", "//div"),
        ("innerText", "foobarBARbaz"),
        ("accessibility", {"role": "banner"}),
        ("accessibility", {"name": "foo"}),
        ("accessibility", {"role": "banner", "name": "foo"}),
    ],
    ids=["css", "xpath", "innerText", "a11y-role", "a11y-name", "a11y-both"],
)
async def test_find_by_locator(bidi_session, inline, top_context, type, value):
    url = inline("""
        <div data-class="one" role="banner" aria-label="foo">foobarBARbaz</div>
        <div data-class="two" role="banner" aria-label="foo">foobarBARbaz</div>
    """)
    await bidi_session.browsing_context.navigate(
        context=top_context["context"], url=url, wait="complete"
    )

    result = await bidi_session.browsing_context.locate_nodes(
        context=top_context["context"], locator={"type": type, "value": value}
    )

    expected = [
        {
            "type": "node",
            "sharedId": any_string,
            "value": {
                "attributes": {"data-class": "one"},
                "childNodeCount": 1,
                "localName": "div",
                "namespaceURI": "http://www.w3.org/1999/xhtml",
                "nodeType": 1,
            },
        },
        {
            "type": "node",
            "sharedId": any_string,
            "value": {
                "attributes": {"data-class": "two"},
                "childNodeCount": 1,
                "localName": "div",
                "namespaceURI": "http://www.w3.org/1999/xhtml",
                "nodeType": 1,
            },
        },
    ]

    recursive_compare(expected, result["nodes"])


@pytest.mark.parametrize("value", [":root", "html"])
async def test_find_root_element_by_css_locator(
    bidi_session, inline, top_context, value
):
    await bidi_session.browsing_context.navigate(
        context=top_context["context"], url=inline("<div>"), wait="complete"
    )

    result = await bidi_session.browsing_context.locate_nodes(
        context=top_context["context"], locator={"type": "css", "value": value}
    )

    expected = [
        {
            "type": "node",
            "sharedId": any_string,
            "value": {
                "attributes": {},
                "childNodeCount": 2,
                "localName": "html",
                "namespaceURI": "http://www.w3.org/1999/xhtml",
                "nodeType": 1,
            },
        },
    ]

    recursive_compare(expected, result["nodes"])


@pytest.mark.parametrize(
    "html, selector",
    [
        ("<div></div>", "div"),
        ("<select></select>", "select"),
        ("<video></video>", "video"),
    ],
    ids=["div", "select", "video"],
)
async def test_no_user_agent_shadow_root(
    bidi_session, inline, top_context, html, selector
):
    url = inline(html)
    await bidi_session.browsing_context.navigate(
        context=top_context["context"], url=url, wait="complete"
    )

    result = await bidi_session.browsing_context.locate_nodes(
        context=top_context["context"], locator={"type": "css", "value": selector}
    )

    node_result = result["nodes"][0]
    expected = {
        "type": "node",
        "sharedId": any_string,
        "value": {
            "attributes": {},
            "childNodeCount": 0,
            "localName": selector,
            "namespaceURI": "http://www.w3.org/1999/xhtml",
            "nodeType": 1,
            # Make sure user-agent shadow roots are not leaked by locateNodes
            # (eg Firefox uses shadow dom to implement the select widget).
            "shadowRoot": None,
        },
    }
    recursive_compare(expected, node_result)


@pytest.mark.parametrize("locator,expected_nodes_values", [
    ({
         "type": "innerText",
         "ignoreCase": True,
         "matchType": "full",
         "value": "bar"
     }, ["strong", "span"]),
    ({
         "type": "innerText",
         "ignoreCase": False,
         "matchType": "full",
         "value": "BAR"
     }, ["span"]),
    ({
         "type": "innerText",
         "ignoreCase": True,
         "matchType": "partial",
         "value": "ba"
     }, ["strong", "span"]),
    ({
         "type": "innerText",
         "ignoreCase": False,
         "matchType": "partial",
         "value": "ba"
     }, ["strong"]),
    ({
         "type": "innerText",
         "ignoreCase": True,
         "matchType": "full",
         "maxDepth": 0,
         "value": "foobarbarbaz"
     }, ["html"]),
    ({
         "type": "innerText",
         "ignoreCase": False,
         "matchType": "full",
         "maxDepth": 0,
         "value": "foobarBARbaz"
     }, ["html"]),
    ({
         "type": "innerText",
         "ignoreCase": True,
         "matchType": "partial",
         "maxDepth": 0,
         "value": "bar"
     }, ["html"]),
    ({
         "type": "innerText",
         "ignoreCase": False,
         "matchType": "partial",
         "maxDepth": 0,
         "value": "BAR"
     }, ["html"]),
    ({
         "type": "innerText",
         "ignoreCase": True,
         "matchType": "full",
         "maxDepth": 2,
         "value": "foobarbarbaz"
     }, ["div"]),
    ({
         "type": "innerText",
         "ignoreCase": False,
         "matchType": "full",
         "maxDepth": 2,
         "value": "foobarBARbaz"
     }, ["div"]),
    ({
         "type": "innerText",
         "ignoreCase": True,
         "matchType": "partial",
         "maxDepth": 2,
         "value": "bar"
     }, ["div"]),
    ({
         "type": "innerText",
         "ignoreCase": False,
         "matchType": "partial",
         "maxDepth": 2,
         "value": "BAR"
     }, ["div"]),
], ids=[
    "ignore_case_true_full_match_no_max_depth",
    "ignore_case_false_full_match_no_max_depth",
    "ignore_case_true_partial_match_no_max_depth",
    "ignore_case_false_partial_match_no_max_depth",
    "ignore_case_true_full_match_max_depth_zero",
    "ignore_case_false_full_match_max_depth_zero",
    "ignore_case_true_partial_match_max_depth_zero",
    "ignore_case_false_partial_match_max_depth_zero",
    "ignore_case_true_full_match_max_depth_two",
    "ignore_case_false_full_match_max_depth_two",
    "ignore_case_true_partial_match_max_depth_two",
    "ignore_case_false_partial_match_max_depth_two",
])
async def test_find_by_inner_text(
    bidi_session, inline, top_context, locator, expected_nodes_values
):
    url = inline("""<div>foo<span><strong>bar</strong></span><span>BAR</span>baz</div>""")
    await bidi_session.browsing_context.navigate(
        context=top_context["context"], url=url, wait="complete"
    )

    # Construct expected nodes list with the expected nodes values emitting other fields.
    expected = [{
        "type": "node",
        "value": {
            "localName": node_value,
        }
    } for node_value in expected_nodes_values]

    result = await bidi_session.browsing_context.locate_nodes(
        context=top_context["context"],
        locator=locator
    )

    recursive_compare(expected, result["nodes"])


@pytest.mark.parametrize(
    "html,locator_value,expected_node_local_name",
    [
        (
            "<article data-class='one'>foo</article><div data-class='two'>bar</div>",
            {"role": "article"},
            "article",
        ),
        (
            "<input role='searchbox' data-class='one' /><input data-class='two' type='text'/>",
            {"role": "searchbox"},
            "input",
        ),
        (
            "<button data-class='one'>Ok</button><button data-class='two'>Cancel</button>",
            {"name": "Ok"},
            "button",
        ),
        (
            "<button data-class='one' aria-labelledby='one two'></button><div id='one'>ok</div><div id='two'>go</div><button data-class='two'>Cancel</button>",
            {"name": "ok go"},
            "button",
        ),
        (
            "<button data-class='one' aria-label='foo'>bar</button><button data-class='two' aria-label='bar'>foo</button>",
            {"name": "foo"},
            "button",
        ),
        (
            "<div role='banner' aria-label='foo' data-class='one'></div><div role='banner'  data-class='two'></div><div aria-label='foo' data-class='three'></div>",
            {"role": "banner", "name": "foo"},
            "div",
        ),
    ],
)
async def test_locate_by_accessibility_attributes(
    bidi_session,
    inline,
    top_context,
    html,
    locator_value,
    expected_node_local_name,
):
    await bidi_session.browsing_context.navigate(
        context=top_context["context"], url=inline(html), wait="complete"
    )

    expected = [
        {
            "type": "node",
            "value": {
                "attributes": {"data-class": "one"},
                "localName": expected_node_local_name,
            },
        }
    ]

    result = await bidi_session.browsing_context.locate_nodes(
        context=top_context["context"],
        locator={"type": "accessibility", "value": locator_value},
    )

    recursive_compare(expected, result["nodes"])


@pytest.mark.parametrize("domain", ["", "alt"], ids=["same_origin", "cross_origin"])
async def test_locate_by_context(bidi_session, inline, top_context, domain):
    iframe_url_1 = inline("<div>foo</div>", domain=domain)
    page_url = inline(f"<iframe id='target' src='{iframe_url_1}'></iframe>")

    await bidi_session.browsing_context.navigate(
        context=top_context["context"], url=page_url, wait="complete"
    )

    contexts = await bidi_session.browsing_context.get_tree(root=top_context["context"])
    iframe_context = contexts[0]["children"][0]

    result = await bidi_session.browsing_context.locate_nodes(
        context=top_context["context"],
        locator={"type": "context", "value": { "context": iframe_context["context"] }}
    )

    expected = [
        {
            "type": "node",
            "sharedId": any_string,
            "value": {
                "attributes": {"src": any_string, "id": "target"},
                "childNodeCount": 0,
                "localName": "iframe",
                "namespaceURI": "http://www.w3.org/1999/xhtml",
                "nodeType": 1,
            }
        }
    ]

    recursive_compare(expected, result["nodes"])


@pytest.mark.parametrize("domain", ["", "alt"], ids=["same_origin", "cross_origin"])
async def test_locate_by_context_in_iframe(bidi_session, inline, top_context, domain):
    iframe_url_2 = inline("<div>foo</div>", domain=domain)
    iframe_url_1 = inline(f"<div><iframe id='target' src='{iframe_url_2}'></iframe></div>")
    page_url = inline(f"<iframe src='{iframe_url_1}'></iframe>")

    await bidi_session.browsing_context.navigate(
        context=top_context["context"], url=page_url, wait="complete"
    )

    contexts = await bidi_session.browsing_context.get_tree(root=top_context["context"])
    iframe1_context = contexts[0]["children"][0]
    iframe2_context = contexts[0]["children"][0]["children"][0]

    result = await bidi_session.browsing_context.locate_nodes(
        context=iframe1_context["context"],
        locator={"type": "context", "value": { "context": iframe2_context["context"] }}
    )

    expected = [
        {
            "type": "node",
            "sharedId": any_string,
            "value": {
                "attributes": {"src": any_string, "id": "target"},
                "childNodeCount": 0,
                "localName": "iframe",
                "namespaceURI": "http://www.w3.org/1999/xhtml",
                "nodeType": 1,
            }
        }
    ]

    recursive_compare(expected, result["nodes"])


@pytest.mark.parametrize("domain", ["", "alt"], ids=["same_origin", "cross_origin"])
@pytest.mark.parametrize("mode", ["open", "closed"])
async def test_locate_by_context_in_shadow_dom(
    bidi_session, inline, top_context, domain, mode
):
    iframe_url_1 = inline(f"<div>foo</div>", domain=domain)
    page_url = inline(f"""
      <div id="host"></div>
      <script>
        const host = document.querySelector('#host');
        const shadow = host.attachShadow({{ mode: '{mode}' }});
        const div = document.createElement('div');
        div.innerHTML = "<iframe id='target' src='{iframe_url_1}'></iframe>";
        shadow.appendChild(div);
      </script>
    """)

    await bidi_session.browsing_context.navigate(
        context=top_context["context"], url=page_url, wait="complete"
    )

    contexts = await bidi_session.browsing_context.get_tree(root=top_context["context"])
    iframe1_context = contexts[0]["children"][0]

    result = await bidi_session.browsing_context.locate_nodes(
        context=top_context["context"],
        locator={"type": "context", "value": { "context": iframe1_context["context"] }}
    )

    expected = [
        {
            "type": "node",
            "sharedId": any_string,
            "value": {
                "attributes": {"src": any_string, "id": "target"},
                "childNodeCount": 0,
                "localName": "iframe",
                "namespaceURI": "http://www.w3.org/1999/xhtml",
                "nodeType": 1,
            }
        }
    ]

    recursive_compare(expected, result["nodes"])
