import datetime

import pytest

from bs4 import BeautifulSoup

import dash_dangerously_set_inner_html
import dash_flow_example

import dash_html_components as html
import dash_core_components as dcc

from dash import Dash

from dash.dependencies import Input, Output
from dash.exceptions import PreventUpdate

from dash.testing.plugin import *
from . import BaseDashView


def test_inin004_wildcard_data_attributes(dash_duo):
    app = Dash()
    test_time = datetime.datetime(2012, 1, 10, 2, 3)
    test_date = datetime.date(test_time.year, test_time.month, test_time.day)
    attrs = {
        "id": "inner-element",
        "data-string": "multiple words",
        "data-number": 512,
        "data-none": None,
        "data-date": test_date,
        "aria-progress": 5,
    }
    app.layout = html.Div([html.Div(**attrs)], id="data-element")

    class DashView(BaseDashView):
        dash = app

    dash_duo.start_server(DashView)

    div = dash_duo.find_element("#data-element")

    # attribute order is ill-defined - BeautifulSoup will sort them
    actual = BeautifulSoup(div.get_attribute("innerHTML"), "lxml").decode()
    expected = BeautifulSoup(
        "<div "
        + " ".join('{}="{!s}"'.format(k, v) for k, v in attrs.items() if v is not None)
        + "></div>",
        "lxml",
    ).decode()

    assert actual == expected, "all attrs are included except None values"

    assert not dash_duo.get_logs()


def test_inin005_no_props_component(dash_duo):
    app = Dash()
    app.layout = html.Div(
        [
            dash_dangerously_set_inner_html.DangerouslySetInnerHTML(
                """
            <h1>No Props Component</h1>
        """
            )
        ]
    )

    class DashView(BaseDashView):
        dash_components = {dcc.__name__, html.__name__, dash_dangerously_set_inner_html.__name__}
        dash = app

    dash_duo.start_server(DashView)

    assert not dash_duo.get_logs()
    dash_duo.percy_snapshot(name="no-props-component")


def test_inin006_flow_component(dash_duo):
    app = Dash()

    app.layout = html.Div(
        [
            dash_flow_example.ExampleReactComponent(
                id="react", value="my-value", label="react component"
            ),
            dash_flow_example.ExampleFlowComponent(
                id="flow", value="my-value", label="flow component"
            ),
            html.Hr(),
            html.Div(id="output"),
        ]
    )

    @app.callback(
        Output("output", "children"), [Input("react", "value"), Input("flow", "value")]
    )
    def display_output(react_value, flow_value):
        return html.Div(
            [
                "You have entered {} and {}".format(react_value, flow_value),
                html.Hr(),
                html.Label("Flow Component Docstring"),
                html.Pre(dash_flow_example.ExampleFlowComponent.__doc__),
                html.Hr(),
                html.Label("React PropTypes Component Docstring"),
                html.Pre(dash_flow_example.ExampleReactComponent.__doc__),
                html.Div(id="waitfor"),
            ]
        )

    class DashView(BaseDashView):
        dash_components = {dcc.__name__, html.__name__, dash_flow_example.__name__}
        dash = app

    dash_duo.start_server(DashView)
    dash_duo.wait_for_element("#waitfor")
    dash_duo.percy_snapshot(name="flowtype")


def test_inin007_meta_tags(dash_duo):
    metas = [
        {"name": "description", "content": "my dash app"},
        {"name": "custom", "content": "customized"},
    ]

    app = Dash(meta_tags=metas)

    app.layout = html.Div(id="content")

    class DashView(BaseDashView):
        dash = app

    dash_duo.start_server(DashView)

    meta = dash_duo.find_elements("meta")

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

    for i in range(2, len(meta)):
        meta_tag = meta[i]
        meta_info = metas[i - 2]
        assert meta_tag.get_attribute("name") == meta_info["name"]
        assert meta_tag.get_attribute("content") == meta_info["content"]


