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

using System.Formats.Nrbf;
using System.Reflection.Metadata;
using System.Runtime.Serialization;

namespace System.Tests;

public class TypeExtensionsTests
{
    public static TheoryData<Type, TypeName, int, bool> Matches_Type_Data() => new()
    {
        { typeof(int), TypeName.Parse(typeof(int).AssemblyQualifiedName), (int)TypeNameComparison.All, true },
        { typeof(int), TypeName.Parse(typeof(int).AssemblyQualifiedName), (int)TypeNameComparison.AllButAssemblyVersion, true },
        { typeof(int), TypeName.Parse(typeof(int).AssemblyQualifiedName), (int)TypeNameComparison.TypeFullName, true },
        { typeof(int), TypeName.Parse(typeof(int).AssemblyQualifiedName), (int)TypeNameComparison.AssemblyVersion, true },
        { typeof(int), TypeName.Parse(typeof(int).AssemblyQualifiedName), (int)TypeNameComparison.AssemblyCultureName, true },
        { typeof(int), TypeName.Parse(typeof(int).AssemblyQualifiedName), (int)TypeNameComparison.AssemblyName, true },
        { typeof(int), TypeName.Parse(typeof(int).AssemblyQualifiedName), (int)TypeNameComparison.AssemblyPublicKeyToken, true },
        {
            typeof(int), TypeName.Parse($"System.Int32, {Assemblies.Mscorlib}"), (int)TypeNameComparison.AllButAssemblyVersion,
#if NET
            false
#else
            true
#endif
        },
        { typeof(int), TypeName.Parse($"System.Int32, {Assemblies.Mscorlib}"), (int)TypeNameComparison.TypeFullName, true },
        { typeof(int), TypeName.Parse($"Int32, {Assemblies.Mscorlib}"), (int)TypeNameComparison.TypeFullName, false },
        { typeof(int?), TypeName.Parse(typeof(int).AssemblyQualifiedName), (int)TypeNameComparison.AllButAssemblyVersion, false },
        { typeof(int?), TypeName.Parse(typeof(int?).AssemblyQualifiedName), (int)TypeNameComparison.All, true },
        {
            typeof(int?[]), TypeName.Parse($"System.Nullable`1[[System.Int32, {Assemblies.Mscorlib}]][], {Assemblies.Mscorlib}"), (int)TypeNameComparison.AllButAssemblyVersion,
#if NET
            false
#else
            true
#endif
        },
        { typeof(DayOfWeek), TypeName.Parse($"System.Nullable`1[[System.DayOfWeek, {Assemblies.Mscorlib}]], {Assemblies.Mscorlib}"), (int)TypeNameComparison.AllButAssemblyVersion, false },
        // Culture is incorrect.
        { typeof(int), TypeName.Parse(typeof(int).AssemblyQualifiedName!.Replace("neutral", "en-US")), (int)TypeNameComparison.AssemblyCultureName, false },
        // Namespace name is incorrect.
        { typeof(int), TypeName.Parse("Int32"), (int)TypeNameComparison.TypeFullName, false },
        // Namespace name is cased differently.
        { typeof(int), TypeName.Parse("SYSTEM.Int32"), (int)TypeNameComparison.TypeFullName, false },
        // Assembly information is missing.
        { typeof(int), TypeName.Parse("System.Int32"), (int)TypeNameComparison.AllButAssemblyVersion, false },
        // Assembly name is cased differently.
        { typeof(int), TypeName.Parse($"System.Int32, {typeof(int).Assembly.FullName!.ToUpperInvariant()}"), (int)TypeNameComparison.AssemblyName, false },
    };

    [Theory]
    [MemberData(nameof(Matches_Type_Data))]
    public void Matches_Type(Type type, TypeName typeName, int comparison, bool matches)
    {
        type.Matches(typeName, (TypeNameComparison)comparison).Should().Be(matches);
        TypeName.Parse(type.AssemblyQualifiedName).Matches(typeName, (TypeNameComparison)comparison).Should().Be(matches);
    }

    [Fact]
    public void Matches_Type_InvalidKey()
    {
        // We assert here as we're not expecting to see this exception in normal usage.
        using NoAssertContext noAsserts = new();

        // On .NET Framework:
        // System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
        bool success = typeof(int).Matches(
            TypeName.Parse(typeof(int).AssemblyQualifiedName!.Replace(
#if NET
                "7cec85d7bea7798e",
#else
                "b77a5c561934e089",
#endif
                "7cec00000ea7798e")),
            TypeNameComparison.AllButAssemblyVersion);
        success.Should().BeFalse();
    }

