import pytest
import dash
from dash import Dash, Input, State, dcc, html, Output
from dash.dash import _ID_LOCATION
from dash.exceptions import NoLayoutException


def get_app(path1="/", path2="/layout2"):
    app = Dash(__name__, use_pages=True)

    # test for storing arbitrary keyword arguments: An `id` prop is defined for every page
    # test for defining multiple pages within a single file: layout is passed directly to `register_page`
    # in the following two modules:
    dash.register_page(
        "multi_layout1",
        layout=html.Div("text for multi_layout1", id="text_multi_layout1"),
        path=path1,
        title="Supplied Title",
        description="This is the supplied description",
        name="Supplied name",
        image="birds.jpeg",
        id="multi_layout1",
    )
    dash.register_page(
        "multi_layout2",
        layout=html.Div("text for multi_layout2", id="text_multi_layout2"),
        path=path2,
        id="multi_layout2",
    )

    app.layout = html.Div(
        [
            html.Div(
                [
                    html.Div(
                        dcc.Link(
                            f"{page['name']} - {page['path']}",
                            id=page["id"],
                            href=page["path"],
                        )
                    )
                    for page in dash.page_registry.values()
                ]
            ),
            dash.page_container,
            dcc.Location(id="url", refresh=True),
        ]
    )
    return app


def test_pala001_layout(dash_duo, clear_pages_state):
    app = get_app()
    dash_duo.start_server(app)

    # test layout and title for each page in `page_registry` with link navigation
    for page in dash.page_registry.values():
        dash_duo.find_element("#" + page["id"]).click()
        dash_duo.wait_for_text_to_equal("#text_" + page["id"], "text for " + page["id"])
        assert dash_duo.driver.title == page["title"], "check that page title updates"

    # test redirects
    dash_duo.wait_for_page(url=f"{dash_duo.server_url}/v2")
    dash_duo.wait_for_text_to_equal("#text_redirect", "text for redirect")
    dash_duo.wait_for_page(url=f"{dash_duo.server_url}/old-home-page")
    dash_duo.wait_for_text_to_equal("#text_redirect", "text for redirect")
    assert dash_duo.driver.current_url == f"{dash_duo.server_url}/redirect"

    # test redirect with button and user defined dcc.Location
    # note:  dcc.Location must be defined in app.py
    dash_duo.wait_for_page(url=f"{dash_duo.server_url}/page1")
    dash_duo.find_element("#btn1").click()
    dash_duo.wait_for_text_to_equal("#text_page2", "text for page2")

    # test query strings
    dash_duo.wait_for_page(url=f"{dash_duo.server_url}/query-string?velocity=10")
    assert (
        dash_duo.find_element("#velocity").get_attribute("value") == "10"
    ), "query string passed to layout"

    # test path variables
    dash_duo.wait_for_page(url=f"{dash_duo.server_url}/a/none/b/none")
    dash_duo.wait_for_text_to_equal("#path_vars", "variables from pathname:none none")

    dash_duo.wait_for_page(url=f"{dash_duo.server_url}/a/var1/b/var2")
    dash_duo.wait_for_text_to_equal("#path_vars", "variables from pathname:var1 var2")

    # test page not found
    dash_duo.wait_for_page(url=f"{dash_duo.server_url}/find_me")
    dash_duo.wait_for_text_to_equal("#text_not_found_404", "text for not_found_404")

    # test `validation_layout` exists when suppress_callback_exceptions=False`
    assert app.validation_layout is not None

    assert dash_duo.get_logs() == [], "browser console should contain no error"
    # dash_duo.percy_snapshot("pala001_layout")


def check_metas(dash_duo, metas):
    meta = dash_duo.find_elements("meta")

    # -3 for the meta charset and http-equiv and viewport.
    assert len(meta) == len(metas) + 3, "Should have  extra meta tags"

    assert meta[0].get_attribute("name") == metas[0]["name"]
    assert meta[0].get_attribute("content") == metas[0]["content"]
    for i in range(1, len(meta) - 3):
        assert meta[i].get_attribute("property") == metas[i]["property"]
        assert meta[i].get_attribute("content") == metas[i]["content"]


def test_pala002_meta_tags_default(dash_duo, clear_pages_state):
    dash_duo.start_server(get_app(path1="/layout1", path2="/"))
    # These are the inferred defaults if description, title, image are not supplied
    metas_layout2 = [
        {"name": "description", "content": ""},
        {"property": "twitter:card", "content": "summary_large_image"},
        {
            "property": "twitter:url",
            "content": f"{dash_duo.server_url}/",
        },
        {"property": "twitter:title", "content": "Multi layout2"},
        {"property": "twitter:description", "content": ""},
        {
            "property": "twitter:image",
            "content": f"{dash_duo.server_url}/assets/app.jpeg",
        },
        {"property": "og:title", "content": "Multi layout2"},
        {"property": "og:type", "content": "website"},
        {"property": "og:description", "content": ""},
        {
            "property": "og:image",
            "content": f"{dash_duo.server_url}/assets/app.jpeg",
        },
    ]

    check_metas(dash_duo, metas_layout2)