def test_inin008_index_customization(dash_duo):
    app = Dash()

    # app.index_string = """<!DOCTYPE html>
    # <html>
    #     <head>
    #         {%metas%}
    #         <title>{%title%}</title>
    #         {%favicon%}
    #         {%css%}
    #     </head>
    #     <body>
    #         <div id="custom-header">My custom header</div>
    #         <div id="add"></div>
    #         {%app_entry%}
    #         <footer>
    #             {%config%}
    #             {%scripts%}
    #             {%renderer%}
    #         </footer>
    #         <div id="custom-footer">My custom footer</div>
    #         <script>
    #         // Test the formatting doesn"t mess up script tags.
    #         var elem = document.getElementById('add');
    #         if (!elem) {
    #             throw Error('could not find container to add');
    #         }
    #         elem.innerHTML = 'Got added';
    #         var config = {};
    #         fetch('/nonexist').then(r => r.json())
    #             .then(r => config = r).catch(err => ({config}));
    #         </script>
    #     </body>
    # </html>"""

    app.layout = html.Div("Dash app", id="app")

    class DashView(BaseDashView):
        template_name = 'dash_index_customization.html'
        dash = app

    dash_duo.start_server(DashView)

    assert dash_duo.find_element("#custom-header").text == "My custom header"
    assert dash_duo.find_element("#custom-footer").text == "My custom footer"
    assert dash_duo.wait_for_element("#add").text == "Got added"

    dash_duo.percy_snapshot("custom-index")


@pytest.mark.skip
def test_inin009_invalid_index_string(dash_duo):
    app = Dash()

    def will_raise():
        app.index_string = """<!DOCTYPE html>
        <html>
            <head>
                {%metas%}
                <title>{%title%}</title>
                {%favicon%}
                {%css%}
            </head>
            <body>
                <div id="custom-header">My custom header</div>
                <div id="add"></div>
                <footer>
                </footer>
            </body>
        </html>"""

    with pytest.raises(Exception) as err:
        will_raise()

    exc_msg = str(err.value)
    assert "{%app_entry%}" in exc_msg
    assert "{%config%}" in exc_msg
    assert "{%scripts%}" in exc_msg

    app.layout = html.Div("Hello World", id="a")

    dash_duo.start_server(app)
    assert dash_duo.find_element("#a").text == "Hello World"


def test_inin010_func_layout_accepted(dash_duo):
    app = Dash()

    def create_layout():
        return html.Div("Hello World", id="a")

    app.layout = create_layout

    class DashView(BaseDashView):
        dash = app

    dash_duo.start_server(DashView)

    assert dash_duo.find_element("#a").text == "Hello World"


def test_inin017_late_component_register(dash_duo):
    app = Dash()

    app.layout = html.Div(
        [html.Button("Click me to put a dcc ", id="btn-insert"), html.Div(id="output")]
    )

    @app.callback(Output("output", "children"), [Input("btn-insert", "n_clicks")])
    def update_output(value):
        if value is None:
            raise PreventUpdate

        return dcc.Input(id="inserted-input")

    class DashView(BaseDashView):
        dash = app

    dash_duo.start_server(DashView)

    btn = dash_duo.find_element("#btn-insert")
    btn.click()

    dash_duo.find_element("#inserted-input")


@pytest.mark.skip
def test_inin_024_port_env_success(dash_duo):
    app = Dash(__name__)
    app.layout = html.Div("hi", "out")
    dash_duo.start_server(app, port="12345")
    assert dash_duo.server_url == "http://localhost:12345"
    dash_duo.wait_for_text_to_equal("#out", "hi")


@pytest.mark.skip
def nested_app(server, path, text):
    app = Dash(__name__, server=server, url_base_pathname=path)
    app.layout = html.Div(id="out")

    @app.callback(Output("out", "children"), [Input("out", "n_clicks")])
    def out(n):
        return text

    return app


@pytest.mark.skip
def test_inin025_url_base_pathname(dash_br, dash_thread_server):
    server = flask.Flask(__name__)
    app = nested_app(server, "/app1/", "The first")
    nested_app(server, "/app2/", "The second")

    dash_thread_server(app)

    dash_br.server_url = "http://localhost:8050/app1/"
    dash_br.wait_for_text_to_equal("#out", "The first")

    dash_br.server_url = "http://localhost:8050/app2/"
    dash_br.wait_for_text_to_equal("#out", "The second")
