using AsbCloudDb.Model.GTR;
using AsbCloudDb.Model.Subsystems;
using Microsoft.EntityFrameworkCore;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudDb.Model.Manuals;

namespace AsbCloudDb.Model
{
    public partial class AsbCloudDbContext : DbContext, IAsbCloudDbContext
    {
        public virtual DbSet<Cluster> Clusters => Set<Cluster>();
        public virtual DbSet<Company> Companies => Set<Company>();
        public virtual DbSet<CompanyType> CompaniesTypes => Set<CompanyType>();
        public virtual DbSet<DailyReport.DailyReport> DailyReports => Set <DailyReport.DailyReport >();
        public virtual DbSet<Deposit> Deposits => Set<Deposit>();
        public virtual DbSet<DetectedOperation> DetectedOperations => Set<DetectedOperation>();
        public virtual DbSet<PlannedTrajectory> PlannedTrajectories => Set<PlannedTrajectory>();
        public virtual DbSet<ProcessMap> ProcessMap => Set<ProcessMap>();
        public virtual DbSet<DrillingProgramPart> DrillingProgramParts => Set<DrillingProgramPart>();
        public virtual DbSet<FileCategory> FileCategories => Set<FileCategory>();
        public virtual DbSet<FileInfo> Files => Set<FileInfo>();
        public virtual DbSet<FileMark> FileMarks => Set<FileMark>();
        public virtual DbSet<Measure> Measures => Set<Measure>();
        public virtual DbSet<MeasureCategory> MeasureCategories => Set<MeasureCategory>();
        public virtual DbSet<Permission> Permissions => Set<Permission>();
        public virtual DbSet<RelationCompanyWell> RelationCompaniesWells => Set<RelationCompanyWell>();
        public virtual DbSet<RelationUserDrillingProgramPart> RelationDrillingProgramPartUsers => Set<RelationUserDrillingProgramPart>();
        public virtual DbSet<RelationUserRolePermission> RelationUserRolePermissions => Set<RelationUserRolePermission>();
        public virtual DbSet<RelationUserRoleUserRole> RelationUserRoleUserRoles => Set<RelationUserRoleUserRole>();
        public virtual DbSet<RelationUserUserRole> RelationUserUserRoles => Set<RelationUserUserRole>();
        public virtual DbSet<RelationContactWell> RelationContactsWells => Set<RelationContactWell>();
        public virtual DbSet<ReportProperty> ReportProperties => Set<ReportProperty>();
        public virtual DbSet<SetpointsRequest> SetpointsRequests => Set<SetpointsRequest>();
        public virtual DbSet<Subsystem> Subsystems => Set<Subsystem>();
        public virtual DbSet<SubsystemOperationTime> SubsystemOperationTimes => Set<SubsystemOperationTime>();
        public virtual DbSet<Telemetry> Telemetries => Set<Telemetry>();
        public virtual DbSet<TelemetryDataSaub> TelemetryDataSaub => Set<TelemetryDataSaub>();
        public virtual DbSet<TelemetryDataSaubStat> TelemetryDataSaubStats => Set<TelemetryDataSaubStat>();
        public virtual DbSet<TelemetryDataSpin> TelemetryDataSpin => Set<TelemetryDataSpin>();
        public virtual DbSet<TelemetryEvent> TelemetryEvents => Set<TelemetryEvent>();
        public virtual DbSet<TelemetryMessage> TelemetryMessages => Set<TelemetryMessage>();
        public virtual DbSet<TelemetryUser> TelemetryUsers => Set<TelemetryUser>();
        public virtual DbSet<User> Users => Set<User>();
        public virtual DbSet<UserRole> UserRoles => Set<UserRole>();
        public virtual DbSet<UserSetting> UserSettings => Set<UserSetting>();
        public virtual DbSet<Well> Wells => Set<Well>();
        public virtual DbSet<WellComposite> WellComposites => Set<WellComposite>();
        public virtual DbSet<WellOperation> WellOperations => Set<WellOperation>();
        public virtual DbSet<WellOperationCategory> WellOperationCategories => Set<WellOperationCategory>();
        public virtual DbSet<WellSectionType> WellSectionTypes => Set<WellSectionType>();
        public virtual DbSet<WellType> WellTypes => Set<WellType>();
        public virtual DbSet<Driller> Drillers => Set<Driller>();
        public virtual DbSet<Schedule> Schedule => Set<Schedule>();
        public virtual DbSet<OperationValue> OperationValues => Set<OperationValue>();
        public virtual DbSet<WellFinalDocument> WellFinalDocuments => Set<WellFinalDocument>();
        public virtual DbSet<LimitingParameter> LimitingParameter => Set<LimitingParameter>();

