using Bunit;
using Xunit;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Components;

namespace Radzen.Blazor.Tests
{
    public class PickListTests
    {
        public class Item
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }

        [Fact]
        public void PickList_Renders_WithClassName()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var component = ctx.RenderComponent<RadzenPickList<Item>>();

            Assert.Contains(@"rz-picklist", component.Markup);
        }

        [Fact]
        public void PickList_Renders_SourceAndTargetLists()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var component = ctx.RenderComponent<RadzenPickList<Item>>();

            Assert.Contains("rz-picklist-source-wrapper", component.Markup);
            Assert.Contains("rz-picklist-target-wrapper", component.Markup);
        }

        [Fact]
        public void PickList_Renders_TransferButtons()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var component = ctx.RenderComponent<RadzenPickList<Item>>();

            Assert.Contains("rz-picklist-buttons", component.Markup);
        }

        [Fact]
        public void PickList_Renders_Orientation_Horizontal()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.Orientation, Orientation.Horizontal);
            });

            Assert.Contains("rz-flex-row", component.Markup);
        }

        [Fact]
        public void PickList_Renders_Orientation_Vertical()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.Orientation, Orientation.Vertical);
            });

            Assert.Contains("rz-flex-column", component.Markup);
        }

        [Fact]
        public void PickList_Renders_AllowFiltering()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var data = new List<Item>
            {
                new Item { Id = 1, Name = "Item 1" }
            };

            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.AllowFiltering, true);
                parameters.Add(p => p.Source, data);
                parameters.Add(p => p.TextProperty, "Name");
            });

            Assert.Contains("rz-listbox-filter", component.Markup);
        }

        [Fact]
        public void PickList_Renders_SourceData()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var data = new List<Item>
            {
                new Item { Id = 1, Name = "Source Item 1" },
                new Item { Id = 2, Name = "Source Item 2" }
            };

            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.Source, data);
                parameters.Add(p => p.TextProperty, "Name");
            });

            Assert.Contains("Source Item 1", component.Markup);
            Assert.Contains("Source Item 2", component.Markup);
        }

        [Fact]
        public void PickList_Renders_ShowHeader()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.ShowHeader, true);
                parameters.Add(p => p.SourceHeader, builder => builder.AddContent(0, "Available Items"));
                parameters.Add(p => p.TargetHeader, builder => builder.AddContent(0, "Selected Items"));
            });

            Assert.Contains("Available Items", component.Markup);
            Assert.Contains("Selected Items", component.Markup);
        }

        [Fact]
        public void PickList_Renders_AllowMoveAll_Buttons()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var data = new List<Item> { new Item { Id = 1, Name = "Item" } };

            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.AllowMoveAll, true);
                parameters.Add(p => p.Source, data);
            });

            // Should have 4 buttons when AllowMoveAll is true
            var buttonCount = System.Text.RegularExpressions.Regex.Matches(component.Markup, "rz-button").Count;
            Assert.True(buttonCount >= 4);
        }

        [Fact]
        public void PickList_Renders_Disabled()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.Disabled, true);
            });

            Assert.Contains("disabled", component.Markup);
        }

        [Fact]
        public void PickList_GetSelectedSources_Respects_ValueProperty_Single()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;

            var data = new List<Item>
            {
                new Item { Id = 1, Name = "A" },
                new Item { Id = 2, Name = "B" }
            };

            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.Source, data);
                parameters.Add(p => p.TextProperty, "Name");
                parameters.Add(p => p.ValueProperty, "Id");
                parameters.Add(p => p.Multiple, false);
            });

            // Simulate ListBox selection when ValueProperty is set: selectedSourceItems becomes the value (Id)
            var field = typeof(RadzenPickList<Item>).GetField("selectedSourceItems", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
            field.SetValue(component.Instance, 2);

            var selected = component.Instance.GetSelectedSources();
            Assert.Single(selected);
            Assert.Equal(2, selected.First().Id);
        }

        [Fact]
        public void PickList_GetSelectedSources_Respects_ValueProperty_Multiple()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;

            var data = new List<Item>
            {
                new Item { Id = 1, Name = "A" },
                new Item { Id = 2, Name = "B" },
                new Item { Id = 3, Name = "C" }
            };

            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.Source, data);
                parameters.Add(p => p.TextProperty, "Name");
                parameters.Add(p => p.ValueProperty, "Id");
                parameters.Add(p => p.Multiple, true);
            });

            // Simulate ListBox selection when ValueProperty is set: selectedSourceItems becomes IEnumerable of values (Ids)
            var field = typeof(RadzenPickList<Item>).GetField("selectedSourceItems", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
            field.SetValue(component.Instance, new[] { 1, 3 });

            var selected = component.Instance.GetSelectedSources().Select(i => i.Id).OrderBy(i => i).ToArray();
            Assert.Equal(new[] { 1, 3 }, selected);
        }

        [Fact]
        public void PickList_Renders_DefaultEmptyText_WhenNoData()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var component = ctx.RenderComponent<RadzenPickList<Item>>();

            Assert.Contains("rz-listbox-empty-message", component.Markup);
            Assert.Contains("No records to display.", component.Markup);
        }

        [Fact]
        public void PickList_Renders_CustomEmptyText()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.EmptyText, "No items");
            });

            Assert.Contains("No items", component.Markup);
        }

        [Fact]
        public void PickList_Renders_SourceEmptyText_OverridesEmptyText()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.EmptyText, "No items");
                parameters.Add(p => p.SourceEmptyText, "No source items");
            });

            Assert.Contains("No source items", component.Markup);
        }

        [Fact]
        public void PickList_Renders_TargetEmptyText_OverridesEmptyText()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.EmptyText, "No items");
                parameters.Add(p => p.TargetEmptyText, "No target items");
            });

            Assert.Contains("No target items", component.Markup);
        }

        [Fact]
        public void PickList_Renders_EmptyTemplate()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.EmptyTemplate, builder => builder.AddContent(0, "Custom empty"));
            });

            Assert.Contains("Custom empty", component.Markup);
        }

        [Fact]
        public void PickList_Renders_SourceEmptyTemplate_OverridesEmptyTemplate()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.EmptyTemplate, builder => builder.AddContent(0, "Shared empty"));
                parameters.Add(p => p.SourceEmptyTemplate, builder => builder.AddContent(0, "Source custom empty"));
            });

            Assert.Contains("Source custom empty", component.Markup);
        }

        [Fact]
        public void PickList_Renders_SourceTemplate_OverridesTemplate()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            List<Item> data = [new() { Id = 1, Name = "Item" }];

            RenderFragment<Item> templateFragment = _ =>
                builder => builder.AddContent(0, "regular template");

            RenderFragment<Item> sourceTemplateFragment = _ =>
                builder => builder.AddContent(0, "source template");

            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters => parameters
                .Add(p => p.Template, templateFragment)
                .Add(p => p.SourceTemplate, sourceTemplateFragment)
                .Add(p => p.Source, data));

            Assert.Contains("source template", component.Markup);
        }

        [Fact]
        public void PickList_Renders_TargetTemplate_OverridesTemplate()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            List<Item> data = [new() { Id = 1, Name = "Item" }];

            RenderFragment<Item> templateFragment = _ =>
                builder => builder.AddContent(0, "regular template");

            RenderFragment<Item> targetTemplateFragment = _ =>
                builder => builder.AddContent(0, "target template");

            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters => parameters
                    .Add(p => p.Template, templateFragment)
                    .Add(p => p.TargetTemplate, targetTemplateFragment)
                    .Add(p => p.Target, data));

            Assert.Contains("target template", component.Markup);
        }

        [Fact]
        public void PickList_Move_Fires_OnMoveAllSourceToTarget()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;

            var source = new List<Item>
            {
                new Item { Id = 1, Name = "A" },
                new Item { Id = 2, Name = "B" }
            };

            PickListMoveEventArgs<Item> captured = null;

            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.Source, source);
                parameters.Add(p => p.TextProperty, "Name");
                parameters.Add(p => p.Move, args => captured = args);
            });

            component.Find("button[title='Move all items from source to target collection']").Click();

            Assert.NotNull(captured);
            Assert.True(captured.MoveDirectionToTarget);
            Assert.Equal(new[] { 1, 2 }, captured.Items.Select(i => i.Id).OrderBy(i => i).ToArray());
        }

        [Fact]
        public void PickList_Move_Fires_OnSelectedSourceToTarget()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;

            var source = new List<Item>
            {
                new Item { Id = 1, Name = "A" },
                new Item { Id = 2, Name = "B" },
                new Item { Id = 3, Name = "C" }
            };

            PickListMoveEventArgs<Item> captured = null;

            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.Source, source);
                parameters.Add(p => p.TextProperty, "Name");
                parameters.Add(p => p.ValueProperty, "Id");
                parameters.Add(p => p.Multiple, true);
                parameters.Add(p => p.Move, args => captured = args);
            });

            var field = typeof(RadzenPickList<Item>).GetField("selectedSourceItems", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
            field.SetValue(component.Instance, new[] { 1, 3 });

            component.Render();
            component.Find("button[title='Move all selected source items to target collection']").Click();

            Assert.NotNull(captured);
            Assert.True(captured.MoveDirectionToTarget);
            Assert.Equal(new[] { 1, 3 }, captured.Items.Select(i => i.Id).OrderBy(i => i).ToArray());
        }

        [Fact]
        public void PickList_Move_Fires_OnMoveAllTargetToSource()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;

            var target = new List<Item>
            {
                new Item { Id = 1, Name = "A" },
                new Item { Id = 2, Name = "B" }
            };

            PickListMoveEventArgs<Item> captured = null;

            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.Target, target);
                parameters.Add(p => p.TextProperty, "Name");
                parameters.Add(p => p.Move, args => captured = args);
            });

            component.Find("button[title='Move all items from target to source collection']").Click();

            Assert.NotNull(captured);
            Assert.False(captured.MoveDirectionToTarget);
            Assert.Equal(new[] { 1, 2 }, captured.Items.Select(i => i.Id).OrderBy(i => i).ToArray());
        }

        [Fact]
        public void PickList_Move_Fires_OnSelectedTargetToSource()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;

            var target = new List<Item>
            {
                new Item { Id = 1, Name = "A" },
                new Item { Id = 2, Name = "B" },
                new Item { Id = 3, Name = "C" }
            };

            PickListMoveEventArgs<Item> captured = null;

            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.Target, target);
                parameters.Add(p => p.TextProperty, "Name");
                parameters.Add(p => p.ValueProperty, "Id");
                parameters.Add(p => p.Multiple, true);
                parameters.Add(p => p.Move, args => captured = args);
            });

            var field = typeof(RadzenPickList<Item>).GetField("selectedTargetItems", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
            field.SetValue(component.Instance, new[] { 2 });

            component.Render();
            component.Find("button[title='Move selected target items to source collection']").Click();

            Assert.NotNull(captured);
            Assert.False(captured.MoveDirectionToTarget);
            Assert.Equal(new[] { 2 }, captured.Items.Select(i => i.Id).ToArray());
        }

        [Fact]
        public void PickList_Move_Fires_AfterSourceAndTargetChanged()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;

            var source = new List<Item>
            {
                new Item { Id = 1, Name = "A" }
            };

            var order = new List<string>();

            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.Source, source);
                parameters.Add(p => p.TextProperty, "Name");
                parameters.Add(p => p.SourceChanged, _ => order.Add(nameof(RadzenPickList<Item>.SourceChanged)));
                parameters.Add(p => p.TargetChanged, _ => order.Add(nameof(RadzenPickList<Item>.TargetChanged)));
                parameters.Add(p => p.Move, _ => order.Add(nameof(RadzenPickList<Item>.Move)));
            });

            component.Find("button[title='Move all items from source to target collection']").Click();

            Assert.Equal(new[]
            {
                nameof(RadzenPickList<Item>.SourceChanged),
                nameof(RadzenPickList<Item>.TargetChanged),
                nameof(RadzenPickList<Item>.Move)
            }, order.ToArray());
        }

        [Fact]
        public void PickList_DoesNotRender_EmptyText_WhenDataExists()
        {
            using var ctx = new TestContext();
            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
            var data = new List<Item>
            {
                new Item { Id = 1, Name = "Item 1" }
            };

            var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
            {
                parameters.Add(p => p.Source, data);
                parameters.Add(p => p.TextProperty, "Name");
            });

            // Source list has data, so its listbox shouldn't show empty message
            var sourceWrapper = component.Find(".rz-picklist-source-wrapper");
            Assert.DoesNotContain("rz-listbox-empty-message", sourceWrapper.InnerHtml);
        }
    }
}

