#nullable enable
using UnitTests;
using Xunit;

namespace UnitTests_Parallelizable.ViewTests;

[Trait ("Category", "View.Scheme")]
public class SchemeTests : FakeDriverBase
{

    [Fact]
    public void GetScheme_Default_ReturnsBaseScheme ()
    {
        var view = new View ();
        var baseScheme = SchemeManager.GetHardCodedSchemes ()? ["Base"];

        Assert.Equal (baseScheme, view.GetScheme ());
        view.Dispose ();
    }

    [Fact]
    public void SetScheme_Explicitly_SetsSchemeCorrectly ()
    {
        var view = new View ();
        var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];

        view.SetScheme (dialogScheme);

        Assert.True (view.HasScheme);
        Assert.Equal (dialogScheme, view.GetScheme ());
        view.Dispose ();
    }

    [Fact]
    public void GetScheme_InheritsFromSuperView_WhenNotExplicitlySet ()
    {
        var superView = new View ();
        var subView = new View ();

        superView.Add (subView);

        var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
        superView.SetScheme (dialogScheme);

        Assert.Equal (dialogScheme, subView.GetScheme ());
        Assert.False (subView.HasScheme);

        subView.Dispose ();
        superView.Dispose ();
    }

    [Fact]
    public void SetSchemeName_OverridesInheritedScheme ()
    {
        var view = new View ();
        view.SchemeName = "Dialog";

        var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
        Assert.Equal (dialogScheme, view.GetScheme ());
        view.Dispose ();
    }

    [Fact]
    public void GetAttribute_ReturnsCorrectAttribute_Via_Mock ()
    {
        var view = new View { SchemeName = "Base" };
        view.Driver = CreateFakeDriver ();
        view.Driver.SetAttribute (new Attribute (Color.Red, Color.Green));

        // Act
        var attribute = view.GetCurrentAttribute ();

        // Assert
        Assert.Equal (new Attribute (Color.Red, Color.Green), attribute);
    }

    [Fact]
    public void GetAttributeForRole_ReturnsCorrectAttribute ()
    {
        var view = new View { SchemeName = "Base" };

        Assert.Equal (view.GetScheme ().Normal, view.GetAttributeForRole (VisualRole.Normal));
        Assert.Equal (view.GetScheme ().HotNormal, view.GetAttributeForRole (VisualRole.HotNormal));
        Assert.Equal (view.GetScheme ().Focus, view.GetAttributeForRole (VisualRole.Focus));
        Assert.Equal (view.GetScheme ().HotFocus, view.GetAttributeForRole (VisualRole.HotFocus));
        Assert.Equal (view.GetScheme ().Disabled, view.GetAttributeForRole (VisualRole.Disabled));

        view.Dispose ();
    }

    [Fact]
    public void GetAttributeForRole_DisabledView_ReturnsCorrectAttribute ()
    {
        var view = new View { SchemeName = "Base" };

        view.Enabled = false;
        Assert.Equal (view.GetScheme ().Disabled, view.GetAttributeForRole (VisualRole.Normal));
        Assert.Equal (view.GetScheme ().Disabled, view.GetAttributeForRole (VisualRole.HotNormal));

        view.Dispose ();
    }

    [Fact]
    public void SetAttributeForRole_SetsCorrectAttribute ()
    {
        var view = new View { SchemeName = "Base" };
        view.Driver = CreateFakeDriver ();
        view.Driver.SetAttribute (new Attribute (Color.Red, Color.Green));

        var previousAttribute = view.SetAttributeForRole (VisualRole.Focus);
        Assert.Equal (view.GetScheme ().Focus, view.GetCurrentAttribute ());
        Assert.NotEqual (previousAttribute, view.GetCurrentAttribute ());

        view.Dispose ();
    }

    [Fact]
    public void OnGettingScheme_Override_StopsDefaultBehavior ()
    {
        var view = new CustomView ();
        var customScheme = SchemeManager.GetHardCodedSchemes ()? ["Error"];

        Assert.Equal (customScheme, view.GetScheme ());
        view.Dispose ();
    }

    [Fact]
    public void OnSettingScheme_Override_PreventsSettingScheme ()
    {
        var view = new CustomView ();
        var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];

        view.SetScheme (dialogScheme);

        Assert.NotEqual (dialogScheme, view.GetScheme ());
        view.Dispose ();
    }

    [Fact]
    public void GettingScheme_Event_CanOverrideScheme ()
    {
        var view = new View ();
        var customScheme = SchemeManager.GetHardCodedSchemes ()? ["Error"]! with { Normal = Attribute.Default };

        Assert.NotEqual (Attribute.Default, view.GetScheme ().Normal);

        view.GettingScheme += (sender, args) =>
                              {
                                  args.Result = customScheme;
                                  args.Handled = true;
                              };

        Assert.Equal (customScheme, view.GetScheme ());
        Assert.Equal (Attribute.Default, view.GetScheme ().Normal);
        view.Dispose ();
    }

    [Fact]
    public void SettingScheme_Event_CanCancelSchemeChange ()
    {
        var view = new View ();
        var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];

        view.SchemeChanging += (sender, args) => args.Handled = true;

        view.SetScheme (dialogScheme);

        Assert.NotEqual (dialogScheme, view.GetScheme ());
        view.Dispose ();
    }

    [Fact]
    public void GetAttributeForRole_Event_CanOverrideAttribute ()
    {
        var view = new View { SchemeName = "Base" };
        var customAttribute = new Attribute (Color.BrightRed, Color.BrightYellow);

        view.GettingAttributeForRole += (sender, args) =>
                                        {
                                            if (args.Role == VisualRole.Focus)
                                            {
                                                args.Result = customAttribute;
                                                args.Handled = true;
                                            }
                                        };

        Assert.Equal (customAttribute, view.GetAttributeForRole (VisualRole.Focus));
        view.Dispose ();
    }

    [Fact]
    public void GetHardCodedSchemes_ReturnsExpectedSchemes ()
    {
        var schemes = Scheme.GetHardCodedSchemes ();

        Assert.NotNull (schemes);
        Assert.Contains ("Base", schemes.Keys);
        Assert.Contains ("Dialog", schemes.Keys);
        Assert.Contains ("Error", schemes.Keys);
        Assert.Contains ("Menu", schemes.Keys);
        Assert.Contains ("Toplevel", schemes.Keys);
    }


    [Fact]
    public void SchemeName_OverridesSuperViewScheme ()
    {
        var superView = new View ();
        var subView = new View ();

        superView.Add (subView);

        subView.SchemeName = "Error";

        var errorScheme = SchemeManager.GetHardCodedSchemes ()? ["Error"];
        Assert.Equal (errorScheme, subView.GetScheme ());

        subView.Dispose ();
        superView.Dispose ();
    }

    [Fact]
    public void Scheme_DefaultsToBase_WhenNotSet ()
    {
        var view = new View ();
        var baseScheme = SchemeManager.GetHardCodedSchemes ()? ["Base"];

        Assert.Equal (baseScheme, view.GetScheme ());
        view.Dispose ();
    }

    [Fact]
    public void Scheme_HandlesNullSuperViewGracefully ()
    {
        var view = new View ();
        view.SchemeName = "Dialog";

        var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
        Assert.Equal (dialogScheme, view.GetScheme ());

        view.Dispose ();
    }

    private class CustomView : View
    {
        protected override bool OnGettingScheme (out Scheme? scheme)
        {
            scheme = SchemeManager.GetHardCodedSchemes ()? ["Error"];

            return true;
        }

        protected override bool OnSettingScheme (ValueChangingEventArgs<Scheme?> args)
        {
            return true; // Prevent setting the scheme
        }
    }

    [Fact]
    public void GetAttributeForRole_SubView_DefersToSuperView_WhenNoExplicitScheme ()
    {
        var parentView = new View { SchemeName = "Base" };
        var childView = new View ();
        parentView.Add (childView);

        // Parent customizes attribute resolution
        var customAttribute = new Attribute (Color.BrightMagenta, Color.BrightGreen);
        parentView.GettingAttributeForRole += (sender, args) =>
        {
            if (args.Role == VisualRole.Normal)
            {
                args.Result = customAttribute;
                args.Handled = true;
            }
        };

        // Child without explicit scheme should get customized attribute from parent
        Assert.Equal (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));

        childView.Dispose ();
        parentView.Dispose ();
    }

    [Fact]
    public void GetAttributeForRole_SubView_UsesOwnScheme_WhenExplicitlySet ()
    {
        var parentView = new View { SchemeName = "Base" };
        var childView = new View ();
        parentView.Add (childView);

        // Set explicit scheme on child
        var childScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
        childView.SetScheme (childScheme);

        // Parent customizes attribute resolution
        var customAttribute = new Attribute (Color.BrightMagenta, Color.BrightGreen);
        parentView.GettingAttributeForRole += (sender, args) =>
        {
            if (args.Role == VisualRole.Normal)
            {
                args.Result = customAttribute;
                args.Handled = true;
            }
        };

        // Child with explicit scheme should NOT get customized attribute from parent
        Assert.NotEqual (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));
        Assert.Equal (childScheme!.Normal, childView.GetAttributeForRole (VisualRole.Normal));

        childView.Dispose ();
        parentView.Dispose ();
    }

    [Fact]
    public void GetAttributeForRole_Adornment_UsesParentScheme ()
    {
        // Border (an Adornment) doesn't have a SuperView but should use its Parent's scheme
        var view = new View { SchemeName = "Dialog" };
        var border = view.Border!;

        Assert.NotNull (border);
        Assert.Null (border.SuperView); // Adornments don't have SuperView
        Assert.NotNull (border.Parent);

        var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];

        // Border should use its Parent's scheme, not Base
        Assert.Equal (dialogScheme!.Normal, border.GetAttributeForRole (VisualRole.Normal));

        view.Dispose ();
    }

    [Fact]
    public void GetAttributeForRole_SubView_UsesSchemeName_WhenSet ()
    {
        var parentView = new View { SchemeName = "Base" };
        var childView = new View ();
        parentView.Add (childView);

        // Set SchemeName on child (not explicit scheme)
        childView.SchemeName = "Dialog";

        // Parent customizes attribute resolution
        var customAttribute = new Attribute (Color.BrightMagenta, Color.BrightGreen);
        parentView.GettingAttributeForRole += (sender, args) =>
        {
            if (args.Role == VisualRole.Normal)
            {
                args.Result = customAttribute;
                args.Handled = true;
            }
        };

        // Child with SchemeName should NOT get customized attribute from parent
        // It should use the Dialog scheme instead
        var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
        Assert.NotEqual (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));
        Assert.Equal (dialogScheme!.Normal, childView.GetAttributeForRole (VisualRole.Normal));

        childView.Dispose ();
        parentView.Dispose ();
    }

    [Fact]
    public void GetAttributeForRole_NestedHierarchy_DefersCorrectly ()
    {
        // Test: grandchild without explicit scheme defers through parent to grandparent
        // Would fail without the SuperView deferral fix (commit 154ac15)

        var grandparentView = new View { SchemeName = "Base" };
        var parentView = new View (); // No scheme or SchemeName
        var childView = new View (); // No scheme or SchemeName

        grandparentView.Add (parentView);
        parentView.Add (childView);

        // Grandparent customizes attributes
        var customAttribute = new Attribute (Color.BrightYellow, Color.BrightBlue);
        grandparentView.GettingAttributeForRole += (sender, args) =>
        {
            if (args.Role == VisualRole.Normal)
            {
                args.Result = customAttribute;
                args.Handled = true;
            }
        };

        // Child should get attribute from grandparent through parent
        Assert.Equal (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));

        // Parent should also get attribute from grandparent
        Assert.Equal (customAttribute, parentView.GetAttributeForRole (VisualRole.Normal));

        childView.Dispose ();
        parentView.Dispose ();
        grandparentView.Dispose ();
    }

    [Fact]
    public void GetAttributeForRole_ParentWithSchemeNameBreaksChain ()
    {
        // Test: parent with SchemeName stops deferral chain
        // Would fail without the SchemeName check (commit 866e002)

        var grandparentView = new View { SchemeName = "Base" };
        var parentView = new View { SchemeName = "Dialog" }; // Sets SchemeName
        var childView = new View (); // No scheme or SchemeName

        grandparentView.Add (parentView);
        parentView.Add (childView);

        // Grandparent customizes attributes
        var customAttribute = new Attribute (Color.BrightYellow, Color.BrightBlue);
        grandparentView.GettingAttributeForRole += (sender, args) =>
        {
            if (args.Role == VisualRole.Normal)
            {
                args.Result = customAttribute;
                args.Handled = true;
            }
        };

        // Parent should NOT get grandparent's customization (it has SchemeName)
        var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
        Assert.NotEqual (customAttribute, parentView.GetAttributeForRole (VisualRole.Normal));
        Assert.Equal (dialogScheme!.Normal, parentView.GetAttributeForRole (VisualRole.Normal));

        // Child should get parent's Dialog scheme (defers to parent, parent uses Dialog scheme)
        Assert.Equal (dialogScheme!.Normal, childView.GetAttributeForRole (VisualRole.Normal));

        childView.Dispose ();
        parentView.Dispose ();
        grandparentView.Dispose ();
    }

    [Fact]
    public void GetAttributeForRole_OnGettingAttributeForRole_TakesPrecedence ()
    {
        // Test: view's own OnGettingAttributeForRole takes precedence over parent
        // This should work with or without the fix, but validates precedence

        var parentView = new View { SchemeName = "Base" };
        var childView = new TestViewWithAttributeOverride ();
        parentView.Add (childView);

        // Parent customizes attributes
        var parentAttribute = new Attribute (Color.BrightYellow, Color.BrightBlue);
        parentView.GettingAttributeForRole += (sender, args) =>
        {
            if (args.Role == VisualRole.Normal)
            {
                args.Result = parentAttribute;
                args.Handled = true;
            }
        };

        // Child's own override should take precedence
        var childOverrideAttribute = new Attribute (Color.BrightRed, Color.BrightCyan);
        childView.OverrideAttribute = childOverrideAttribute;

        Assert.Equal (childOverrideAttribute, childView.GetAttributeForRole (VisualRole.Normal));

        childView.Dispose ();
        parentView.Dispose ();
    }

    [Fact]
    public void GetAttributeForRole_MultipleRoles_DeferCorrectly ()
    {
        // Test: multiple VisualRoles all defer correctly
        // Would fail without the SuperView deferral fix for any role

        var parentView = new View { SchemeName = "Base" };
        var childView = new View ();
        parentView.Add (childView);

        var normalAttr = new Attribute (Color.Red, Color.Blue);
        var focusAttr = new Attribute (Color.Green, Color.Yellow);
        var hotNormalAttr = new Attribute (Color.Magenta, Color.Cyan);

        parentView.GettingAttributeForRole += (sender, args) =>
        {
            switch (args.Role)
            {
                case VisualRole.Normal:
                    args.Result = normalAttr;
                    args.Handled = true;
                    break;
                case VisualRole.Focus:
                    args.Result = focusAttr;
                    args.Handled = true;
                    break;
                case VisualRole.HotNormal:
                    args.Result = hotNormalAttr;
                    args.Handled = true;
                    break;
            }
        };

        // All roles should defer to parent
        Assert.Equal (normalAttr, childView.GetAttributeForRole (VisualRole.Normal));
        Assert.Equal (focusAttr, childView.GetAttributeForRole (VisualRole.Focus));
        Assert.Equal (hotNormalAttr, childView.GetAttributeForRole (VisualRole.HotNormal));

        childView.Dispose ();
        parentView.Dispose ();
    }

    private class TestViewWithAttributeOverride : View
    {
        public Attribute? OverrideAttribute { get; set; }

        protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attribute currentAttribute)
        {
            if (OverrideAttribute.HasValue && role == VisualRole.Normal)
            {
                currentAttribute = OverrideAttribute.Value;
                return true;
            }
            return base.OnGettingAttributeForRole (role, ref currentAttribute);
        }
    }

}