        public virtual DbSet<TelemetryWirelineRunOut> TelemetryWirelineRunOut => Set<TelemetryWirelineRunOut>();

        // GTR WITS
        public DbSet<WitsItemFloat> WitsItemFloat => Set<WitsItemFloat>();
        public DbSet<WitsItemInt> WitsItemInt => Set<WitsItemInt>();
        public DbSet<WitsItemString> WitsItemString => Set<WitsItemString>();

        // WITS
        public DbSet<WITS.Record1> Record1 => Set<WITS.Record1>();
        public DbSet<WITS.Record7> Record7 => Set<WITS.Record7>();
        public DbSet<WITS.Record8> Record8 => Set<WITS.Record8>();
        public DbSet<WITS.Record50> Record50 => Set<WITS.Record50>();
        public DbSet<WITS.Record60> Record60 => Set<WITS.Record60>();
        public DbSet<WITS.Record61> Record61 => Set<WITS.Record61>();

        private static int referenceCount;
        public static int ReferenceCount => referenceCount;

        public DbSet<Faq> Faqs => Set<Faq>();
        public DbSet<HelpPage> HelpPages => Set<HelpPage>();
        public DbSet<Notification> Notifications => Set<Notification>();
        public DbSet<NotificationCategory> NotificationCategories => Set<NotificationCategory>();
        public DbSet<Manual> Manuals => Set<Manual>();
        public DbSet<ManualDirectory> ManualDirectories => Set<ManualDirectory>();
        

        public AsbCloudDbContext() : base()
        {
            Interlocked.Increment(ref referenceCount);
        }

        public AsbCloudDbContext(DbContextOptions<AsbCloudDbContext> options)
            : base(options)
        {
            Interlocked.Increment(ref referenceCount);
        }

