using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace MultiTenancySample.Models
{
    public class BloggingContext : DbContext
    {
        private readonly int _tenantId;

        private readonly Tenant _tenant;

        public BloggingContext(DbContextOptions<BloggingContext> options, ITenantProvider tenantProvider) : base(options)
        {
            _tenantId = tenantProvider.GetTenantId();
            _tenant = new Tenant();//TODO：根据租户编号查询租户
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.UseSqlServer(_tenant.DatabaseConnectionString);
            }

            base.OnConfiguring(optionsBuilder);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Blog>(builder =>
            {
                builder.HasKey(t => t.Id);
                builder.Property(t => t.Name).HasMaxLength(100).IsRequired();
                builder.Property(t => t.Url).HasMaxLength(100).IsRequired();

                builder.HasData(
                    new Blog { Id = 1, Name = "Blog1 by A", Url = "https://www.xcode.me/a", TenantId = 11 },
                    new Blog { Id = 2, Name = "Blog2 by A", Url = "https://www.xcode.me/b", TenantId = 11 },
                    new Blog { Id = 3, Name = "Blog1 by B", Url = "https://www.xcode.me/c", TenantId = 22 });
            });

            modelBuilder.Entity<Post>(builder =>
            {
                builder.HasKey(t => t.Id);
                builder.Property(t => t.Title).HasMaxLength(100).IsRequired();
                builder.Property(t => t.Content).HasMaxLength(500);

                builder.HasOne(p => p.Blog).WithMany(b => b.Posts).HasForeignKey(p => p.BlogId).OnDelete(DeleteBehavior.Cascade);
            });

            foreach (var entityType in modelBuilder.Model.GetEntityTypes())
            {
                if (entityType.ClrType.BaseType == typeof(BaseEntity))
                {
                    ConfigureGlobalFiltersMethodInfo.MakeGenericMethod(entityType.ClrType).Invoke(this, new object[] { modelBuilder });
                }
            }

            base.OnModelCreating(modelBuilder);
        }

        public override int SaveChanges(bool acceptAllChangesOnSuccess)
        {
            BeforeSaveChanges();

            return base.SaveChanges(acceptAllChangesOnSuccess);
        }

        private void BeforeSaveChanges()
        {
            ChangeTracker.DetectChanges();

            var entities = ChangeTracker.Entries().Where(e => e.State == EntityState.Added && e.Entity.GetType().BaseType == typeof(BaseEntity));

            foreach (var item in entities)
            {
                (item.Entity as BaseEntity).TenantId = _tenantId;
            }
        }

        public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            BeforeSaveChanges();

            return await base.SaveChangesAsync(cancellationToken);
        }

        private static MethodInfo ConfigureGlobalFiltersMethodInfo = typeof(BloggingContext).GetMethod(nameof(ConfigureGlobalFilters), BindingFlags.Instance | BindingFlags.NonPublic);

        protected void ConfigureGlobalFilters<T>(ModelBuilder builder) where T : BaseEntity
        {
            builder.Entity<T>().HasQueryFilter(e => e.TenantId == _tenantId);
        }

        public DbSet<Blog> Blogs { get; set; }

        public DbSet<Post> Posts { get; set; }
    }
}