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

using System;
using System.Linq.Expressions;
using Xunit;

namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
    public class CachedExpressionCompilerTest
    {
        [Fact]
        public void Process_IdentityExpression()
        {
            // Arrange
            var model = new TestModel();
            var expression = GetTestModelExpression(m => m);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Same(model, result);
        }

        [Fact]
        public void Process_CachesIdentityExpression()
        {
            // Arrange
            var expression1 = GetTestModelExpression(m => m);
            var expression2 = GetTestModelExpression(m => m);

            // Act
            var func1 = CachedExpressionCompiler.Process(expression1);
            var func2 = CachedExpressionCompiler.Process(expression2);

            // Assert
            Assert.NotNull(func1);
            Assert.Same(func1, func2);
        }

        [Fact]
        public void Process_ConstLookup()
        {
            // Arrange
            var model = new TestModel();
            var differentModel = new DifferentModel();
            var expression = GetTestModelExpression(m => differentModel);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Same(differentModel, result);
        }

        [Fact]
        public void Process_ConstLookup_ReturningNull()
        {
            // Arrange
            var model = new TestModel();
            var expression = GetTestModelExpression(m => (DifferentModel)null);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Null(result);
        }

        [Fact]
        public void Process_ConstLookup_WithNullModel()
        {
            // Arrange
            var differentModel = new DifferentModel();
            var expression = GetTestModelExpression(m => differentModel);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(null);
            Assert.Same(differentModel, result);
        }

        [Fact]
        public void Process_ConstLookup_UsingCachedValue()
        {
            // Arrange
            var model = new TestModel();
            var differentModel = new DifferentModel();
            var expression1 = GetTestModelExpression(m => differentModel);
            var expression2 = GetTestModelExpression(m => differentModel);

            // Act - 1
            var func1 = CachedExpressionCompiler.Process(expression1);

            // Assert - 1
            var result1 = func1(null);
            Assert.Same(differentModel, result1);

            // Act - 2
            var func2 = CachedExpressionCompiler.Process(expression2);

            // Assert - 2
            var result2 = func1(null);
            Assert.Same(differentModel, result2);
        }

        [Fact]
        public void Process_ConstLookup_WhenCapturedLocalChanges()
        {
            // Arrange
            var model = new TestModel();
            var differentModel = new DifferentModel();
            var expression = GetTestModelExpression(m => differentModel);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert - 1
            var result1 = func(null);
            Assert.Same(differentModel, result1);

            // Act - 2
            differentModel = new DifferentModel();

            // Assert - 2
            var result2 = func(null);
            Assert.NotSame(differentModel, result1);
            Assert.Same(differentModel, result2);
        }

        [Fact]
        public void Process_ConstLookup_WithPrimitiveConstant()
        {
            // Arrange
            var model = new TestModel();
            var expression = GetTestModelExpression(m => 10);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(10, result);
        }

        [Fact]
        public void Process_StaticFieldAccess()
        {
            // Arrange
            var model = new TestModel();
            var expression = GetTestModelExpression(m => TestModel.StaticField);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal("StaticValue", result);
        }

        [Fact]
        public void Process_CachesStaticFieldAccess()
        {
            // Arrange
            var expression1 = GetTestModelExpression(m => TestModel.StaticField);
            var expression2 = GetTestModelExpression(m => TestModel.StaticField);

            // Act
            var func1 = CachedExpressionCompiler.Process(expression1);
            var func2 = CachedExpressionCompiler.Process(expression2);

            // Assert
            Assert.NotNull(func1);
            Assert.Same(func1, func2);
        }

        [Fact]
        public void Process_StaticPropertyAccess()
        {
            // Arrange
            var expected = "TestValue";
            TestModel.StaticProperty = expected;
            var model = new TestModel();
            var expression = GetTestModelExpression(m => TestModel.StaticProperty);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(expected, result);
        }

        [Fact]
        public void Process_CachesStaticPropertyAccess()
        {
            // Arrange
            var expression1 = GetTestModelExpression(m => TestModel.StaticProperty);
            var expression2 = GetTestModelExpression(m => TestModel.StaticProperty);

            // Act
            var func1 = CachedExpressionCompiler.Process(expression1);
            var func2 = CachedExpressionCompiler.Process(expression2);

            // Assert
            Assert.NotNull(func1);
            Assert.Same(func1, func2);
        }

        [Fact]
        public void Process_StaticPropertyAccess_WithNullModel()
        {
            // Arrange
            var expected = "TestValue";
            TestModel.StaticProperty = expected;
            var expression = GetTestModelExpression(m => TestModel.StaticProperty);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(null);
            Assert.Equal(expected, result);
        }

        [Fact]
        public void Process_ConstFieldLookup()
        {
            // Arrange
            var model = new TestModel();
            var expression = GetTestModelExpression(m => DifferentModel.Constant);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(10, result);
        }

        [Fact]
        public void Process_ConstFieldLookup_WthNullModel()
        {
            // Arrange
            var expression = GetTestModelExpression(m => DifferentModel.Constant);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(null);
            Assert.Equal(10, result);
        }

        [Fact]
        public void Process_SimpleMemberAccess()
        {
            // Arrange
            var model = new TestModel { Name = "Test" };
            var expression = GetTestModelExpression(m => m.Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal("Test", result);
        }

        [Fact]
        public void Process_CachesSimpleMemberAccess()
        {
            // Arrange
            var expression1 = GetTestModelExpression(m => m.Name);
            var expression2 = GetTestModelExpression(m => m.Name);

            // Act
            var func1 = CachedExpressionCompiler.Process(expression1);
            var func2 = CachedExpressionCompiler.Process(expression2);

            // Assert
            Assert.NotNull(func1);
            Assert.Same(func1, func2);
        }

        [Fact]
        public void Process_SimpleMemberAccess_ToPrimitive()
        {
            // Arrange
            var model = new TestModel { Age = 12 };
            var expression = GetTestModelExpression(m => m.Age);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(12, result);
        }

        [Fact]
        public void Process_SimpleMemberAccess_WithNullModel()
        {
            // Arrange
            var model = (TestModel)null;
            var expression = GetTestModelExpression(m => m.Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Null(result);
        }

        [Fact]
        public void Process_SimpleMemberAccess_ToPrimitive_WithNullModel()
        {
            // Arrange
            var model = (TestModel)null;
            var expression = GetTestModelExpression(m => m.Age);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Null(result);
        }

        [Fact]
        public void Process_SimpleMemberAccess_OnTypeWithBadEqualityComparer()
        {
            // Arrange
            var model = new BadEqualityModel { Id = 7 };
            var expression = GetExpression<BadEqualityModel, int>(m => m.Id);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(7, result);
        }

        [Fact]
        public void Process_SimpleMemberAccess_OnTypeWithBadEqualityComparer_WithNullModel()
        {
            // Arrange
            var model = (BadEqualityModel)null;
            var expression = GetExpression<BadEqualityModel, int>(m => m.Id);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Null(result);
        }

        [Fact]
        public void Process_SimpleMemberAccess_OnValueTypeWithBadEqualityComparer()
        {
            // Arrange
            var model = new BadEqualityValueTypeModel { Id = 7 };
            var expression = GetExpression<BadEqualityValueTypeModel, int>(m => m.Id);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(7, result);
        }

        [Fact]
        public void Process_SimpleMemberAccess_OnTypeWithBadEqualityComparer_WithDefaultValue()
        {
            // Arrange
            var model = (BadEqualityValueTypeModel)default;
            var expression = GetExpression<BadEqualityValueTypeModel, int>(m => m.Id);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(model.Id, result);
        }

        [Fact]
        public void Process_SimpleMemberAccess_OnValueType()
        {
            // Arrange
            var model = new DateTime(2000, 1, 1);
            var expression = GetExpression<DateTime, int>(m => m.Year);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(2000, result);
        }

        [Fact]
        public void Process_SimpleMemberAccess_OnValueType_WithDefaultValue()
        {
            // Arrange
            var model = default(DateTime);
            var expression = GetExpression<DateTime, int>(m => m.Year);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(1, result);
        }

        [Fact]
        public void Process_SimpleMemberAccess_OnNullableValueType()
        {
            // Arrange
            var model = new DateTime(2000, 1, 1);
            var nullableModel = (DateTime?)model;
            var expression = GetExpression<DateTime?, DateTime>(m => m.Value);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(nullableModel);
            Assert.Equal(model, result);
        }

        [Fact]
        public void Process_SimpleMemberAccess_OnNullableValueType_WithNullValue()
        {
            // Arrange
            var nullableModel = (DateTime?)null;
            var expression = GetExpression<DateTime?, DateTime>(m => m.Value);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(nullableModel);
            Assert.Null(result);
        }

        [Fact]
        public void Process_ChainedMemberAccess_ToValueType()
        {
            // Arrange
            var dateTime = new DateTime(2000, 1, 1);
            var model = new TestModel { Date = dateTime };
            var expression = GetTestModelExpression(m => m.Date.Year);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(2000, result);
        }

        [Fact]
        public void Process_ChainedMemberAccess_ToValueType_WithNullModel()
        {
            // Arrange
            var model = (TestModel)null;
            var expression = GetTestModelExpression(m => m.Date.Year);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Null(result);
        }

        [Fact]
        public void Process_ChainedMemberAccess_ToReferenceType()
        {
            // Arrange
            var expected = "Test1";
            var model = new TestModel { DifferentModel = new DifferentModel { Name = expected } };
            var expression = GetTestModelExpression(m => m.DifferentModel.Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(expected, result);
        }

        [Fact]
        public void Process_CachesChainedMemberAccess()
        {
            // Arrange
            var expression1 = GetTestModelExpression(m => m.DifferentModel.Name);
            var expression2 = GetTestModelExpression(m => m.DifferentModel.Name);

            // Act
            var func1 = CachedExpressionCompiler.Process(expression1);
            var func2 = CachedExpressionCompiler.Process(expression2);

            // Assert
            Assert.NotNull(func1);
            Assert.Same(func1, func2);
        }

        [Fact]
        public void Process_CachesChainedMemberAccess_ToValueType()
        {
            // Arrange
            var expression1 = GetTestModelExpression(m => m.Date.Year);
            var expression2 = GetTestModelExpression(m => m.Date.Year);

            // Act
            var func1 = CachedExpressionCompiler.Process(expression1);
            var func2 = CachedExpressionCompiler.Process(expression2);

            // Assert
            Assert.NotNull(func1);
            Assert.Same(func1, func2);
        }

        [Fact]
        public void Process_ChainedMemberAccess_LongChain_WithReferenceType()
        {
            // Arrange
            var expected = "TestVal";
            var model = new Chain0Model
            {
                Chain1 = new Chain1Model
                {
                    ValueTypeModel = new ValueType1
                    {
                        TestModel = new TestModel { DifferentModel = new DifferentModel { Name = expected } }
                    }
                }
            };

            var expression = GetExpression<Chain0Model, string>(m => m.Chain1.ValueTypeModel.TestModel.DifferentModel.Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(expected, result);
        }

        [Fact]
        public void Process_ChainedMemberAccess_LongChain_WithNullIntermediary()
        {
            // Arrange
            var model = new Chain0Model
            {
                Chain1 = new Chain1Model
                {
                    ValueTypeModel = new ValueType1 { TestModel = null },
                }
            };

            var expression = GetExpression<Chain0Model, string>(m => m.Chain1.ValueTypeModel.TestModel.DifferentModel.Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Null(result);
        }

        [Fact]
        public void Process_ChainedMemberAccess_LongChain_WithNullValueTypeAccessor()
        {
            // Arrange
            // Chain2 is a value type
            var model = new Chain0Model
            {
                Chain1 = null
            };

            var expression = GetExpression<Chain0Model, string>(m => m.Chain1.ValueTypeModel.TestModel.DifferentModel.Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Null(result);
        }

        [Fact]
        public void Process_ChainedMemberAccess_LongChain_WithNullableValueType()
        {
            // Arrange
            var expected = "TestVal";
            var model = new Chain0Model
            {
                Chain1 = new Chain1Model
                {
                    NullableValueTypeModel = new ValueType1
                    {
                        TestModel = new TestModel { DifferentModel = new DifferentModel { Name = expected } }
                    }
                }
            };

            var expression = GetExpression<Chain0Model, string>(m => m.Chain1.NullableValueTypeModel.Value.TestModel.DifferentModel.Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(expected, result);
        }

        [Fact]
        public void Process_ChainedMemberAccess_LongChain_WithNullValuedNullableValueType()
        {
            // Arrange
            var model = new Chain0Model
            {
                Chain1 = new Chain1Model
                {
                    NullableValueTypeModel = null
                }
            };

            var expression = GetExpression<Chain0Model, string>(m => m.Chain1.NullableValueTypeModel.Value.TestModel.DifferentModel.Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Null(result);
        }

        [Fact]
        public void Process_ChainedMemberAccess_ToReferenceType_WithNullIntermediary()
        {
            // Arrange
            var model = new TestModel { DifferentModel = null };
            var expression = GetTestModelExpression(m => m.DifferentModel.Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Null(result);
        }

        [Fact]
        public void Process_ChainedMemberAccess_ToReferenceType_WithNullModel()
        {
            // Arrange
            var model = (TestModel)null;
            var expression = GetTestModelExpression(m => m.DifferentModel.Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Null(result);
        }

        [Fact]
        public void Process_ChainedMemberAccess_OfValueTypes_ReturningReferenceTypeMember()
        {
            // Arrange
            var expected = "TestName";
            var model = new ValueType1
            {
                ValueType2 = new ValueType2 { Name = expected },
            };
            var expression = GetExpression<ValueType1, string>(m => m.ValueType2.Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(expected, result);
        }

        [Fact]
        public void Process_ChainedMemberAccess_OfValueTypes_ReturningValueType()
        {
            // Arrange
            var expected = new DateTime(2001, 1, 1);
            var model = new ValueType1
            {
                ValueType2 = new ValueType2 { Date = expected },
            };
            var expression = GetExpression<ValueType1, DateTime>(m => m.ValueType2.Date);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(expected, result);
        }

        [Fact]
        public void Process_ChainedMemberAccess_OfValueTypes_IncludingNullableType()
        {
            // Arrange
            var expected = "TestName";
            var model = new ValueType1
            {
                NullableValueType2 = new ValueType2 { Name = expected },
            };
            var expression = GetExpression<ValueType1, string>(m => m.NullableValueType2.Value.Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Equal(expected, result);
        }

        [Fact]
        public void Process_ChainedMemberAccess_OfValueTypes_WithNullValuedNullable()
        {
            // Arrange
            var model = new ValueType1 { NullableValueType2 = null };
            var expression = GetExpression<ValueType1, string>(m => m.NullableValueType2.Value.Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Null(result);
        }

        [Fact]
        public void Process_ChainedMemberAccess_OfValueTypes_WithNullValuedNullable_ReturningValueType()
        {
            // Arrange
            var model = new ValueType1 { NullableValueType2 = null };
            var expression = GetExpression<ValueType1, DateTime>(m => m.NullableValueType2.Value.Date);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Null(result);
        }

        [Fact]
        public void Process_MemberAccessOnCapturedVariable_ReturnsNull()
        {
            // Arrange
            var differentModel = new DifferentModel { Name = "Test" };
            var expression = GetTestModelExpression(m => differentModel.Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.Null(func);
        }

        [Fact]
        public void Process_CapturedVariable()
        {
            // Arrange
            var differentModel = new DifferentModel();
            var model = new TestModel();
            var expression = GetTestModelExpression(m => differentModel);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Same(differentModel, result);
        }

        [Fact]
        public void Process_CapturedVariable_WithNullModel()
        {
            // Arrange
            var differentModel = new DifferentModel();
            var model = (TestModel)null;
            var expression = GetTestModelExpression(m => differentModel);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.NotNull(func);
            var result = func(model);
            Assert.Same(differentModel, result);
        }


        [Fact]
        public void Process_MemberAccess_OnCapturedVariable_ReturnsNull()
        {
            // Arrange
            var differentModel = "Hello world";
            var expression = GetTestModelExpression(m => differentModel.Length);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.Null(func);
        }

        [Fact]
        public void Process_ComplexChainedMemberAccess_ReturnsNull()
        {
            // Arrange
            var expected = "SomeName";
            var model = new TestModel { DifferentModels = new[] { new DifferentModel { Name = expected } } };
            var expression = GetTestModelExpression(m => m.DifferentModels[0].Name);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.Null(func);
        }

        [Fact]
        public void Process_ArrayMemberAccess_ReturnsNull()
        {
            // Arrange
            var expression = GetTestModelExpression(m => m.Sizes[1]);

            // Act
            var func = CachedExpressionCompiler.Process(expression);

            // Assert
            Assert.Null(func);
        }

        private static Expression<Func<TModel, TResult>> GetExpression<TModel, TResult>(Expression<Func<TModel, TResult>> expression)
            => expression;

        private static Expression<Func<TestModel, TResult>> GetTestModelExpression<TResult>(Expression<Func<TestModel, TResult>> expression)
            => GetExpression(expression);

        public class TestModel
        {
            public static readonly string StaticField = "StaticValue";

            public static string StaticProperty { get; set; }

            public int Age { get; set; }

            public string Name { get; set; }

            public DateTime Date { get; set; }

            public DifferentModel DifferentModel { get; set; }

            public int[] Sizes { get; set; }

            public DifferentModel[] DifferentModels { get; set; }
        }

        public class DifferentModel
        {
            public const int Constant = 10;

            public string Name { get; set; }
        }

        public class Chain0Model
        {
            public Chain1Model Chain1 { get; set; }
        }

        public class Chain1Model
        {
            public ValueType1 ValueTypeModel { get; set; }

            public ValueType1? NullableValueTypeModel { get; set; }
        }

        public struct ValueType1
        {
            public TestModel TestModel { get; set; }

            public ValueType2 ValueType2 { get; set; }

            public ValueType2? NullableValueType2 { get; set; }
        }

        public struct ValueType2
        {
            public string Name { get; set; }

            public DateTime Date { get; set; }
        }

        public class BadEqualityModel
        {
            public int Id { get; set; }

            public override bool Equals(object obj)
            {
                return this == obj;
            }

            public static bool operator ==(BadEqualityModel a, object b)
            {
                if (a is null || b is null)
                {
                    throw new TimeZoneNotFoundException();
                }

                return true;
            }

            public static bool operator !=(BadEqualityModel a, object b)
            {
                return !(a == b);
            }

            public override int GetHashCode() => 0;
        }

        public struct BadEqualityValueTypeModel
        {
            public int Id { get; set; }

            public override bool Equals(object obj)
            {
                return this == obj;
            }

            public static bool operator ==(BadEqualityValueTypeModel a, object b)
            {
                if (b is null)
                {
                    throw new TimeZoneNotFoundException();
                }

                return true;
            }

            public static bool operator !=(BadEqualityValueTypeModel a, object b)
            {
                return !(a == b);
            }

            public override int GetHashCode() => 0;
        }
    }
}
