using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Workshop;

namespace Workshop
{
	class Program
	{
		static void Main(string[] args)
		{
			using (var db = new MyContext())
			{
				db.Database.EnsureDeleted();
				db.Database.EnsureCreated();

				//var a = new Author()
				//{
				//	FirstName = "Jiri",
				//	LastName = "Test",
				//};
				//db.Add(a);
				//Console.WriteLine($"*** {a.Id}");
				//db.SaveChanges();
				//Console.WriteLine($"*** {a.Id}");
				//db.Set<Author>()/*.IgnoreQueryFilters()*/.Where(x => EF.Property<string>(x, "CreatedBy") == "Jiri").ToList();
				//db.Books
				//	.TagWith("foobar")
				//	.Where(x => x.Id == 10)
				//	.ToList();

				//var book = new Book() { ISBN = "test", Name = "Test", Duration = new MyTimeSpan(10) };
				//db.Add(book);
				//db.SaveChanges();
				////db.Set<Book>()
				////	.Where(x => x.Duration == new MyTimeSpan(20))
				////	.ToList();
				//book.Duration = new MyTimeSpan(10);
				//book.Name = "Test";
				//db.SaveChanges();

				//db.Set<Something>()
				//	.FromSqlInterpolated($"select FirstName + ' ' + LastName as Name from Author").ToList();

				//db.Set<Comment>()
				//	//.OfType<InternalComment>()
				//	.ToList();
				//db.Add(new PublicComment() { Text = "Great product!", Rating = 5 });
				//db.SaveChanges();

				//db.Set<Order>().FirstOrDefault();
				//db.Set<OrderDetail>().FirstOrDefault();

				//var product = new Product();
				//product.Id = default;
				//product.Name = "Laptop";
				//product.Price = 100m;
				//product["CPU"] = "AMD";
				//product["Screen"] = 14;
				//db.Add(product);
				//db.SaveChanges();

				//var duration = 10;
				//db.Books.FromSqlInterpolated($"select * from Books where Duration = {duration}")
				//	.Where(x => x.Duration == new MyTimeSpan(10))
				//	.Load();

				//db.Set<Product>()
				//	.Where(x => MyContext.MyToUpper(x.Name) == "A")
				//	.Where(x => EF.Functions.Like(x.Name, "abc%abc%."))
				//	.Load();

				//var books = db.Books/*.Include(x => x.Authors)*/.ToList();
				//foreach (var item in books)
				//{
				//	if (item.Rating > 4)
				//	{
				//		db.Entry(item).Collection(x => x.Authors).Load();
				//	}
				//}
				//var books = db.Books.ToList();
				//db.Set<Author>().Load();

				//var books = db.Books.Include(x => x.Authors).AsSplitQuery().AsNoTrackingWithIdentityResolution().ToList();
			}
		}
	}

	public class MyContext : DbContext
	{
		[DbFunction]
		public static string MyToUpper(string s)
		{
			throw new NotImplementedException();
		}

		public DbSet<Book> Books { get; }

		public MyContext()
		{
			Books = Set<Book>();
		}

		protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
		{
			base.OnConfiguring(optionsBuilder);
			optionsBuilder.UseSqlServer(@"Server=.;Database=workshop;Trusted_Connection=true;ConnectRetryCount=0",
				options => options.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery));
			optionsBuilder.LogTo(Console.WriteLine);
			optionsBuilder.EnableSensitiveDataLogging();
		}