    public static TheoryData<TypeName, TypeName, bool> Matches_Type_Mscorlib_Data() => new()
    {
        { TypeName.Parse($"System.Int32, {Assemblies.Mscorlib}"), TypeName.Parse($"System.Int32,  {Assemblies.Mscorlib}"), true },
        { TypeName.Parse($"System.Int32, {Assemblies.Mscorlib}"), TypeName.Parse($"System.String, {Assemblies.Mscorlib}"), false },
        { TypeName.Parse($"System.Collections.Generic.Dictionary`2[[System.String, {Assemblies.Mscorlib}],[System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]], {Assemblies.Mscorlib}"), TypeName.Parse($"System.Collections.Generic.Dictionary`2[[System.String, {Assemblies.Mscorlib}],[System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]], {Assemblies.Mscorlib}"), true },
        { TypeName.Parse($"System.Collections.Generic.Dictionary`2[[System.String, {Assemblies.Mscorlib}],[System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]], {Assemblies.Mscorlib}"), TypeName.Parse($"System.Collections.Generic.Dictionary`2[[System.String, {Assemblies.Mscorlib}],[System.Drawing.Bitmap, System.Drawing, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]], {Assemblies.Mscorlib}"), false },
        { TypeName.Parse($"System.Collections.Generic.Dictionary`2[[System.String, {Assemblies.Mscorlib}],[System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]], {Assemblies.Mscorlib}"), TypeName.Parse($"System.Collections.Generic.Dictionary`2[[System.String, {Assemblies.Mscorlib}],[System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]], mscorlib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"), false },
        { TypeName.Parse($"System.Drawing.Bitmap[], System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), TypeName.Parse($"System.Drawing.Bitmap[], System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), true },
        { TypeName.Parse($"System.Drawing.Bitmap[], System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), TypeName.Parse($"System.Drawing.Bitmap[], System.Drawing, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), false },
        { TypeName.Parse($"System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), TypeName.Parse($"System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), true },
        { TypeName.Parse($"System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), TypeName.Parse($"System.Drawing.Bitmap, System.Drawing, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), false },
    };

    [Theory]
    [MemberData(nameof(Matches_Type_Mscorlib_Data))]
    public void Matches_Type_Mscorlib(TypeName x, TypeName y, bool matches) =>
        // Match TypeName objects including assembly version.
        x.Matches(y, TypeNameComparison.All).Should().Be(matches);

    [Serializable]
    private class MyGenericClass<T>
    {
    }

    [Serializable]
    private class MyClass
    {
    }

    public static TheoryData<object, string[]> BinaryFormatter_BinderTypes_Data => new()
    {
        { 42, ["System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"] },
        { new List<int>(), ["System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"] },
        {
            new List<MyClass>(),
            [
                "System.Collections.Generic.List`1[[System.Tests.TypeExtensionsTests+MyClass, System.Private.Windows.Core.Tests, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
                "System.Tests.TypeExtensionsTests+MyClass, System.Private.Windows.Core.Tests, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
            ]
        }
    };

    [Theory]
    [MemberData(nameof(BinaryFormatter_BinderTypes_Data))]
    public void BinaryFormatter_BinderTypes(object value, string[] expected)
    {
        using MemoryStream stream = new();
        stream.WriteBinaryFormat(value);
        stream.Position = 0;

        List<string> bindings = [];

        _ = BinarySerialization.DeserializeFromStream(
            stream,
            binder: new BindToTypeBinder(
                (assembly, type) =>
                {
                    bindings.Add($"{type}, {assembly}");
                    return null;
                }));

        bindings.Should().BeEquivalentTo(expected);
    }

    [Theory]
    [MemberData(nameof(BinaryFormatter_BinderTypes_Data))]
    public void BinaryFormatter_NrbfDecoder_Types(object value, string[] expected)
    {
        SerializationRecord record = BinarySerialization.SerializeAndDecode(value, out _);
        record.TypeName.AssemblyQualifiedName.Should().Be(expected[0]);
    }

    public class BindToTypeBinder : SerializationBinder
    {
        private readonly Func<string, string, Type?> _bindToType;
        public BindToTypeBinder(Func<string, string, Type?> bindToType) => _bindToType = bindToType;
        public override Type BindToType(string assemblyName, string typeName) => _bindToType(assemblyName, typeName)!;
        public override void BindToName(Type serializedType, out string? assemblyName, out string typeName) => throw new NotImplementedException();
    }
}