        ~AsbCloudDbContext()
        {
            Interlocked.Decrement(ref referenceCount);
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {            
            if (!optionsBuilder.IsConfigured)
                optionsBuilder.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True"
                    //, builder=>builder.EnableRetryOnFailure(2, System.TimeSpan.FromMinutes(1))
                    );
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.HasPostgresExtension("adminpack")
                .HasAnnotation("Relational:Collation", "Russian_Russia.1251");

            modelBuilder.Entity<Deposit>(entity =>
            {
                entity.Property(e => e.Timezone)
                    .HasJsonConversion();
            });

            modelBuilder.Entity<Cluster>(entity =>
            {
                entity.HasOne(d => d.Deposit)
                    .WithMany(p => p.Clusters)
                    .HasForeignKey(d => d.IdDeposit)
                    .HasConstraintName("t_cluster_t_deposit_id_fk");

                entity.Property(e => e.Timezone)
                    .HasJsonConversion();
            });         

            modelBuilder.Entity<Well>(entity =>
            {
                entity.HasOne(d => d.Cluster)
                    .WithMany(p => p.Wells)
                    .HasForeignKey(d => d.IdCluster)
                    .HasConstraintName("t_well_t_cluster_id_fk");

                entity.HasOne(d => d.Telemetry)
                    .WithOne(p => p.Well)
                    .HasForeignKey<Well>(d => d.IdTelemetry)
                    .OnDelete(DeleteBehavior.SetNull)
                    .HasConstraintName("t_well_t_telemetry_id_fk");

                entity.Property(e => e.Timezone)
                    .HasJsonConversion();
            });

            modelBuilder.Entity<Telemetry>(entity =>
            {
                entity.Property(e => e.Info)
                    .HasJsonConversion();

                entity.Property(e => e.TimeZone)
                    .HasJsonConversion();
            });

            modelBuilder.Entity<WellComposite>(entity =>
            {
                entity.HasKey(
                    nameof(WellComposite.IdWell),
                    nameof(WellComposite.IdWellSrc),
                    nameof(WellComposite.IdWellSectionType));

                entity.HasOne(d => d.Well)
                    .WithMany(p => p.WellComposites)
                    .HasForeignKey(d => d.IdWell)
                    .HasConstraintName("t_well_сomposite_t_well_id_fk");

                entity.HasOne(d => d.WellSrc)
                    .WithMany(p => p.WellCompositeSrcs)
                    .HasForeignKey(d => d.IdWellSrc)
                    .HasConstraintName("t_well_сomposite_src_t_well_id_fk");

                entity.HasOne(d => d.WellSectionType)
                    .WithMany(p => p.WellComposites)
                    .HasForeignKey(d => d.IdWellSectionType)
                    .HasConstraintName("t_well_сomposite_t_well_section_type_id_fk");
            });

            modelBuilder.Entity<TelemetryDataSaub>(entity =>
            {
                entity.HasOne(d => d.Telemetry)
                    .WithMany(p => p.DataSaub)
                    .HasForeignKey(d => d.IdTelemetry)
                    .OnDelete(DeleteBehavior.Cascade)
                    .HasConstraintName("t_telemetry_data_saub_t_telemetry_id_fk");

                entity.HasKey(nameof(ITelemetryData.IdTelemetry), nameof(ITelemetryData.DateTime));
            });

            modelBuilder.Entity<WITS.RecordBase>(entity =>
            {
                entity.HasKey(nameof(ITelemetryData.IdTelemetry), nameof(ITelemetryData.DateTime));
            });

            modelBuilder.Entity<TelemetryDataSpin>(entity =>
            {
                entity.HasOne(d => d.Telemetry)
                    .WithMany(p => p.DataSpin)
                    .HasForeignKey(d => d.IdTelemetry)
                    .OnDelete(DeleteBehavior.Cascade)
                    .HasConstraintName("t_telemetry_data_spin_t_telemetry_id_fk");

                entity.HasKey(nameof(ITelemetryData.IdTelemetry), nameof(ITelemetryData.DateTime));
            });

            modelBuilder.Entity<TelemetryMessage>(entity =>
            {
                entity.HasOne(d => d.Telemetry)
                    .WithMany(p => p.Messages)
                    .HasForeignKey(d => d.IdTelemetry)
                    .OnDelete(DeleteBehavior.Cascade)
                    .HasConstraintName("t_messages_t_telemetry_id_fk");
            });

            modelBuilder.Entity<TelemetryUser>(entity =>
            {
                entity.HasKey(nameof(TelemetryUser.IdTelemetry), nameof(TelemetryUser.IdUser));
                entity.HasOne(d => d.Telemetry)
                    .WithMany(p => p.Users)
                    .HasForeignKey(d => d.IdTelemetry)
                    .OnDelete(DeleteBehavior.Cascade)
                    .HasConstraintName("t_telemetry_user_t_telemetry_id_fk");
            });

            modelBuilder.Entity<TelemetryEvent>(entity =>
            {
                entity.HasKey(nameof(TelemetryEvent.IdTelemetry), nameof(TelemetryEvent.IdEvent));
                entity.HasOne(d => d.Telemetry)
                    .WithMany(p => p.Events)
                    .HasForeignKey(d => d.IdTelemetry)
                    .OnDelete(DeleteBehavior.Cascade)
                    .HasConstraintName("t_event_t_telemetry_id_fk");
            });

            modelBuilder.Entity<User>(entity =>
            {
                entity.HasOne(d => d.Company)
                    .WithMany(p => p.Users)
                    .HasForeignKey(d => d.IdCompany)
                    .OnDelete(DeleteBehavior.SetNull)
                    .HasConstraintName("t_user_t_company_id_fk");

                entity.HasIndex(d => d.Login)
                    .IsUnique();
            });

            modelBuilder.Entity<RelationCompanyWell>(entity =>
            {
                entity.HasKey(nameof(RelationCompanyWell.IdCompany), nameof(RelationCompanyWell.IdWell));

                entity.HasOne(r => r.Well)
                    .WithMany(w => w.RelationCompaniesWells)
                    .HasForeignKey(r => r.IdWell)
                    .HasConstraintName("t_relation_company_well_t_well_id_fk");

                entity.HasOne(r => r.Company)
                    .WithMany(w => w.RelationCompaniesWells)
                    .HasForeignKey(r => r.IdCompany)
                    .HasConstraintName("t_relation_company_well_t_company_id_fk");
            });

            modelBuilder.Entity<RelationUserRoleUserRole>(entity =>
            {
                entity.HasKey(x => new { x.Id, x.IdInclude })
                .HasName("t_relation_user_role_user_role_pk");
            });

            modelBuilder.Entity<RelationUserDrillingProgramPart>(entity =>
            {
                entity.HasKey(x => new { x.IdUser, x.IdDrillingProgramPart })
                .HasName("t_relation_user_drilling_program_part_pk");
            });

            modelBuilder.Entity<WellOperation>(entity =>
            {
                entity.HasIndex(d => d.DepthEnd);
                entity.HasIndex(d => d.DateStart);
            });

            modelBuilder.Entity<DrillingProgramPart>(entity =>
            {
                entity.HasIndex(x => new { x.IdWell, x.IdFileCategory })
                .IsUnique();
            });

            modelBuilder.Entity<Measure>(entity =>
            {
                entity.Property(e => e.Data)
                    .HasJsonConversion();
            });

            modelBuilder.Entity<FileMark>(entity =>
            {
                entity.HasOne(d => d.User)
                    .WithMany(p => p.FileMarks)
                    .HasForeignKey(d => d.IdUser)
                    .OnDelete(DeleteBehavior.Cascade)
                    .HasConstraintName("t_user_t_file_mark_fk");

                entity.HasOne(d => d.FileInfo)
                    .WithMany(p => p.FileMarks)
                    .HasForeignKey(d => d.IdFile)
                    .OnDelete(DeleteBehavior.Cascade)
                    .HasConstraintName("t_file_mark_t_file_info_fk");
            });

            modelBuilder.Entity<RelationUserUserRole>(entity =>
            {
                entity.HasKey(e => new { e.IdUser, e.IdUserRole });
            });

            modelBuilder.Entity<RelationUserRolePermission>(entity =>
            {
                entity.HasKey(e => new { e.IdUserRole, e.IdPermission });

                entity.HasOne(r => r.Permission)
                    .WithMany(p => p.RelationUserRolePermissions)
                    .IsRequired();

                entity.HasOne(r => r.UserRole)
                    .WithMany(r => r.RelationUserRolePermissions)
                    .IsRequired();
            });

            modelBuilder.Entity<DailyReport.DailyReport >(entity =>
            {
                entity.HasKey(e => new { e.IdWell, e.StartDate })
                   .HasName("t_id_well_date_start_pk");
                entity.Property(e => e.Info)
                    .HasJsonConversion();
            });

            modelBuilder.Entity<TelemetryDataSaubStat>(entity =>
            {
                entity.HasNoKey()
                .ToView("mw_telemetry_datas_saub_stat");
            });

            modelBuilder.Entity<Schedule>(entity =>
            {
                entity.HasOne(d => d.Driller)
                    .WithMany(p => p.Schedule)
                    .HasForeignKey(d => d.IdDriller)
                    .HasConstraintName("t_schedule_t_driller_id_driller");
            });

            modelBuilder.Entity<SetpointsRequest>(entity => {
                entity.Property(e => e.Setpoints)
                    .HasJsonConversion();
            });

            modelBuilder.Entity<WitsItemFloat>(entity =>
            {
                entity.HasKey(
                    nameof(ITelemetryData.IdTelemetry),
                    nameof(WitsItemBase<float>.IdRecord),
                    nameof(WitsItemBase<float>.IdItem),
                    nameof(WitsItemBase<float>.DateTime));
        });
            modelBuilder.Entity<WitsItemInt>(entity =>
            {
                entity.HasKey(
                    nameof(ITelemetryData.IdTelemetry),
                    nameof(WitsItemBase<int>.IdRecord),
                    nameof(WitsItemBase<int>.IdItem),
                    nameof(WitsItemBase<int>.DateTime));
            });
            modelBuilder.Entity<WitsItemString>(entity =>
            {
                entity.HasKey(
                    nameof(ITelemetryData.IdTelemetry),
                    nameof(WitsItemBase<string>.IdRecord),
                    nameof(WitsItemBase<string>.IdItem),
                    nameof(WitsItemBase<string>.DateTime));
            });


            modelBuilder.Entity<UserSetting>(entity =>
            {
                entity.HasKey(nameof(UserSetting.IdUser), nameof(UserSetting.Key));
            });

            modelBuilder.Entity<WellFinalDocument>(entity =>
            {
                entity.HasKey(x => new { x.IdWell, x.IdUser, x.IdCategory })
                .HasName("t_well_final_documents_pk");
            });

            modelBuilder.Entity<RelationContactWell>(entity =>
            {
                entity.HasKey(x => new { x.IdWell, x.IdUser });
            });

            modelBuilder.Entity<ManualDirectory>()
                .HasOne(mf => mf.Parent)
                .WithMany(mf => mf.Children)
                .HasForeignKey(mf => mf.IdParent)
                .OnDelete(DeleteBehavior.Cascade);
            
            modelBuilder.Entity<Manual>()
                .HasOne(m => m.Directory)
                .WithMany(f => f.Manuals)
                .HasForeignKey(m => m.IdDirectory)
                .OnDelete(DeleteBehavior.Cascade);

            DefaultData.DefaultContextData.Fill(modelBuilder);
        }

        public Task<int> RefreshMaterializedViewAsync<TEntity>(CancellationToken token)
            where TEntity : class
        {
            var materializedViewName = Set<TEntity>().EntityType.GetViewName()
                ?? throw new System.Exception($"RefreshMaterializedViewAsync<{typeof(TEntity).Name}>(..) db table for this type does not found.");
            return RefreshMaterializedViewAsync(materializedViewName, token);
        }

        public Task<int> RefreshMaterializedViewAsync(string materializedViewName, CancellationToken token)
        {
            var sql = $"REFRESH MATERIALIZED VIEW {materializedViewName};";
            return Database.ExecuteSqlRawAsync(sql, token);
        }        
    }
}