from decimal import Decimal

import pytest
from rich.style import Style

from textual.color import Color
from textual.css.errors import StyleValueError
from textual.css.scalar import Scalar, Unit
from textual.css.styles import RenderStyles, Styles
from textual.dom import DOMNode
from textual.widget import Widget


def test_styles_reset():
    styles = Styles()
    styles.text_style = "not bold"
    assert styles.text_style == Style(bold=False)
    styles.reset()
    assert styles.text_style is Style.null()


def test_has_rule():
    styles = Styles()
    assert not styles.has_rule("text_style")
    styles.text_style = "bold"
    assert styles.has_rule("text_style")
    styles.text_style = None
    assert not styles.has_rule("text_style")


def test_clear_rule():
    styles = Styles()
    styles.text_style = "bold"
    assert styles.has_rule("text_style")
    styles.clear_rule("text_style")
    assert not styles.has_rule("text_style")


def test_get_rules():
    styles = Styles()
    # Empty rules at start
    assert styles.get_rules() == {}
    styles.text_style = "bold"
    assert styles.get_rules() == {"text_style": Style.parse("bold")}
    styles.display = "none"
    assert styles.get_rules() == {
        "text_style": Style.parse("bold"),
        "display": "none",
    }


def test_set_rule():
    styles = Styles()
    assert styles.get_rules() == {}
    styles.set_rule("text_style", Style.parse("bold"))
    assert styles.get_rules() == {"text_style": Style.parse("bold")}


def test_reset():
    styles = Styles()
    assert styles.get_rules() == {}
    styles.set_rule("text_style", Style.parse("bold"))
    assert styles.get_rules() == {"text_style": Style.parse("bold")}
    styles.reset()
    assert styles.get_rules() == {}


def test_merge():
    styles = Styles()
    styles.set_rule("text_style", Style.parse("bold"))
    styles2 = Styles()
    styles2.set_rule("display", "none")
    styles.merge(styles2)
    assert styles.get_rules() == {
        "text_style": Style.parse("bold"),
        "display": "none",
    }


def test_merge_rules():
    styles = Styles()
    styles.set_rule("text_style", Style.parse("bold"))
    styles.merge_rules({"display": "none"})
    assert styles.get_rules() == {
        "text_style": Style.parse("bold"),
        "display": "none",
    }


def test_render_styles_border():
    base = Styles()
    inline = Styles()
    styles_view = RenderStyles(None, base, inline)

    base.border_top = ("heavy", "red")
    # Base has border-top: heavy red
    assert styles_view.border_top == ("heavy", Color.parse("red"))

    inline.border_left = ("rounded", "green")
    # Base has border-top heavy red, inline has border-left: rounded green
    assert styles_view.border_top == ("heavy", Color.parse("red"))
    assert styles_view.border_left == ("rounded", Color.parse("green"))
    assert styles_view.border == (
        ("heavy", Color.parse("red")),
        ("", Color(0, 255, 0)),
        ("", Color(0, 255, 0)),
        ("rounded", Color.parse("green")),
    )


def test_get_opacity_default():
    styles = RenderStyles(DOMNode(), Styles(), Styles())
    assert styles.text_opacity == 1.0


def test_styles_css_property():
    css = "opacity: 50%; text-opacity: 20%; background: green; color: red; tint: dodgerblue 20%;"
    styles = Styles().parse(css, path="")
    assert styles.css == (
        "background: #008000;\n"
        "color: #FF0000;\n"
        "opacity: 0.5;\n"
        "text-opacity: 0.2;\n"
        "tint: rgba(30,144,255,0.2);"
    )


@pytest.mark.parametrize(
    "set_value, expected",
    [
        [0.2, 0.2],
        [-0.4, 0.0],
        [5.8, 1.0],
        ["25%", 0.25],
        ["-10%", 0.0],
        ["120%", 1.0],
    ],
)
def test_opacity_set_then_get(set_value, expected):
    styles = RenderStyles(DOMNode(), Styles(), Styles())
    styles.text_opacity = set_value
    assert styles.text_opacity == expected


def test_opacity_set_invalid_type_error():
    styles = RenderStyles(DOMNode(), Styles(), Styles())
    with pytest.raises(StyleValueError):
        styles.text_opacity = "invalid value"


@pytest.mark.parametrize(
    "size_dimension_input,size_dimension_expected_output",
    [
        # fmt: off
        [None, None],
        [1, Scalar(1, Unit.CELLS, Unit.WIDTH)],
        [1.0, Scalar(1.0, Unit.CELLS, Unit.WIDTH)],
        [1.2, Scalar(1.2, Unit.CELLS, Unit.WIDTH)],
        [1.2e3, Scalar(1200.0, Unit.CELLS, Unit.WIDTH)],
        ["20", Scalar(20, Unit.CELLS, Unit.WIDTH)],
        ["1.4", Scalar(1.4, Unit.CELLS, Unit.WIDTH)],
        [Scalar(100, Unit.CELLS, Unit.WIDTH), Scalar(100, Unit.CELLS, Unit.WIDTH)],
        [Scalar(10.3, Unit.CELLS, Unit.WIDTH), Scalar(10.3, Unit.CELLS, Unit.WIDTH)],
        [Scalar(10.4, Unit.CELLS, Unit.HEIGHT), Scalar(10.4, Unit.CELLS, Unit.HEIGHT)],
        [Scalar(10.5, Unit.PERCENT, Unit.WIDTH), Scalar(10.5, Unit.WIDTH, Unit.WIDTH)],
        [Scalar(10.6, Unit.PERCENT, Unit.PERCENT), Scalar(10.6, Unit.WIDTH, Unit.WIDTH)],
        [Scalar(10.7, Unit.HEIGHT, Unit.PERCENT), Scalar(10.7, Unit.HEIGHT, Unit.PERCENT)],
        # percentage values are normalised to floats and get the WIDTH "percent_unit":
        [Scalar(11, Unit.PERCENT, Unit.HEIGHT), Scalar(11.0, Unit.WIDTH, Unit.WIDTH)],
        # fmt: on
    ],
)
def test_widget_style_size_can_accept_various_data_types_and_normalize_them(
    size_dimension_input, size_dimension_expected_output
):
    widget = Widget()

    widget.styles.width = size_dimension_input
    assert widget.styles.width == size_dimension_expected_output


@pytest.mark.parametrize(
    "size_dimension_input",
    [
        "a",
        "1.4e3",
        3.14j,
        Decimal("3.14"),
        list(),
        tuple(),
        dict(),
    ],
)
def test_widget_style_size_fails_if_data_type_is_not_supported(size_dimension_input):
    widget = Widget()

    with pytest.raises(StyleValueError):
        widget.styles.width = size_dimension_input
