// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms.Design.Behavior;
using Microsoft.CodeAnalysis;
using Moq;

namespace System.Windows.Forms.Design.Tests;

public sealed class ToolStripEditorManagerTests : IDisposable
{
    private readonly BehaviorService _behaviorService;
    private readonly Mock<IDesignerHost> _mockDesignerHost;
    private readonly Mock<IComponent> _mockComponent;
    private readonly Mock<ISite> _mockSite;
    private readonly Mock<IServiceProvider> _mockServiceProvider;
    private readonly ToolStripEditorManager _editorManager;
    private readonly Control _editorControl;
    private readonly Rectangle _bounds;
    private readonly object _toolStripEditorControl;
    private readonly Type _toolStripEditorControlType;
    private readonly ConstructorInfo _constructor;
    private readonly ToolStripItem _toolStripItem;
    private readonly Mock<DesignerFrame> _mockDesignerFrame;

    public ToolStripEditorManagerTests()
    {
        _mockServiceProvider = new();
        _mockSite = new();
        _mockDesignerHost = new();
        _mockComponent = new();
        _mockDesignerFrame = new(_mockSite.Object) { CallBase = true };
        _behaviorService = new(_mockServiceProvider.Object, _mockDesignerFrame.Object);
        _toolStripItem = new ToolStripButton("Sample Button");

        _mockComponent.Setup(c => c.Site).Returns(_mockSite.Object);
        _mockSite.Setup(s => s.GetService(typeof(BehaviorService))).Returns(_behaviorService);
        _mockSite.Setup(s => s.GetService(typeof(IDesignerHost))).Returns(_mockDesignerHost.Object);
        _editorManager = new(_mockComponent.Object);

        _editorControl = new();
        _bounds = new(10, 20, 100, 200);

        _toolStripEditorControlType = typeof(ToolStripEditorManager).GetNestedType("ToolStripEditorControl", BindingFlags.NonPublic)
            ?? throw new InvalidOperationException("ToolStripEditorControl type not found.");

        _constructor = _toolStripEditorControlType?.GetConstructor([typeof(Control), typeof(Rectangle)])
            ?? throw new InvalidOperationException("Constructor for ToolStripEditorControl not found.");

        _toolStripEditorControl = _constructor.Invoke([_editorControl, _bounds]);
    }

    public void Dispose()
    {
        _behaviorService.Dispose();
        _editorControl.Dispose();
        _toolStripItem.Dispose();
    }

    [Fact]
    public void Constructor_InitializesBehaviorServiceAndDesignerHost()
    {
        _editorManager.Should().BeOfType<ToolStripEditorManager>();

        BehaviorService? behaviorService = _editorManager.TestAccessor.Dynamic._behaviorService;
        behaviorService.Should().Be(_behaviorService);

        IDesignerHost? designerHost = _editorManager.TestAccessor.Dynamic._designerHost;
        designerHost.Should().Be(_mockDesignerHost.Object);
    }

    [WinFormsFact]
    public void ActivateEditor_ShouldNotAddNewEditor_WhenItemIsNull()
    {
        Action action = () => _editorManager.ActivateEditor(null);
        action.Should().NotThrow();
    }

    [WinFormsFact]
    public void ActivateEditor_ShouldReturn_WhenItemIsSameAsCurrentItem()
    {
        _editorManager.TestAccessor.Dynamic._behaviorService = _behaviorService;
        _editorManager.TestAccessor.Dynamic._currentItem = _toolStripItem;

        Action action = () => _editorManager.ActivateEditor(_toolStripItem);
        action.Should().NotThrow();

        ToolStripItem currentItem = _editorManager.TestAccessor.Dynamic._currentItem;
        currentItem.Should().Be(_toolStripItem);
    }

    [WinFormsFact]
    public void ActivateEditor_ShouldDeactivateCurrentEditor_WhenEditorIsNotNull()
    {
        _editorManager.TestAccessor.Dynamic._behaviorService = _behaviorService;
        _editorManager.TestAccessor.Dynamic._editor = _toolStripEditorControl;
        _editorManager.TestAccessor.Dynamic._itemDesigner = new Mock<ToolStripItemDesigner>().Object;
        _editorManager.TestAccessor.Dynamic._currentItem = new ToolStripButton();

        _editorManager.ActivateEditor(null);

        _behaviorService.AdornerWindowControl.Controls.Cast<Control>().Should().NotContain((Control)_toolStripEditorControl);

        ToolStripTemplateNode editorUI = _editorManager.TestAccessor.Dynamic._editorUI;
        editorUI.Should().BeNull();

        object? editor = _editorManager.TestAccessor.Dynamic._editor;
        editor.Should().BeNull();

        ToolStripItem currentItem = _editorManager.TestAccessor.Dynamic._currentItem;
        currentItem.Should().BeNull();

        bool? isEditorActive = _editorManager.TestAccessor.Dynamic._itemDesigner.IsEditorActive;
        isEditorActive.Should().BeFalse();
    }

