namespace Spectre.Console.Tests.Unit;

public sealed class StyleTests
{
    [Fact]
    public void Should_Convert_From_Color_As_Expected()
    {
        // Given, When
        Style style = Color.Red;

        // Then
        style.Foreground.ShouldBe(Color.Red);
    }

    [Fact]
    public void Should_Combine_Two_Styles_As_Expected()
    {
        // Given
        var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic);
        var other = new Style(Color.Green, Color.Silver, Decoration.Underline, "https://example.com");

        // When
        var result = first.Combine(other);

        // Then
        result.Foreground.ShouldBe(Color.Green);
        result.Background.ShouldBe(Color.Silver);
        result.Decoration.ShouldBe(Decoration.Bold | Decoration.Italic | Decoration.Underline);
        result.Link.ShouldBe("https://example.com");
    }

    [Fact]
    public void Should_Consider_Two_Identical_Styles_Equal()
    {
        // Given
        var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
        var second = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");

        // When
        var result = first.Equals(second);

        // Then
        result.ShouldBeTrue();
    }

    [Fact]
    public void Should_Not_Consider_Two_Styles_With_Different_Foreground_Colors_Equal()
    {
        // Given
        var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
        var second = new Style(Color.Blue, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");

        // When
        var result = first.Equals(second);

        // Then
        result.ShouldBeFalse();
    }

    [Fact]
    public void Should_Not_Consider_Two_Styles_With_Different_Background_Colors_Equal()
    {
        // Given
        var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
        var second = new Style(Color.White, Color.Blue, Decoration.Bold | Decoration.Italic, "http://example.com");

        // When
        var result = first.Equals(second);

        // Then
        result.ShouldBeFalse();
    }

    [Fact]
    public void Should_Not_Consider_Two_Styles_With_Different_Decorations_Equal()
    {
        // Given
        var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
        var second = new Style(Color.White, Color.Yellow, Decoration.Bold, "http://example.com");

        // When
        var result = first.Equals(second);

        // Then
        result.ShouldBeFalse();
    }

    [Fact]
    public void Should_Not_Consider_Two_Styles_With_Different_Links_Equal()
    {
        // Given
        var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
        var second = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://foo.com");

        // When
        var result = first.Equals(second);

        // Then
        result.ShouldBeFalse();
    }

    public sealed class TheParseMethod
    {
        [Fact]
        public void Default_Keyword_Should_Return_Default_Style()
        {
            // Given, When
            var result = Style.Parse("default");

            // Then
            result.ShouldNotBeNull();
            result.Foreground.ShouldBe(Color.Default);
            result.Background.ShouldBe(Color.Default);
            result.Decoration.ShouldBe(Decoration.None);
        }

        [Theory]
        [InlineData("bold", Decoration.Bold)]
        [InlineData("b", Decoration.Bold)]
        [InlineData("dim", Decoration.Dim)]
        [InlineData("i", Decoration.Italic)]
        [InlineData("italic", Decoration.Italic)]
        [InlineData("underline", Decoration.Underline)]
        [InlineData("u", Decoration.Underline)]
        [InlineData("invert", Decoration.Invert)]
        [InlineData("conceal", Decoration.Conceal)]
        [InlineData("slowblink", Decoration.SlowBlink)]
        [InlineData("rapidblink", Decoration.RapidBlink)]
        [InlineData("strikethrough", Decoration.Strikethrough)]
        [InlineData("s", Decoration.Strikethrough)]
        public void Should_Parse_Decoration(string text, Decoration decoration)
        {
            // Given, When
            var result = Style.Parse(text);

            // Then
            result.ShouldNotBeNull();
            result.Decoration.ShouldBe(decoration);
        }

        [Fact]
        public void Should_Parse_Link_Without_Address()
        {
            // Given, When
            var result = Style.Parse("link");

            // Then
            result.ShouldNotBeNull();
            result.Link.ShouldBe("https://emptylink");
        }

        [Fact]
        public void Should_Parse_Link()
        {
            // Given, When
            var result = Style.Parse("link=https://example.com");

            // Then
            result.ShouldNotBeNull();
            result.Link.ShouldBe("https://example.com");
        }

        [Fact]
        public void Should_Throw_If_Link_Is_Set_Twice()
        {
            // Given, When
            var result = Record.Exception(() => Style.Parse("link=https://example.com link=https://example.com"));

            // Then
            result.ShouldBeOfType<InvalidOperationException>();
            result.Message.ShouldBe("A link has already been set.");
        }

        [Fact]
        public void Should_Parse_Background_If_Foreground_Is_Set_To_Default()
        {
            // Given, When
            var result = Style.Parse("default on green");

            // Then
            result.ShouldNotBeNull();
            result.Decoration.ShouldBe(Decoration.None);
            result.Foreground.ShouldBe(Color.Default);
            result.Background.ShouldBe(Color.Green);
        }

        [Fact]
        public void Should_Throw_If_Foreground_Is_Set_Twice()
        {
            // Given, When
            var result = Record.Exception(() => Style.Parse("green yellow"));

            // Then
            result.ShouldBeOfType<InvalidOperationException>();
            result.Message.ShouldBe("A foreground color has already been set.");
        }

        [Fact]
        public void Should_Throw_If_Background_Is_Set_Twice()
        {
            // Given, When
            var result = Record.Exception(() => Style.Parse("green on blue yellow"));

            // Then
            result.ShouldBeOfType<InvalidOperationException>();
            result.Message.ShouldBe("A background color has already been set.");
        }

        [Fact]
        public void Should_Throw_If_Color_Name_Could_Not_Be_Found()
        {
            // Given, When
            var result = Record.Exception(() => Style.Parse("bold lol"));

            // Then
            result.ShouldBeOfType<InvalidOperationException>();
            result.Message.ShouldBe("Could not find color or style 'lol'.");
        }

        [Fact]
        public void Should_Throw_If_Background_Color_Name_Could_Not_Be_Found()
        {
            // Given, When
            var result = Record.Exception(() => Style.Parse("blue on lol"));

            // Then
            result.ShouldBeOfType<InvalidOperationException>();
            result.Message.ShouldBe("Could not find color 'lol'.");
        }

        [Fact]
        public void Should_Parse_Colors_And_Decoration_And_Link()
        {
            // Given, When
            var result = Style.Parse("link=https://example.com bold underline blue on green");

            // Then
            result.ShouldNotBeNull();
            result.Decoration.ShouldBe(Decoration.Bold | Decoration.Underline);
            result.Foreground.ShouldBe(Color.Blue);
            result.Background.ShouldBe(Color.Green);
            result.Link.ShouldBe("https://example.com");
        }

        [Theory]
        [InlineData("#FF0000 on #0000FF")]
        [InlineData("#F00 on #00F")]
        public void Should_Parse_Hex_Colors_Correctly(string style)
        {
            // Given, When
            var result = Style.Parse(style);

            // Then
            result.Foreground.ShouldBe(Color.Red);
            result.Background.ShouldBe(Color.Blue);
        }

        [Theory]
        [InlineData("#", "Invalid hex color '#'.")]
        [InlineData("#FF00FF00FF", "Invalid hex color '#FF00FF00FF'.")]
        [InlineData("#FOO", "Invalid hex color '#FOO'. Could not find any recognizable digits.")]
        public void Should_Return_Error_If_Hex_Color_Is_Invalid(string style, string expected)
        {
            // Given, When
            var result = Record.Exception(() => Style.Parse(style));

            // Then
            result.ShouldNotBeNull();
            result.Message.ShouldBe(expected);
        }

        [Theory]
        [InlineData("rgb(255,0,0) on rgb(0,0,255)")]
        public void Should_Parse_Rgb_Colors_Correctly(string style)
        {
            // Given, When
            var result = Style.Parse(style);

            // Then
            result.Foreground.ShouldBe(Color.Red);
            result.Background.ShouldBe(Color.Blue);
        }

        [Theory]
        [InlineData("12 on 24")]
        public void Should_Parse_Colors_Numbers_Correctly(string style)
        {
            // Given, When
            var result = Style.Parse(style);

            // Then
            result.Foreground.ShouldBe(Color.Blue);
            result.Background.ShouldBe(Color.DeepSkyBlue4_1);
        }

        [Theory]
        [InlineData("-12", "Color number must be greater than or equal to 0 (was -12)")]
        [InlineData("256", "Color number must be less than or equal to 255 (was 256)")]
        public void Should_Return_Error_If_Color_Number_Is_Invalid(string style, string expected)
        {
            // Given, When
            var result = Record.Exception(() => Style.Parse(style));

            // Then
            result.ShouldNotBeNull();
            result.Message.ShouldBe(expected);
        }

        [Theory]
        [InlineData("rgb()", "Invalid RGB color 'rgb()'.")]
        [InlineData("rgb(", "Invalid RGB color 'rgb('.")]
        [InlineData("rgb(255)", "Invalid RGB color 'rgb(255)'.")]
        [InlineData("rgb(255,255)", "Invalid RGB color 'rgb(255,255)'.")]
        [InlineData("rgb(255,255,255", "Invalid RGB color 'rgb(255,255,255'.")]
        [InlineData("rgb(A,B,C)", "Invalid RGB color 'rgb(A,B,C)'.")]
        public void Should_Return_Error_If_Rgb_Color_Is_Invalid(string style, string expected)
        {
            // Given, When
            var result = Record.Exception(() => Style.Parse(style));

            // Then
            result.ShouldNotBeNull();
            result.Message.ShouldStartWith(expected);
        }
    }

    public sealed class TheTryParseMethod
    {
        [Fact]
        public void Should_Return_True_If_Parsing_Succeeded()
        {
            // Given, When
            var result = Style.TryParse("bold", out var style);

            // Then
            result.ShouldBeTrue();
            style.ShouldNotBeNull();
            style.Decoration.ShouldBe(Decoration.Bold);
        }

        [Fact]
        public void Should_Return_False_If_Parsing_Failed()
        {
            // Given, When
            var result = Style.TryParse("lol", out _);

            // Then
            result.ShouldBeFalse();
        }
    }

    public sealed class TheToMarkupMethod
    {
        [Fact]
        public void Should_Return_Expected_Markup_For_Style_With_Foreground_Color()
        {
            // Given
            var style = new Style(Color.Red);

            // When
            var result = style.ToMarkup();

            // Then
            result.ShouldBe("red");
        }

        [Fact]
        public void Should_Return_Expected_Markup_For_Style_With_Foreground_And_Background_Color()
        {
            // Given
            var style = new Style(Color.Red, Color.Green);

            // When
            var result = style.ToMarkup();

            // Then
            result.ShouldBe("red on green");
        }

        [Fact]
        public void Should_Return_Expected_Markup_For_Style_With_Foreground_And_Background_Color_And_Decoration()
        {
            // Given
            var style = new Style(Color.Red, Color.Green, Decoration.Bold | Decoration.Underline);

            // When
            var result = style.ToMarkup();

            // Then
            result.ShouldBe("bold underline red on green");
        }

        [Fact]
        public void Should_Return_Expected_Markup_For_Style_With_Only_Background_Color()
        {
            // Given
            var style = new Style(background: Color.Green);

            // When
            var result = style.ToMarkup();

            // Then
            result.ShouldBe("default on green");
        }

        [Fact]
        public void Should_Return_Expected_Markup_For_Style_With_Only_Link()
        {
            // Given
            var style = new Style(link: "https://spectreconsole.net/");

            // When
            var result = style.ToMarkup();

            // Then
            result.ShouldBe("link=https://spectreconsole.net/");
        }

        [Fact]
        public void Should_Return_Expected_Markup_For_Style_With_Background_And_Link()
        {
            // Given
            var style = new Style(background: Color.Blue, link: "https://spectreconsole.net/");

            // When
            var result = style.ToMarkup();

            // Then
            result.ShouldBe("default on blue link=https://spectreconsole.net/");
        }
    }
}