def test_pala003_meta_tags_custom(dash_duo, clear_pages_state):
    dash_duo.start_server(get_app())
    # In the "multi_layout1" module, the description, title, image are supplied
    metas_layout1 = [
        {"name": "description", "content": "This is the supplied description"},
        {"property": "twitter:card", "content": "summary_large_image"},
        {
            "property": "twitter:url",
            "content": f"{dash_duo.server_url}/",
        },
        {"property": "twitter:title", "content": "Supplied Title"},
        {
            "property": "twitter:description",
            "content": "This is the supplied description",
        },
        {
            "property": "twitter:image",
            "content": f"{dash_duo.server_url}/assets/birds.jpeg",
        },
        {"property": "og:title", "content": "Supplied Title"},
        {"property": "og:type", "content": "website"},
        {"property": "og:description", "content": "This is the supplied description"},
        {
            "property": "og:image",
            "content": f"{dash_duo.server_url}/assets/birds.jpeg",
        },
    ]

    check_metas(dash_duo, metas_layout1)


def test_pala004_no_layout_exception(clear_pages_state):
    error_msg = 'No layout found in module pages_error.no_layout_page\nA variable or a function named "layout" is required.'

    with pytest.raises(NoLayoutException) as err:
        Dash(__name__, use_pages=True, pages_folder="pages_error")

    assert error_msg in err.value.args[0]


def get_routing_inputs_app():
    app = Dash(
        __name__,
        use_pages=True,
        routing_callback_inputs={
            "hash": State(_ID_LOCATION, "hash"),
            "language": Input("language", "value"),
        },
    )
    # Page with layout from a variable: should render and not be impacted
    # by routing callback inputs
    dash.register_page(
        "home",
        layout=html.Div("Home", id="contents"),
        path="/",
    )

    # Page with a layout function, should see the routing callback inputs
    # as keyword arguments
    def layout1(hash: str = None, language: str = "en", **kwargs):
        translations = {
            "en": "Hash says: {}",
            "fr": "Le hash dit: {}",
        }
        return html.Div(translations[language].format(hash), id="contents")

    dash.register_page(
        "function_layout",
        path="/function-layout",
        layout=layout1,
    )
    app.layout = html.Div(
        [
            dcc.Dropdown(id="language", options=["en", "fr"], value="en"),
            dash.page_container,
        ]
    )
    return app


def test_pala005_routing_inputs(dash_duo, clear_pages_state):
    dash_duo.start_server(get_routing_inputs_app())
    dash_duo.wait_for_page(url=f"{dash_duo.server_url}#123")
    dash_duo.wait_for_text_to_equal("#contents", "Home")
    dash_duo.wait_for_page(url=f"{dash_duo.server_url}/")
    dash_duo.wait_for_text_to_equal("#contents", "Home")
    dash_duo.wait_for_page(url=f"{dash_duo.server_url}/function-layout")
    dash_duo.wait_for_text_to_equal("#contents", "Hash says:")
    # hash is a State therefore navigating to the same page with hash will not
    # re-render the layout function
    dash_duo.wait_for_page(url=f"{dash_duo.server_url}/function-layout#123")
    dash_duo.wait_for_text_to_equal("#contents", "Hash says:")
    # Refreshing the page re-runs the layout function
    dash_duo.driver.refresh()
    dash_duo.wait_for_text_to_equal("#contents", "Hash says: #123")
    # Changing the language Input re-runs the layout function
    dash_duo.select_dcc_dropdown("#language", "fr")
    dash_duo.wait_for_text_to_equal("#contents", "Le hash dit: #123")


def test_pala006_pages_external_library(dash_duo):
    import dash_test_components as dt

    app = Dash(use_pages=True, pages_folder="")

    @app.callback(
        Output("out", "children"),
        Input("button", "n_clicks"),
        prevent_initial_call=True,
    )
    def on_click(n_clicks):
        return f"Button has been clicked {n_clicks} times"

    dash.register_page(
        "page",
        path="/",
        layout=html.Div(
            [
                dt.DelayedEventComponent(id="button"),
                html.Div("The button has not been clicked yet", id="out"),
            ]
        ),
    )

    dash_duo.start_server(app)
    dash_duo.wait_for_element("#button").click()
    dash_duo.wait_for_text_to_equal("#out", "Button has been clicked 1 times")


def get_app_title_description():
    app = Dash(
        __name__, use_pages=True, title="App Title", description="App Description"
    )
    dash.register_page("home", layout=html.Div("Home"), path="/")
    dash.register_page(
        "page1",
        layout=html.Div("Page1"),
        title="Page 1 Title",
        description="Page 1 Description",
    )
    app.layout = html.Div(dash.page_container)
    return app


def test_pala007_app_title_discription(dash_duo, clear_pages_state):
    dash_duo.start_server(get_app_title_description())

    assert dash.page_registry["home"]["title"] == "App Title"
    assert dash.page_registry["page1"]["title"] == "Page 1 Title"
    assert dash.page_registry["home"]["description"] == "App Description"
    assert dash.page_registry["page1"]["description"] == "Page 1 Description"
