context: using System;
  using System.Collections.Generic;
  using System.Data;
  using System.Data.Common;
  using System.Linq;
  using System.Reflection;
  using NHibernate.AdoNet;
  using NHibernate.Cfg;
  using NHibernate.Driver;
  using NHibernate.Engine;
  using NHibernate.SqlCommand;
  using NHibernate.SqlTypes;
  using NHibernate.Tool.hbm2ddl;
  using NHibernate.Type;
  using NHibernate.Util;
  using NUnit.Framework;
  
  namespace NHibernate.Test.TypesTest
  {
  	[TestFixture]
  	public abstract class AbstractDateTimeTypeFixture : TypeFixtureBase
  	{
  		protected abstract AbstractDateTimeType Type { get; }
  		protected virtual bool RevisionCheck => true;
  
  		protected const int DateId = 1;
  		protected const int AdditionalDateId = 2;
  
  		protected override void Configure(Configuration configuration)
  		{
  			base.Configure(configuration);
  
  			var driverClass = ReflectHelper.ClassForName(configuration.GetProperty(Cfg.Environment.ConnectionDriver));
  			ClientDriverWithParamsStats.DriverClass = driverClass;
  
  			configuration.SetProperty(
  				Cfg.Environment.ConnectionDriver,
  				typeof(ClientDriverWithParamsStats).AssemblyQualifiedName);
  		}
  
  		protected override void OnSetUp()
  		{
  			base.OnSetUp();
  
  			using (var s = OpenSession())
  			using (var t = s.BeginTransaction())
  			{
  				var d = new DateTimeClass
  				{
  					Id = DateId,
  					Value = Now
  				};
  				s.Save(d);
  				t.Commit();
  			}
  		}
  
  		protected override void OnTearDown()
  		{
  			base.OnTearDown();
  
  			using (var s = OpenSession())
  			using (var t = s.BeginTransaction())
  			{
  				s.CreateQuery("delete from DateTimeClass").ExecuteUpdate();
  				t.Commit();
  			}
  		}
  
  		protected override void DropSchema()
  		{
  			(Sfi.ConnectionProvider.Driver as ClientDriverWithParamsStats)?.CleanUp();
  			base.DropSchema();
  		}
  
  		[Test]
  		public void Next()
  		{
  			// Take some margin, as DbTimestampType takes its next value from the database, which
  			// may have its clock a bit shifted even if running on the same server. (Seen with PostgreSQL,
  			// off by a few seconds, and with SAP HANA running in a vm, off by twenty seconds.)
  			var current = Now.Subtract(TimeSpan.FromMinutes(2));
  			var next = Type.Next(current, null);
  
  			Assert.That(next, Is.TypeOf<DateTime>(), "next should be DateTime");
  			Assert.That(next, Is.GreaterThan(current), "next should be greater than current");
  		}
  
  		[Test]
  		public void Seed()
  		{
  			Assert.That(Type.Seed(null), Is.TypeOf<DateTime>(), "seed should be DateTime");
  		}
  
  		[Test]
  		public void Comparer()
  		{
  			var v1 = Type.Seed(null);
  			var v2 = Now.Subtract(TimeSpan.FromTicks(DateAccuracyInTicks));
  			Assert.That(() => Type.Comparator.Compare(v1, v2), Throws.Nothing);
  		}
  
  		[Test]
  		public void DeepCopyNotNull()
  		{
  			var value1 = DateTime.Now;
  			var value2 = Type.DeepCopy(value1, null);
  
  			Assert.That(value2, Is.EqualTo(value1), "Copies should be the same.");
  
  			// Bit moot with structs...
  			value2 = ((DateTime) value2).AddHours(2);
  			Assert.That(value2, Is.Not.EqualTo(value1), "value2 was changed, value1 should not have changed also.");
  		}
  
  		[Test]
  		[TestCase(DateTimeKind.Unspecified)]
  		[TestCase(DateTimeKind.Local)]
  		[TestCase(DateTimeKind.Utc)]
  		public void Equality(DateTimeKind kind)
  		{
  			var testDate = GetTestDate(kind);
  			var sameDate = GetSameDate(testDate);
  			Assert.That(Type.IsEqual(testDate, sameDate), Is.True);
  		}
  
  		[Test]
  		[TestCase(DateTimeKind.Unspecified)]
  		[TestCase(DateTimeKind.Local)]
  		[TestCase(DateTimeKind.Utc)]
  		public void Inequality(DateTimeKind kind)
  		{
  			var testDate = GetTestDate(kind);
  			var diffDate = GetDifferentDate(testDate);
  			Assert.That(Type.IsEqual(testDate, diffDate), Is.False);
  		}
  
  		[Test]
  		[TestCase(DateTimeKind.Unspecified)]
  		[TestCase(DateTimeKind.Local)]
  		[TestCase(DateTimeKind.Utc)]
  		public void ReadWrite(DateTimeKind kind)
  		{
  			var entity = new DateTimeClass
  			{
  				Id = AdditionalDateId,
  				Value = GetTestDate(kind)
  			};
  
  			var typeKind = GetTypeKind();
  			// Now must be acquired before transaction because some db freezes current_timestamp at transaction start,
  			// like PostgreSQL. https://www.postgresql.org/docs/7.2/static/functions-datetime.html#AEN6700
  			// This then wrecks tests with DbTimestampType if the always out of tran Now is called for fetching
  			// beforeNow only after transaction start.
  			// And account db accuracy
  			var beforeNow = Now.AddTicks(-DateAccuracyInTicks);
  			// Save
  			using (var s = OpenSession())
  			using (var t = s.BeginTransaction())
  			{
  				s.Save(entity);
  				if (kind != typeKind && typeKind != DateTimeKind.Unspecified)
  				{
  					Assert.That(() => t.Commit(), Throws.TypeOf<PropertyValueException>());
  					return;
  				}
  				t.Commit();
  			}
  			var afterNow = Now.AddTicks(DateAccuracyInTicks);
  
  			if (RevisionCheck)
  			{
  				Assert.That(entity.Revision, Is.GreaterThan(beforeNow).And.LessThan(afterNow), "Revision not correctly seeded.");
  				if (typeKind != DateTimeKind.Unspecified)
  					Assert.That(entity.Revision.Kind, Is.EqualTo(typeKind), "Revision kind not correctly seeded.");
  				Assert.That(entity.NullableValue, Is.Null, "NullableValue unexpectedly seeded.");
  			}
  
  			// Retrieve, compare then update
  			DateTimeClass retrieved;
  			using (var s = OpenSession())
  			{
  				using (var t = s.BeginTransaction())
  				{
  					retrieved = s.Get<DateTimeClass>(AdditionalDateId);
  
  					Assert.That(retrieved, Is.Not.Null, "Entity not saved or cannot be retrieved by its key.");
  					Assert.That(retrieved.Value, Is.EqualTo(GetExpectedValue(entity.Value)), "Unexpected value.");
  					if (RevisionCheck)
  						Assert.That(retrieved.Revision, Is.EqualTo(entity.Revision), "Revision should be the same.");
  					Assert.That(retrieved.NullableValue, Is.EqualTo(entity.NullableValue), "NullableValue should be the same.");
  					if (typeKind != DateTimeKind.Unspecified)
  					{
  						Assert.That(retrieved.Value.Kind, Is.EqualTo(typeKind), "Value kind not correctly retrieved.");
  						if (RevisionCheck)
  							Assert.That(retrieved.Revision.Kind, Is.EqualTo(typeKind), "Revision kind not correctly retrieved.");
  					}
  					t.Commit();
  				}
  				beforeNow = Now.AddTicks(-DateAccuracyInTicks);
  				using (var t = s.BeginTransaction())
  				{
  					retrieved.NullableValue = GetTestDate(kind);
  					retrieved.Value = GetTestDate(kind).AddMonths(-1);
  					t.Commit();
  				}
  				afterNow = Now.AddTicks(DateAccuracyInTicks);
  			}
  
  			if (RevisionCheck)
  			{
  				Assert.That(
  					retrieved.Revision,
  					Is.GreaterThan(beforeNow).And.LessThan(afterNow).And.GreaterThanOrEqualTo(entity.Revision),
  					"Revision not correctly incremented.");
  				if (typeKind != DateTimeKind.Unspecified)
  					Assert.That(retrieved.Revision.Kind, Is.EqualTo(typeKind), "Revision kind incorrectly changed.");
  			}
  
  			// Retrieve and compare again
  			using (var s = OpenSession())
  			using (var t = s.BeginTransaction())
  			{
  				var retrievedAgain = s.Get<DateTimeClass>(AdditionalDateId);
  
  				Assert.That(retrievedAgain, Is.Not.Null, "Entity deleted or cannot be retrieved again by its key.");
  				Assert.That(
  					retrievedAgain.Value,
  					Is.EqualTo(GetExpectedValue(retrieved.Value)),
  					"Unexpected value at second compare.");
  				if (RevisionCheck)
  					Assert.That(retrievedAgain.Revision, Is.EqualTo(retrieved.Revision), "Revision should be the same again.");
  				Assert.That(
  					retrievedAgain.NullableValue,
  					Is.EqualTo(GetExpectedValue(retrieved.NullableValue.Value)),
  					"Unexpected NullableValue at second compare.");
  				if (typeKind != DateTimeKind.Unspecified)
  				{
  					Assert.That(retrievedAgain.Value.Kind, Is.EqualTo(typeKind), "Value kind not correctly retrieved again.");
  					if (RevisionCheck)
  						Assert.That(retrievedAgain.Revision.Kind, Is.EqualTo(typeKind), "Revision kind not correctly retrieved again.");
  					Assert.That(
  						retrievedAgain.NullableValue.Value.Kind,
  						Is.EqualTo(typeKind),
  						"NullableValue kind not correctly retrieved again.");
  				}
  				t.Commit();
  			}
  		}
  
  		[Test]
  		public void PropertiesHasExpectedType()
  		{
  			var classMetaData = Sfi.GetClassMetadata(typeof(DateTimeClass));
  			Assert.That(
  				classMetaData.GetPropertyType(nameof(DateTimeClass.Revision)),
  				Is.EqualTo(Type));
  			Assert.That(
  				classMetaData.GetPropertyType(nameof(DateTimeClass.Value)),
  				Is.EqualTo(Type));
  			Assert.That(
  				classMetaData.GetPropertyType(nameof(DateTimeClass.NullableValue)),
  				Is.EqualTo(Type));
  		}
  
  		[Test]
  		public void DbHasExpectedType()
  		{
  			var validator = new SchemaValidator(cfg);
  			validator.Validate();
  		}
  
  		[Test]
  		public virtual void SaveUseExpectedSqlType()
  		{
  			var driver = (ClientDriverWithParamsStats) Sfi.ConnectionProvider.Driver;
  
  			using (var s = OpenSession())
  			using (var t = s.BeginTransaction())
  			{
  				var d = new DateTimeClass
  				{
  					Id = 2,
  					Value = Now,
  					NullableValue = Now
  				};
  				driver.ClearStats();
  				s.Save(d);
  				t.Commit();
  			}
  
  			var expected = 3;
  			// GenericBatchingBatcher uses IDriver.GenerateCommand method to create the batching command,
  			// so the expected result will be doubled as GenerateCommand calls IDriver.GenerateParameter
  			// for each parameter.
  			if (Sfi.Settings.BatcherFactory is GenericBatchingBatcherFactory)
  			{
  				expected *= 2;
  			}
  			// 2 properties + revision
  			AssertSqlType(driver, expected, true);
  		}
  
  		[Test]
  		public virtual void UpdateUseExpectedSqlType()
  		{
  			var driver = (ClientDriverWithParamsStats) Sfi.ConnectionProvider.Driver;
  
  			using (var s = OpenSession())
  			using (var t = s.BeginTransaction())
  			{
  				var d = s.Get<DateTimeClass>(DateId);
  				d.Value = Now;
  				d.NullableValue = Now;
  				driver.ClearStats();
  				t.Commit();
  			}
  
  			// 2 properties + revision x 2 (check + update)
  			AssertSqlType(driver, 4, true);
  		}
  
  		[Test]
  		public virtual void QueryUseExpectedSqlType()
  		{
  			if (!TestDialect.SupportsNonDataBoundCondition)
  				Assert.Ignore("Dialect does not support the test query");
  
  			var driver = (ClientDriverWithParamsStats) Sfi.ConnectionProvider.Driver;
  
  			using (var s = OpenSession())
  			using (var t = s.BeginTransaction())
  			{
  				var q = s
  					.CreateQuery(
  						"from DateTimeClass d where d.Value = :value and " +
  						"d.NullableValue = :nullableValue and " +
  						"d.Revision = :revision and " +
  						":other1 = :other2")
  					.SetDateTime("value", Now)
  					.SetDateTime("nullableValue", Now)
  					.SetDateTime("revision", Now)
  					.SetDateTime("other1", Now)
  					.SetDateTime("other2", Now);
  				driver.ClearStats();
  				q.List<DateTimeClass>();
  				t.Commit();
  			}
  
  			AssertSqlType(driver, 5, false);
  
  			using (var s = OpenSession())
  			using (var t = s.BeginTransaction())
  			{
  				var q = s
  					.CreateQuery(
  						"from DateTimeClass d where d.Value = :value and " +
  						"d.NullableValue = :nullableValue and " +
  						"d.Revision = :revision and " +
  
gt: 						":other1 = :other2")