using System.Text.Json;

using Microsoft.Teams.Common;

namespace Microsoft.Teams.Cards.Tests;

public class AdaptiveCardsTest
{
    [Fact]
    public void Should_Serialize_AdaptiveCard_Simple()
    {
        // arrange
        AdaptiveCard card = new AdaptiveCard(new List<CardElement>
            {
                new TextBlock("Hello, Adaptive Card!")
            });

        // act
        var json = JsonSerializer.Serialize(card);

        // assert
        using var doc = JsonDocument.Parse(json);
        var root = doc.RootElement;

        Assert.True(root.TryGetProperty("body", out var bodyElement));
        Assert.Equal(JsonValueKind.Array, bodyElement.ValueKind);
        Assert.Equal(1, bodyElement.GetArrayLength());

        var first = bodyElement[0];
        Assert.Equal("TextBlock", first.GetProperty("type").GetString());
        Assert.Equal("Hello, Adaptive Card!", first.GetProperty("text").GetString());
    }

    [Fact]
    public void Should_Deserialize_AdaptiveCard_Simple()
    {
        string json = @"{
            ""body"": [
                {
                    ""type"": ""TextBlock"",
                    ""text"": ""Hello, Adaptive Card!""
                }
            ]
        }";

        var card = JsonSerializer.Deserialize<AdaptiveCard>(json);