    [WinFormsFact]
    public void ActivateEditor_ShouldAddNewEditor_WhenItemIsNotNull()
    {
        Mock<ToolStrip> mockToolStrip = new();
        Mock<IComponent> mockComponent = mockToolStrip.As<IComponent>();
        Mock<ISite> mockSite = new();
        Mock<IDesignerHost> mockDesignerHost = new();
        Mock<DesignSurface> mockDesignSurface = new();
        mockComponent.Setup(c => c.Site).Returns(mockSite.Object);
        mockSite.Setup(s => s.GetService(typeof(IDesignerHost))).Returns(mockDesignerHost.Object);
        mockSite.Setup(s => s.GetService(typeof(DesignSurface))).Returns(mockDesignSurface.Object);
        Mock<Control> mockRootComponent = new();
        mockDesignerHost.Setup(dh => dh.RootComponent).Returns(mockRootComponent.Object);

        Mock<ToolStripItemDesigner> mockToolStripItemDesigner = new();
        Mock<ToolStripTemplateNode> mockToolStripTemplateNode = new(MockBehavior.Default, mockComponent.Object, "SampleText");
        FieldInfo? centerLabelField = typeof(ToolStripTemplateNode).GetField("_centerLabel", BindingFlags.NonPublic | BindingFlags.Instance);
        centerLabelField?.SetValue(mockToolStripTemplateNode.Object, new ToolStripLabel("Test"));
        mockToolStripItemDesigner.Setup(d => d.Editor).Returns(mockToolStripTemplateNode.Object);
        _mockDesignerHost.Setup(dh => dh.GetDesigner(It.IsAny<ToolStripItem>())).Returns(mockToolStripItemDesigner.Object);

        _editorManager.ActivateEditor(_toolStripItem);

        ToolStripItem currentItem = _editorManager.TestAccessor.Dynamic._currentItem;
        currentItem.Should().Be(_toolStripItem);

        ToolStripTemplateNode editorUI = _editorManager.TestAccessor.Dynamic._editorUI;
        editorUI.Should().Be(mockToolStripTemplateNode.Object);

        ToolStripItemDesigner itemDesigner = _editorManager.TestAccessor.Dynamic._itemDesigner;
        itemDesigner.Should().Be(mockToolStripItemDesigner.Object);

        _mockDesignerHost.Verify(dh => dh.GetDesigner(_toolStripItem), Times.Once);
        mockToolStripItemDesigner.Object.IsEditorActive.Should().BeTrue();
    }

    [Fact]
    public void CloseManager_ShouldNotThrowException()
    {
        Action action = ToolStripEditorManager.CloseManager;
        action.Should().NotThrow();
    }

    [WinFormsFact]
    public void OnEditorResize_ShouldInvalidateAndUpdateBounds()
    {
        _editorManager.TestAccessor.Dynamic._editor = _toolStripEditorControl;
        _editorManager.TestAccessor.Dynamic.OnEditorResize(_editorManager, EventArgs.Empty);

        Rectangle _editorManagerBounds = _editorManager.TestAccessor.Dynamic._lastKnownEditorBounds;
        _editorManagerBounds.X.Should().Be(_bounds.X);
        _editorManagerBounds.Y.Should().Be(_bounds.Y);
    }

    [Fact]
    public void ToolStripEditorControl_Constructor_InitializesProperties()
    {
        VerifyProperty("Bounds1", _toolStripEditorControl, _bounds);
        VerifyProperty(nameof(Location), _toolStripEditorControl, new Point(_bounds.X, _bounds.Y));
        VerifyProperty(nameof(Text), _toolStripEditorControl, "InSituEditorWrapper");
        VerifyProperty(nameof(Size), _toolStripEditorControl, new Size(_editorControl.Size.Width, _editorControl.Size.Height));
        _toolStripEditorControlType?.GetProperty("Controls")?.GetValue(_toolStripEditorControl).Should().BeOfType<Control.ControlCollection>();
    }

    private void VerifyProperty<T>(string propertyName, object targetObject, T expectedValue)
    {
        PropertyInfo? propertyInfo = targetObject.TestAccessor.Dynamic.GetType().GetProperty(propertyName);
        object? propertyValue = propertyInfo?.GetValue(targetObject);
        propertyValue?.Should().Be(expectedValue);
    }

    [WinFormsFact]
    public void ToolStripEditorControl_Bounds1_Setter_UpdatesBounds()
    {
        Rectangle newBounds = new(30, 40, 150, 250);
        PropertyInfo? boundsProperty = _toolStripEditorControlType?.GetProperty("Bounds1");

        boundsProperty.Should().NotBeNull();
        if (boundsProperty is not null)
        {
            boundsProperty.SetValue(_toolStripEditorControl, newBounds);
            Rectangle? actualBounds = boundsProperty.GetValue(_toolStripEditorControl) as Rectangle?;
            actualBounds.Should().Be(newBounds);
        }
    }
}
