from typing import Any, Dict, List

import pytest

from reflex.components.tags import CondTag, Tag, tagless
from reflex.event import EVENT_ARG, EventChain, EventHandler, EventSpec
from reflex.style import Style
from reflex.vars import BaseVar, Var


def mock_event(arg):
    pass


@pytest.mark.parametrize(
    "prop,formatted",
    [
        ("string", '"string"'),
        ("{wrapped_string}", "{wrapped_string}"),
        (True, "{true}"),
        (False, "{false}"),
        (123, "{123}"),
        (3.14, "{3.14}"),
        ([1, 2, 3], "{[1, 2, 3]}"),
        (["a", "b", "c"], '{["a", "b", "c"]}'),
        ({"a": 1, "b": 2, "c": 3}, '{{"a": 1, "b": 2, "c": 3}}'),
        ({"a": 'foo "bar" baz'}, r'{{"a": "foo \"bar\" baz"}}'),
        (
            {
                "a": 'foo "{ "bar" }" baz',
                "b": BaseVar(name="val", type_="str"),
            },
            r'{{"a": "foo \"{ \"bar\" }\" baz", "b": val}}',
        ),
        (
            EventChain(events=[EventSpec(handler=EventHandler(fn=mock_event))]),
            '{_e => Event([E("mock_event", {})], _e)}',
        ),
        (
            EventChain(
                events=[
                    EventSpec(
                        handler=EventHandler(fn=mock_event),
                        args=((Var.create_safe("arg"), EVENT_ARG.target.value),),
                    )
                ]
            ),
            '{_e => Event([E("mock_event", {arg:_e.target.value})], _e)}',
        ),
        ({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
        (BaseVar(name="var", type_="int"), "{var}"),
        (
            BaseVar(
                name="_",
                type_=Any,
                state="",
                is_local=True,
                is_string=False,
            ),
            "{_}",
        ),
        (BaseVar(name='state.colors["a"]', type_="str"), '{state.colors["a"]}'),
        ({"a": BaseVar(name="val", type_="str")}, '{{"a": val}}'),
        ({"a": BaseVar(name='"val"', type_="str")}, '{{"a": "val"}}'),
        (
            {"a": BaseVar(name='state.colors["val"]', type_="str")},
            '{{"a": state.colors["val"]}}',
        ),
        # tricky real-world case from markdown component
        (
            {
                "h1": f"{{({{node, ...props}}) => <Heading {{...props}} {''.join(Tag(name='', props=Style({'as_': 'h1'})).format_props())} />}}"
            },
            '{{"h1": ({node, ...props}) => <Heading {...props} as={`h1`} />}}',
        ),
    ],
)
def test_format_prop(prop: Var, formatted: str):
    """Test that the formatted value of an prop is correct.

    Args:
        prop: The prop to test.
        formatted: The expected formatted value.
    """
    assert Tag.format_prop(prop) == formatted


@pytest.mark.parametrize(
    "props,test_props",
    [
        ({}, []),
        ({"key": 1}, ["key={1}"]),
        ({"key": "value"}, ["key={`value`}"]),
        ({"key": True, "key2": "value2"}, ["key={true}", "key2={`value2`}"]),
    ],
)
def test_format_props(props: Dict[str, Var], test_props: List):
    """Test that the formatted props are correct.

    Args:
        props: The props to test.
        test_props: The expected props.
    """
    tag_props = Tag(props=props).format_props()
    for i, tag_prop in enumerate(tag_props):
        assert tag_prop == test_props[i]


@pytest.mark.parametrize(
    "prop,valid",
    [
        (1, True),
        (3.14, True),
        ("string", True),
        (False, True),
        ([], True),
        ({}, False),
        (None, False),
    ],
)
def test_is_valid_prop(prop: Var, valid: bool):
    """Test that the prop is valid.

    Args:
        prop: The prop to test.
        valid: The expected validity of the prop.
    """
    assert Tag.is_valid_prop(prop) == valid


def test_add_props():
    """Test that the props are added."""
    tag = Tag().add_props(key="value", key2=42, invalid=None, invalid2={})
    assert tag.props["key"] == Var.create("value")
    assert tag.props["key2"] == Var.create(42)
    assert "invalid" not in tag.props
    assert "invalid2" not in tag.props


@pytest.mark.parametrize(
    "tag,expected",
    [
        (Tag(), {"name": "", "contents": "", "props": {}}),
        (Tag(name="br"), {"name": "br", "contents": "", "props": {}}),
        (Tag(contents="hello"), {"name": "", "contents": "hello", "props": {}}),
        (
            Tag(name="h1", contents="hello"),
            {"name": "h1", "contents": "hello", "props": {}},
        ),
        (
            Tag(name="box", props={"color": "red", "textAlign": "center"}),
            {
                "name": "box",
                "contents": "",
                "props": {"color": "red", "textAlign": "center"},
            },
        ),
        (
            Tag(
                name="box",
                props={"color": "red", "textAlign": "center"},
                contents="text",
            ),
            {
                "name": "box",
                "contents": "text",
                "props": {"color": "red", "textAlign": "center"},
            },
        ),
    ],
)
def test_format_tag(tag: Tag, expected: Dict):
    """Test that the tag dict is correct.

    Args:
        tag: The tag to test.
        expected: The expected tag dictionary.
    """
    tag_dict = dict(tag)
    assert tag_dict["name"] == expected["name"]
    assert tag_dict["contents"] == expected["contents"]
    assert tag_dict["props"] == expected["props"]


def test_format_cond_tag():
    """Test that the cond tag dict is correct."""
    tag = CondTag(
        true_value=dict(Tag(name="h1", contents="True content")),
        false_value=dict(Tag(name="h2", contents="False content")),
        cond=BaseVar(name="logged_in", type_=bool),
    )
    tag_dict = dict(tag)
    cond, true_value, false_value = (
        tag_dict["cond"],
        tag_dict["true_value"],
        tag_dict["false_value"],
    )
    assert cond == "logged_in"

    assert true_value["name"] == "h1"
    assert true_value["contents"] == "True content"

    assert false_value["name"] == "h2"
    assert false_value["contents"] == "False content"


def test_tagless_string_representation():
    """Test that the string representation of a tagless is correct."""
    tag = tagless.Tagless(contents="Hello world")
    expected_output = "Hello world"
    assert str(tag) == expected_output