        Assert.NotNull(card);
        Assert.Single(card.Body!);
        Assert.IsType<TextBlock>(card.Body![0]);
        Assert.Equal("Hello, Adaptive Card!", ((TextBlock)card.Body[0]).Text);
    }

    [Fact]
    public void Should_Serialize_BasicCard_WithToggleInput()
    {
        // arrange - recreating CreateBasicAdaptiveCard from samples
        var card = new AdaptiveCard(new List<CardElement>
            {
                new TextBlock("Hello world")
                {
                    Wrap = true,
                    Weight = TextWeight.Bolder
                },
                new ToggleInput("Notify me")
                {
                    Id = "notify"
                }
            })
        {
            Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
            Actions = new List<Microsoft.Teams.Cards.Action>
            {
                new ExecuteAction
                {
                    Title = "Submit",
                    Data = new Union<string, SubmitActionData>(new SubmitActionData { NonSchemaProperties = new Dictionary<string, object?> { { "action", "submit_basic" } } }),
                    AssociatedInputs = AssociatedInputs.Auto
                }
            }
        };

        // act
        var json = JsonSerializer.Serialize(card);

        // assert
        using var doc = JsonDocument.Parse(json);
        var root = doc.RootElement;

        Assert.True(root.TryGetProperty("$schema", out var schemaElement));
        Assert.Equal("http://adaptivecards.io/schemas/adaptive-card.json", schemaElement.GetString());

        Assert.True(root.TryGetProperty("body", out var bodyElement));
        Assert.Equal(2, bodyElement.GetArrayLength());

        var textBlock = bodyElement[0];
        Assert.Equal("TextBlock", textBlock.GetProperty("type").GetString());
        Assert.Equal("Hello world", textBlock.GetProperty("text").GetString());
        Assert.True(textBlock.GetProperty("weight").GetString() == "Bolder");

        var toggleInput = bodyElement[1];
        Assert.Equal("Input.Toggle", toggleInput.GetProperty("type").GetString());
        Assert.Equal("notify", toggleInput.GetProperty("id").GetString());

        Assert.True(root.TryGetProperty("actions", out var actionsElement));
        Assert.Single(actionsElement.EnumerateArray());
    }

    [Fact]
    public void Should_Deserialize_ProfileCard_WithInputs()
    {
        string json = @"{
            ""schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"",
            ""body"": [
                {
                    ""type"": ""TextBlock"",
                    ""text"": ""User Profile"",
                    ""weight"": ""Bolder"",
                    ""size"": ""Large""
                },
                {
                    ""type"": ""Input.Text"",
                    ""id"": ""name"",
                    ""label"": ""Name"",
                    ""value"": ""John Doe""
                },
                {
                    ""type"": ""Input.Text"",
                    ""id"": ""email"",
                    ""label"": ""Email"",
                    ""value"": ""john@contoso.com""
                },
                {
                    ""type"": ""Input.Toggle"",
                    ""id"": ""subscribe"",
                    ""title"": ""Subscribe to newsletter"",
                    ""value"": ""false""
                }
            ],
            ""actions"": [
                {
                    ""type"": ""Action.Execute"",
                    ""title"": ""Save"",
                    ""data"": {
                        ""action"": ""save_profile"",
                        ""entity_id"": ""12345""
                    }
                }
            ]
        }";

        var card = JsonSerializer.Deserialize<AdaptiveCard>(json);

        Assert.NotNull(card);
        // Note: Schema might be serialized as $schema in JSON but not always set on deserialized object
        Assert.Equal(4, card.Body!.Count);

        var titleBlock = card.Body[0] as TextBlock;
        Assert.NotNull(titleBlock);
        Assert.Equal("User Profile", titleBlock.Text);
        Assert.Equal("Bolder", titleBlock.Weight?.ToString());

        var nameInput = card.Body[1] as TextInput;
        Assert.NotNull(nameInput);
        Assert.Equal("name", nameInput.Id);
        Assert.Equal("Name", nameInput.Label);
        Assert.Equal("John Doe", nameInput.Value);

        var emailInput = card.Body[2] as TextInput;
        Assert.NotNull(emailInput);
        Assert.Equal("email", emailInput.Id);
        Assert.Equal("Email", emailInput.Label);

        var toggleInput = card.Body[3] as ToggleInput;
        Assert.NotNull(toggleInput);
        Assert.Equal("subscribe", toggleInput.Id);
        Assert.Equal("Subscribe to newsletter", toggleInput.Title);

        Assert.Single(card.Actions!);
        var executeAction = card!.Actions[0]! as ExecuteAction;
        Assert.NotNull(executeAction);
        Assert.Equal("Save", executeAction.Title);
    }

    [Fact]
    public void Should_Serialize_TaskFormCard_WithChoiceSet()
    {
        // arrange - recreating CreateTaskFormCard from samples
        var card = new AdaptiveCard(new List<CardElement>
            {
                new TextBlock("Create New Task")
                {
                    Weight = TextWeight.Bolder,
                    Size = TextSize.Large
                },
                new TextInput
                {
                    Id = "title",
                    Label = "Task Title",
                    Placeholder = "Enter task title"
                },
                new ChoiceSetInput(new List<Choice>
                    {
                        new() { Title = "High", Value = "high" },
                        new() { Title = "Medium", Value = "medium" },
                        new() { Title = "Low", Value = "low" }
                    })
                {
                    Id = "priority",
                    Label = "Priority",
                    Value = "medium"
                },
                new DateInput
                {
                    Id = "due_date",
                    Label = "Due Date",
                    Value = "2024-01-15"
                }
            })
        {
            Schema = "http://adaptivecards.io/schemas/adaptive-card.json"
        };

        // act
        var json = JsonSerializer.Serialize(card);

        // assert
        using var doc = JsonDocument.Parse(json);
        var root = doc.RootElement;

        Assert.True(root.TryGetProperty("body", out var bodyElement));
        Assert.Equal(4, bodyElement.GetArrayLength());

        var choiceSetInput = bodyElement[2];
        Assert.Equal("Input.ChoiceSet", choiceSetInput.GetProperty("type").GetString());
        Assert.Equal("priority", choiceSetInput.GetProperty("id").GetString());
        Assert.Equal("medium", choiceSetInput.GetProperty("value").GetString());

        Assert.True(choiceSetInput.TryGetProperty("choices", out var choicesElement));
        Assert.Equal(3, choicesElement.GetArrayLength());
        Assert.Equal("High", choicesElement[0].GetProperty("title").GetString());
        Assert.Equal("high", choicesElement[0].GetProperty("value").GetString());

        var dateInput = bodyElement[3];
        Assert.Equal("Input.Date", dateInput.GetProperty("type").GetString());
        Assert.Equal("due_date", dateInput.GetProperty("id").GetString());
    }

    [Fact]
    public void Should_Deserialize_ComplexCard_FromJson()
    {
        // Using the JSON structure from CreateCardFromJson in samples
        string json = @"{
            ""type"": ""AdaptiveCard"",
            ""body"": [
                {
                    ""type"": ""ColumnSet"",
                    ""columns"": [
                        {
                            ""type"": ""Column"",
                            ""verticalContentAlignment"": ""center"",
                            ""items"": [
                                {
                                    ""type"": ""Image"",
                                    ""style"": ""Person"",
                                    ""url"": ""https://aka.ms/AAp9xo4"",
                                    ""size"": ""Small"",
                                    ""altText"": ""Portrait of David Claux""
                                }
                            ],
                            ""width"": ""auto""
                        },
                        {
                            ""type"": ""Column"",
                            ""spacing"": ""medium"",
                            ""verticalContentAlignment"": ""center"",
                            ""items"": [
                                {
                                    ""type"": ""TextBlock"",
                                    ""weight"": ""Bolder"",
                                    ""text"": ""David Claux"",
                                    ""wrap"": true
                                }
                            ],
                            ""width"": ""auto""
                        }
                    ]
                },
                {
                    ""type"": ""TextBlock"",
                    ""text"": ""This card was created from JSON deserialization!"",
                    ""wrap"": true,
                    ""color"": ""good"",
                    ""spacing"": ""medium""
                }
            ],
            ""actions"": [
                {
                    ""type"": ""Action.Execute"",
                    ""title"": ""Test JSON Action"",
                    ""data"": {
                        ""Value"": {
                            ""action"": ""test_json""
                        }
                    }
                }
            ],
            ""version"": ""1.5"",
            ""schema"": ""http://adaptivecards.io/schemas/adaptive-card.json""
        }";

        var card = JsonSerializer.Deserialize<AdaptiveCard>(json)!;

        Assert.NotNull(card);
        Assert.Equal("1.5", card.Version);
        // Note: Schema property might not be set during deserialization, focus on content verification
        Assert.Equal(2, card.Body!.Count);

        var columnSet = card.Body[0] as ColumnSet;
        Assert.NotNull(columnSet);
        Assert.Equal(2, columnSet.Columns!.Count);

        var firstColumn = columnSet.Columns[0];
        Assert.Equal("auto", firstColumn!.Width.ToString());
        Assert.Single(firstColumn.Items!);

        var image = firstColumn.Items[0] as Image;
        Assert.NotNull(image);
        Assert.Equal("https://aka.ms/AAp9xo4", image.Url);
        Assert.Equal("Person", image.Style?.ToString());

        var textBlock = card.Body[1] as TextBlock;
        Assert.NotNull(textBlock);
        Assert.Equal("This card was created from JSON deserialization!", textBlock.Text);
        Assert.Equal("good", textBlock.Color?.ToString());

        Assert.Single(card.Actions!);
        var executeAction = card.Actions[0] as ExecuteAction;
        Assert.NotNull(executeAction);
        Assert.Equal("Test JSON Action", executeAction.Title);
    }

    [Fact]
    public void Should_Serialize_FeedbackCard_WithMultilineInput()
    {
        // arrange - recreating CreateFeedbackCard from samples
        var card = new AdaptiveCard(new List<CardElement>
            {
                new TextBlock("Feedback Form")
                {
                    Weight = TextWeight.Bolder,
                    Size = TextSize.Large
                },
                new TextInput
                {
                    Id = "feedback",
                    Label = "Your Feedback",
                    Placeholder = "Please share your thoughts...",
                    IsMultiline = true,
                    IsRequired = true
                }
            })
        {
            Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
            Actions = new List<Microsoft.Teams.Cards.Action>
            {
                new ExecuteAction
                {
                    Title = "Submit Feedback",
                    Data = new Union<string, SubmitActionData>(new SubmitActionData { NonSchemaProperties = new Dictionary<string, object?> { { "action", "submit_feedback" } } }),
                    AssociatedInputs = AssociatedInputs.Auto
                }
            }
        };

        // act
        var json = JsonSerializer.Serialize(card);

        // assert
        using var doc = JsonDocument.Parse(json);
        var root = doc.RootElement;

        Assert.True(root.TryGetProperty("body", out var bodyElement));
        Assert.Equal(2, bodyElement.GetArrayLength());

        var textInput = bodyElement[1];
        Assert.Equal("Input.Text", textInput.GetProperty("type").GetString());
        Assert.Equal("feedback", textInput.GetProperty("id").GetString());
        Assert.Equal("Your Feedback", textInput.GetProperty("label").GetString());
        Assert.True(textInput.GetProperty("isMultiline").GetBoolean());
        Assert.True(textInput.GetProperty("isRequired").GetBoolean());

        Assert.True(root.TryGetProperty("actions", out var actionsElement));
        var action = actionsElement[0];
        Assert.Equal("Action.Execute", action.GetProperty("type").GetString());
        Assert.Equal("Submit Feedback", action.GetProperty("title").GetString());
    }

    [Fact]
    public void Should_Deserialize_ValidationCard_WithNumberInput()
    {
        string json = @"{
            ""schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"",
            ""body"": [
                {
                    ""type"": ""TextBlock"",
                    ""text"": ""Profile with Validation"",
                    ""weight"": ""Bolder"",
                    ""size"": ""Large""
                },
                {
                    ""type"": ""Input.Number"",
                    ""id"": ""age"",
                    ""label"": ""Age"",
                    ""isRequired"": true,
                    ""min"": 0,
                    ""max"": 120
                },
                {
                    ""type"": ""Input.Text"",
                    ""id"": ""name"",
                    ""label"": ""Name"",
                    ""isRequired"": true,
                    ""errorMessage"": ""Name is required""
                }
            ]
        }";

        var card = JsonSerializer.Deserialize<AdaptiveCard>(json);

        Assert.NotNull(card);
        Assert.Equal(3, card.Body!.Count);

        var numberInput = card.Body[1] as NumberInput;
        Assert.NotNull(numberInput);
        Assert.Equal("age", numberInput.Id);
        Assert.Equal("Age", numberInput.Label);
        Assert.True(numberInput.IsRequired);
        Assert.Equal(0, numberInput.Min);
        Assert.Equal(120, numberInput.Max);

        var textInput = card.Body[2] as TextInput;
        Assert.NotNull(textInput);
        Assert.Equal("name", textInput.Id);
        Assert.True(textInput.IsRequired);
        Assert.Equal("Name is required", textInput.ErrorMessage);
    }

    [Fact]
    public void Should_Deserialize()
    {
        // Test what minimal JsonSerializerOptions are actually required
        string json = """
        {
            "type": "AdaptiveCard",
            "body": [
                {
                    "type": "TextBlock",
                    "text": "Hello World",
                    "weight": "Bolder"
                }
            ],
            "actions": [
                {
                    "type": "Action.Execute",
                    "title": "Submit",
                    "associatedInputs": "auto"
                }
            ]
        }
        """;

        // Test 1: No options at all
        var card1 = JsonSerializer.Deserialize<AdaptiveCard>(json);
        Assert.NotNull(card1);
        Assert.Single(card1.Body!);

        // Test 2: Only PropertyNameCaseInsensitive 
        var options2 = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
        var card2 = JsonSerializer.Deserialize<AdaptiveCard>(json, options2);
        Assert.NotNull(card2);
        Assert.Single(card2.Body!);

        // Test 3: With CamelCase policy (what we had in docs)
        var options3 = new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true,
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        };
        var card3 = JsonSerializer.Deserialize<AdaptiveCard>(json, options3);
        Assert.NotNull(card3);
        Assert.Single(card3.Body!);

        // All should work the same
        var textBlock1 = card1.Body![0] as TextBlock;
        var textBlock2 = card2.Body![0] as TextBlock;
        var textBlock3 = card3.Body![0] as TextBlock;

        Assert.Equal("Hello World", textBlock1?.Text);
        Assert.Equal("Hello World", textBlock2?.Text);
        Assert.Equal("Hello World", textBlock3?.Text);
    }

    [Fact]
    public void Should_Not_Serialize_Null_MsTeams_Property_On_SubmitAction()
    {
        // arrange
        var card = new AdaptiveCard(new List<CardElement>
            {
                new TextBlock("Test card with Submit action")
            })
        {
            Actions = new List<Microsoft.Teams.Cards.Action>
            {
                new SubmitAction
                {
                    Title = "Submit",
                    Data = new Union<string, SubmitActionData>("test_data")
                }
            }
        };

        // act
        var json = JsonSerializer.Serialize(card);

        // assert
        Assert.DoesNotContain("\"msTeams\":null", json);
        Assert.DoesNotContain("\"msTeams\": null", json);

        // Verify the action is still properly serialized
        using var doc = JsonDocument.Parse(json);
        var root = doc.RootElement;

        Assert.True(root.TryGetProperty("actions", out var actionsElement));
        var action = actionsElement[0];
        Assert.Equal("Action.Submit", action.GetProperty("type").GetString());
        Assert.Equal("Submit", action.GetProperty("title").GetString());

        // Verify msTeams property is completely absent, not just null
        Assert.False(action.TryGetProperty("msTeams", out _));
    }

    [Fact]
    public void Should_Serialize_Actions()
    {
        var actionJson = """
            {
            "type": "Action.OpenUrl",
            "url": "https://adaptivecards.microsoft.com",
            "title": "Learn More"
            }
        """;
        var action = OpenUrlAction.Deserialize(actionJson);
        Assert.NotNull(action);
        Assert.IsType<OpenUrlAction>(action);
        Assert.Equal("Learn More", action.Title);
        Assert.Equal("https://adaptivecards.microsoft.com", action.Url);
    }

    [Fact]
    public void SubmitData_Should_Set_Action_Field()
    {
        var data = new SubmitData("save_profile");

        var json = JsonSerializer.Serialize(data);
        using var doc = JsonDocument.Parse(json);
        var root = doc.RootElement;

        Assert.True(root.TryGetProperty("action", out var actionElement));
        Assert.Equal("save_profile", actionElement.GetString());
    }

    [Fact]
    public void SubmitData_Should_Include_Extra_Data()
    {
        var data = new SubmitData("save_profile", new Dictionary<string, object?> { ["entity_id"] = "12345" });

        var json = JsonSerializer.Serialize(data);
        using var doc = JsonDocument.Parse(json);
        var root = doc.RootElement;

        Assert.True(root.TryGetProperty("action", out var actionElement));
        Assert.Equal("save_profile", actionElement.GetString());
        Assert.True(root.TryGetProperty("entity_id", out var entityElement));
        Assert.Equal("12345", entityElement.GetString());
    }

    [Fact]
    public void OpenDialogData_Should_Set_Msteams_And_DialogId()
    {
        var data = new OpenDialogData("simple_form");

        var json = JsonSerializer.Serialize(data);
        using var doc = JsonDocument.Parse(json);
        var root = doc.RootElement;

        Assert.True(root.TryGetProperty("msteams", out var msteamsElement));
        Assert.Equal("task/fetch", msteamsElement.GetProperty("type").GetString());
        Assert.True(root.TryGetProperty("dialog_id", out var dialogIdElement));
        Assert.Equal("simple_form", dialogIdElement.GetString());
    }

    [Fact]
    public void OpenDialogData_Should_Include_Extra_Data()
    {
        var data = new OpenDialogData("simple_form", new Dictionary<string, object?> { ["custom_key"] = "value" });

        var json = JsonSerializer.Serialize(data);
        using var doc = JsonDocument.Parse(json);
        var root = doc.RootElement;

        Assert.True(root.TryGetProperty("msteams", out _));
        Assert.True(root.TryGetProperty("dialog_id", out var dialogIdElement));
        Assert.Equal("simple_form", dialogIdElement.GetString());
        Assert.True(root.TryGetProperty("custom_key", out var customElement));
        Assert.Equal("value", customElement.GetString());
    }
}