using API.Schema.Jobs;
using API.Schema.MangaConnectors;
using log4net;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;

namespace API.Schema.Contexts;

public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(options)
{
    public DbSet<Job> Jobs { get; set; }
    public DbSet<MangaConnector> MangaConnectors { get; set; }
    public DbSet<Manga> Mangas { get; set; }
    public DbSet<LocalLibrary> LocalLibraries { get; set; }
    public DbSet<Chapter> Chapters { get; set; }
    public DbSet<Author> Authors { get; set; }
    public DbSet<MangaTag> Tags { get; set; }
    private ILog Log => LogManager.GetLogger(GetType());
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.EnableSensitiveDataLogging();
        optionsBuilder.LogTo(s =>
        {
            Log.Debug(s);
        }, [DbLoggerCategory.Query.Name], LogLevel.Trace, DbContextLoggerOptions.Level | DbContextLoggerOptions.Category);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //Job Types
        modelBuilder.Entity<Job>()
            .HasDiscriminator(j => j.JobType)
            .HasValue<MoveFileOrFolderJob>(JobType.MoveFileOrFolderJob)
            .HasValue<MoveMangaLibraryJob>(JobType.MoveMangaLibraryJob)
            .HasValue<DownloadAvailableChaptersJob>(JobType.DownloadAvailableChaptersJob)
            .HasValue<DownloadSingleChapterJob>(JobType.DownloadSingleChapterJob)
            .HasValue<DownloadMangaCoverJob>(JobType.DownloadMangaCoverJob)
            .HasValue<RetrieveChaptersJob>(JobType.RetrieveChaptersJob)
            .HasValue<UpdateChaptersDownloadedJob>(JobType.UpdateChaptersDownloadedJob)
            .HasValue<UpdateSingleChapterDownloadedJob>(JobType.UpdateSingleChapterDownloadedJob);
        
        //Job specification
        modelBuilder.Entity<DownloadAvailableChaptersJob>()
            .HasOne<Manga>(j => j.Manga)
            .WithMany()
            .HasForeignKey(j => j.MangaId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
        modelBuilder.Entity<DownloadAvailableChaptersJob>()
            .Navigation(j => j.Manga)
            .EnableLazyLoading();
        modelBuilder.Entity<DownloadMangaCoverJob>()
            .HasOne<Manga>(j => j.Manga)
            .WithMany()
            .HasForeignKey(j => j.MangaId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
        modelBuilder.Entity<DownloadMangaCoverJob>()
            .Navigation(j => j.Manga)
            .EnableLazyLoading();
        modelBuilder.Entity<DownloadSingleChapterJob>()
            .HasOne<Chapter>(j => j.Chapter)
            .WithMany()
            .HasForeignKey(j => j.ChapterId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
        modelBuilder.Entity<DownloadSingleChapterJob>()
            .Navigation(j => j.Chapter)
            .EnableLazyLoading();
        modelBuilder.Entity<MoveMangaLibraryJob>()
            .HasOne<Manga>(j => j.Manga)
            .WithMany()
            .HasForeignKey(j => j.MangaId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
        modelBuilder.Entity<MoveMangaLibraryJob>()
            .Navigation(j => j.Manga)
            .EnableLazyLoading();
        modelBuilder.Entity<MoveMangaLibraryJob>()
            .HasOne<LocalLibrary>(j => j.ToLibrary)
            .WithMany()
            .HasForeignKey(j => j.ToLibraryId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
        modelBuilder.Entity<MoveMangaLibraryJob>()
            .Navigation(j => j.ToLibrary)
            .EnableLazyLoading();
        modelBuilder.Entity<RetrieveChaptersJob>()
            .HasOne<Manga>(j => j.Manga)
            .WithMany()
            .HasForeignKey(j => j.MangaId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
        modelBuilder.Entity<RetrieveChaptersJob>()
            .Navigation(j => j.Manga)
            .EnableLazyLoading();
        modelBuilder.Entity<UpdateChaptersDownloadedJob>()
            .HasOne<Manga>(j => j.Manga)
            .WithMany()
            .HasForeignKey(j => j.MangaId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
        modelBuilder.Entity<UpdateChaptersDownloadedJob>()
            .Navigation(j => j.Manga)
            .EnableLazyLoading();
        
        //Job has possible ParentJob
        modelBuilder.Entity<Job>()
            .HasOne<Job>(childJob => childJob.ParentJob)
            .WithMany()
            .HasForeignKey(childjob => childjob.ParentJobId)
            .OnDelete(DeleteBehavior.Cascade);
        //Job might be dependent on other Jobs
        modelBuilder.Entity<Job>()
            .HasMany<Job>(root => root.DependsOnJobs)
            .WithMany();
        modelBuilder.Entity<Job>()
            .Navigation(j => j.DependsOnJobs)
            .AutoInclude(false);
        
        //MangaConnector Types
        modelBuilder.Entity<MangaConnector>()
            .HasDiscriminator(c => c.Name)
            .HasValue<Global>("Global")
            .HasValue<MangaDex>("MangaDex")
            .HasValue<ComickIo>("ComickIo");
        //MangaConnector is responsible for many Manga
        modelBuilder.Entity<MangaConnector>()
            .HasMany<Manga>()
            .WithOne(m => m.MangaConnector)
            .HasForeignKey(m => m.MangaConnectorName)
            .IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
        modelBuilder.Entity<Manga>()
            .Navigation(m => m.MangaConnector)
            .AutoInclude();

        //Manga has many Chapters
        modelBuilder.Entity<Manga>()
            .HasMany<Chapter>(m => m.Chapters)
            .WithOne(c => c.ParentManga)
            .HasForeignKey(c => c.ParentMangaId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
        modelBuilder.Entity<Chapter>()
            .Navigation(c => c.ParentManga)
            .AutoInclude();
        modelBuilder.Entity<Manga>()
            .Navigation(m => m.Chapters)
            .AutoInclude(false);
        //Manga owns MangaAltTitles
        modelBuilder.Entity<Manga>()
            .OwnsMany<MangaAltTitle>(m => m.AltTitles)
            .WithOwner();
        modelBuilder.Entity<Manga>()
            .Navigation(m => m.AltTitles)
            .AutoInclude();
        //Manga owns Links
        modelBuilder.Entity<Manga>()
            .OwnsMany<Link>(m => m.Links)
            .WithOwner();
        modelBuilder.Entity<Manga>()
            .Navigation(m => m.Links)
            .AutoInclude();
        //Manga has many Tags associated with many Manga
        modelBuilder.Entity<Manga>()
            .HasMany<MangaTag>(m => m.MangaTags)
            .WithMany()
            .UsingEntity("MangaTagToManga",
                l=> l.HasOne(typeof(MangaTag)).WithMany().HasForeignKey("MangaTagIds").HasPrincipalKey(nameof(MangaTag.Tag)),
                r => r.HasOne(typeof(Manga)).WithMany().HasForeignKey("MangaIds").HasPrincipalKey(nameof(Manga.MangaId)),
                j => j.HasKey("MangaTagIds", "MangaIds")
            );
        modelBuilder.Entity<Manga>()
            .Navigation(m => m.MangaTags)
            .AutoInclude();
        //Manga has many Authors associated with many Manga
        modelBuilder.Entity<Manga>()
            .HasMany<Author>(m => m.Authors)
            .WithMany()
            .UsingEntity("AuthorToManga",
                l=> l.HasOne(typeof(Author)).WithMany().HasForeignKey("AuthorIds").HasPrincipalKey(nameof(Author.AuthorId)),
                r => r.HasOne(typeof(Manga)).WithMany().HasForeignKey("MangaIds").HasPrincipalKey(nameof(Manga.MangaId)),
                j => j.HasKey("AuthorIds", "MangaIds")
            );
        modelBuilder.Entity<Manga>()
            .Navigation(m => m.Authors)
            .AutoInclude();
        
        //LocalLibrary has many Mangas
        modelBuilder.Entity<LocalLibrary>()
            .HasMany<Manga>()
            .WithOne(m => m.Library)
            .HasForeignKey(m => m.LibraryId)
            .OnDelete(DeleteBehavior.SetNull);
        modelBuilder.Entity<Manga>()
            .Navigation(m => m.Library)
            .AutoInclude();
    }
}