		protected override void OnModelCreating(ModelBuilder modelBuilder)
		{
			base.OnModelCreating(modelBuilder);

			foreach (var entity in modelBuilder.Model.GetEntityTypes())
			{
				foreach (var prop in entity.GetProperties())
				{
					if (prop.ClrType == typeof(string))
					{
						prop.SetMaxLength(200);
					}
				}
			}

			//modelBuilder.UseCollation("SQLServer_Latin1_CX")

			modelBuilder.HasSequence("PKs")
				.HasMin(0)
				.HasMax(long.MaxValue)
				.IncrementsBy(50);

			//modelBuilder.ApplyConfiguration(new BookConfiguration());
			modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
			//modelBuilder.Ignore<Author>();

			var authorConf = modelBuilder.Entity<Author>();
			var fullNameConf = authorConf.OwnsOne(x => x.FullName);
			fullNameConf.Property(x => x.FirstName).HasColumnName("FirstName");
			//authorConf.Property(x => x.Id)
			//	//.ValueGeneratedNever();
			//	.UseHiLo("PKs");
			//authorConf.Property(x => x.Homepage)
			//	.HasField("_homepage")
			//	.UsePropertyAccessMode(PropertyAccessMode.Field);
			authorConf.Property<string>("CreatedBy");
			authorConf.HasQueryFilter(x => x.Active);

			var somethingConf = modelBuilder.Entity<Something>();
			somethingConf.HasNoKey();

			modelBuilder.Entity<Order>(b =>
			{
				b.ToTable("Orders");
				//b.Property(x => x.FooBar).HasColumnName("FooBar");
				b.HasOne(x => x.Detail).WithOne().HasForeignKey<OrderDetail>(x => x.Id);
			});

			modelBuilder.Entity<OrderDetail>(b =>
			{
				b.ToTable("Orders");
				//b.Property(x => x.FooBar).HasColumnName("FooBar");
			});

			//modelBuilder.SharedTypeEntity<Dictionary<string, object>>("Product", b =>
			//{
			//	b.ToTable("Products");
			//	b.IndexerProperty<int>("Id").UseIdentityColumn();
			//	b.IndexerProperty<string>("Name");
			//	b.IndexerProperty<decimal>("Price");
			//	b.IndexerProperty<string>("CPU");
			//	b.IndexerProperty<int>("Screen");
			//});
		}
	}

	public class BookConfiguration : IEntityTypeConfiguration<Book>
	{
		public void Configure(EntityTypeBuilder<Book> builder)
		{
			builder.ToTable("BOOKS");
			builder.HasKey(x => x.Id);
			builder.HasAlternateKey(x => x.ISBN);
			builder.Property(x => x.Name)
				.HasColumnName("Title")
				//.HasMaxLength(200)
				.IsRequired()
				.IsUnicode(false);
			//bookConf.HasCheckConstraint("RatingValidation", "...")
			//builder.Ignore(x => x.Rating);

			builder.HasMany(x => x.Authors)
				.WithMany(x => x.Books)
				.UsingEntity<BookAuthor>(
					x => x
						.HasOne(y => y.Author)
						.WithMany(y => y.BookAuthors),
					x => x
						.HasOne(y => y.Book)
						.WithMany(y => y.BookAuthors),
					x =>
					{
						x.Property(x => x.Created);
					});

			builder.HasMany(x => x.Comments)
				.WithOne()
				.OnDelete(DeleteBehavior.Cascade);

			builder.ToSqlQuery("select * from BOOKS where 1=1");
			builder.HasQueryFilter(x => x.ISBN != "");
			//builder.HasData(new Book() { });

			builder.Property(x => x.Duration).HasConversion(new MyTimeSpanConverter(), new MyTimeSpanComparer());

			//builder.HasIndex()
		}
	}

	[Table("BOOKS")]
	public class Book
	{
		public int Id { get; set; }
		public string ISBN { get; set; }
		public string Name { get; set; }
		public double Rating { get; set; }
		public ICollection<BookAuthor> BookAuthors { get; set; }
		public ICollection<Author> Authors { get; set; }
		public ICollection<Comment> Comments { get; set; }
		public MyTimeSpan Duration { get; set; }

		//public Book(string isbn, IEntityType entityType)
		//{
		//	ISBN = isbn;
		//}
	}


	#region MyTimeSpan
	public class MyTimeSpan
	{
		public TimeSpan Value { get; set; }

		public MyTimeSpan(TimeSpan value)
		{
			Value = value;
		}

		public MyTimeSpan(long seconds)
		{
			Value = TimeSpan.FromSeconds(seconds);
		}
	}

	public class MyTimeSpanConverter : ValueConverter<MyTimeSpan, long>
	{
		public MyTimeSpanConverter()
			: base(x => (long)x.Value.TotalSeconds, x => new MyTimeSpan(x), null)
		{ }
	}

	public class MyTimeSpanComparer : ValueComparer<MyTimeSpan>
	{
		public MyTimeSpanComparer()
			: base((lhs, rhs) => lhs.Value == rhs.Value, x => x.Value.GetHashCode())
		{ }
	}
	#endregion

	public class Author
	{
		public int Id { get; set; }
		public FullName FullName { get; set; }
		public ICollection<BookAuthor> BookAuthors { get; set; }
		public ICollection<Book> Books { get; set; }

		//private string _homepage;
		//string Homepage
		//{
		//	get => _homepage;
		//}

		//public void SetHomepage(string value)
		//{
		//	if (!value.StartsWith("https://"))
		//		throw new ArgumentException();
		//	_homepage = value;
		//}

		public bool Active { get; set; }
	}

	public class FullName
	{
		public string FirstName { get; set; }
		public string LastName { get; set; }
	}

	public class BookAuthor
	{
		public int BookId { get; set; }
		public Book Book { get; set; }
		public int AuthorId { get; set; }
		public Author Author { get; set; }
		public DateTime Created { get; set; }
	}

	#region Comment
	public abstract class Comment
	{
		public int Id { get; set; }
		public string Text { get; set; }
	}
	public class PublicComment : Comment
	{
		public double Rating { get; set; }
	}
	public class InternalComment : Comment
	{ }
	public class CommentConfiguration : IEntityTypeConfiguration<Comment>
	{
		public void Configure(EntityTypeBuilder<Comment> builder)
		{
			builder.ToTable("Comments");
		}
	}
	public class PublicCommentConfiguration : IEntityTypeConfiguration<PublicComment>
	{
		public void Configure(EntityTypeBuilder<PublicComment> builder)
		{
			//builder.ToTable("Comments")
			//	.HasDiscriminator<string>("D")
			//	.HasValue("PC");
			builder.ToTable("PublicComments");
		}
	}
	public class InternalCommentConfiguration : IEntityTypeConfiguration<InternalComment>
	{
		public void Configure(EntityTypeBuilder<InternalComment> builder)
		{
			//builder.ToTable("Comments")
			//	.HasDiscriminator<string>("D")
			//	.HasValue("IC");
			builder.ToTable("InternalComments");
		}
	}
	#endregion

	public class OrderBase
	{
		public int Id { get; set; }
		public string FooBar { get; set; }
	}
	public class Order : OrderBase
	{
		public OrderDetail Detail { get; set; }
	}
	public class OrderDetail : OrderBase
	{
		public decimal Price { get; set; }
		public string Customer { get; set; }
	}

	public class Product
	{
		public int Id { get; set; }
		public string Name { get; set; }
		public decimal Price { get; set; }

		Dictionary<string, object> _extra = new Dictionary<string, object>();
		public object this[string name]
		{
			get => _extra[name];
			set => _extra[name] = value;
		}
	}
	public class ProductConfiguration : IEntityTypeConfiguration<Product>
	{
		public void Configure(EntityTypeBuilder<Product> builder)
		{
			//builder.IndexerProperty<int>("Id").UseIdentityColumn();
			//builder.IndexerProperty<string>("Name");
			//builder.IndexerProperty<decimal>("Price");
			builder.IndexerProperty<string>("CPU");
			builder.IndexerProperty<int>("Screen");
		}
	}

	public class Something
	{
		public string Name { get; set; }
	}
}
