using Ikar.Server.Data.Entities;
using System.Globalization;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace Ikar.Server.Data;

public sealed class IkarDbContext(DbContextOptions<IkarDbContext> options) : DbContext(options)
{
    private static readonly ValueConverter<DateTimeOffset, string> DateTimeOffsetToStringConverter = new(
        value => value.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture),
        value => DateTimeOffset.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind));

    private static readonly ValueConverter<DateTimeOffset?, string?> NullableDateTimeOffsetToStringConverter = new(
        value => value.HasValue ? value.Value.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture) : null,
        value => value == null ? null : DateTimeOffset.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind));

    public DbSet<User> Users => Set<User>();
    public DbSet<UserSession> UserSessions => Set<UserSession>();
    public DbSet<UserPrivacySettings> UserPrivacySettings => Set<UserPrivacySettings>();
    public DbSet<Chat> Chats => Set<Chat>();
    public DbSet<ChatMember> ChatMembers => Set<ChatMember>();
    public DbSet<ChatInviteLink> ChatInviteLinks => Set<ChatInviteLink>();
    public DbSet<ChatJoinRequest> ChatJoinRequests => Set<ChatJoinRequest>();
    public DbSet<ChatTopic> ChatTopics => Set<ChatTopic>();
    public DbSet<Message> Messages => Set<Message>();
    public DbSet<MessageAttachment> MessageAttachments => Set<MessageAttachment>();
    public DbSet<MessageReaction> MessageReactions => Set<MessageReaction>();
    public DbSet<MessageView> MessageViews => Set<MessageView>();
    public DbSet<PushDevice> PushDevices => Set<PushDevice>();
    public DbSet<Bot> Bots => Set<Bot>();
    public DbSet<BotCommand> BotCommands => Set<BotCommand>();
    public DbSet<BotUpdate> BotUpdates => Set<BotUpdate>();
    public DbSet<ModerationLogEntry> ModerationLogEntries => Set<ModerationLogEntry>();
    public DbSet<Story> Stories => Set<Story>();
    public DbSet<StoryAttachment> StoryAttachments => Set<StoryAttachment>();
    public DbSet<StoryView> StoryViews => Set<StoryView>();
    public DbSet<AndroidUpdateCompatibilityHit> AndroidUpdateCompatibilityHits => Set<AndroidUpdateCompatibilityHit>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        ConfigureUsers(modelBuilder);
        ConfigureChats(modelBuilder);
        ConfigureMessages(modelBuilder);
        ConfigurePushDevices(modelBuilder);
        ConfigureBots(modelBuilder);
        ConfigureModeration(modelBuilder);
        ConfigureStories(modelBuilder);
        ConfigureAndroidUpdateCompatibility(modelBuilder);
        ConfigureDateTimeOffsetConverters(modelBuilder);
    }

    private static void ConfigureDateTimeOffsetConverters(ModelBuilder modelBuilder)
    {
        foreach (var property in modelBuilder.Model.GetEntityTypes().SelectMany(entityType => entityType.GetProperties()))
        {
            if (property.ClrType == typeof(DateTimeOffset))
            {
                property.SetValueConverter(DateTimeOffsetToStringConverter);
            }
            else if (property.ClrType == typeof(DateTimeOffset?))
            {
                property.SetValueConverter(NullableDateTimeOffsetToStringConverter);
            }
        }
    }

    private static void ConfigureUsers(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>(entity =>
        {
            entity.ToTable("Users");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => x.NormalizedUsername).IsUnique();
            entity.HasIndex(x => x.NormalizedPhoneNumber)
                .IsUnique()
                .HasFilter("\"NormalizedPhoneNumber\" IS NOT NULL");

            entity.Property(x => x.Username).IsRequired();
            entity.Property(x => x.NormalizedUsername).IsRequired();
            entity.Property(x => x.DisplayName).IsRequired();
            entity.Property(x => x.PasswordHash).IsRequired();

            entity.HasMany(x => x.Sessions)
                .WithOne(x => x.User)
                .HasForeignKey(x => x.UserId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(x => x.PrivacySettings)
                .WithOne(x => x.User)
                .HasForeignKey<UserPrivacySettings>(x => x.UserId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        modelBuilder.Entity<UserPrivacySettings>(entity =>
        {
            entity.ToTable("UserPrivacySettings");
            entity.HasKey(x => x.UserId);
            entity.Property(x => x.PhoneNumberVisibility).IsRequired();
            entity.Property(x => x.LastSeenVisibility).IsRequired();
            entity.Property(x => x.ProfilePhotoVisibility).IsRequired();
            entity.Property(x => x.BlockedUserIds).IsRequired();
            entity.Property(x => x.AlwaysAllowUserIds).IsRequired();
            entity.Property(x => x.NeverAllowUserIds).IsRequired();
        });

        modelBuilder.Entity<UserSession>(entity =>
        {
            entity.ToTable("UserSessions");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => x.TokenHash).IsUnique();
            entity.HasIndex(x => x.UserId);
            entity.Property(x => x.TokenHash).IsRequired();
            entity.Property(x => x.ClientPlatform).HasMaxLength(32);
            entity.Property(x => x.ClientVersionName).HasMaxLength(64);
        });
    }

    private static void ConfigureChats(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Chat>(entity =>
        {
            entity.ToTable("Chats");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => x.CreatedById);
            entity.HasIndex(x => x.LastActivityAt);
            entity.HasIndex(x => x.LinkedDiscussionChatId);
            entity.HasIndex(x => x.PublicUsername)
                .IsUnique()
                .HasFilter("\"PublicUsername\" IS NOT NULL");

            entity.HasOne(x => x.CreatedBy)
                .WithMany(x => x.CreatedChats)
                .HasForeignKey(x => x.CreatedById)
                .OnDelete(DeleteBehavior.Restrict);

            entity.HasMany(x => x.Members)
                .WithOne(x => x.Chat)
                .HasForeignKey(x => x.ChatId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasMany(x => x.Messages)
                .WithOne(x => x.Chat)
                .HasForeignKey(x => x.ChatId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasMany(x => x.InviteLinks)
                .WithOne(x => x.Chat)
                .HasForeignKey(x => x.ChatId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasMany(x => x.JoinRequests)
                .WithOne(x => x.Chat)
                .HasForeignKey(x => x.ChatId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasMany(x => x.Topics)
                .WithOne(x => x.Chat)
                .HasForeignKey(x => x.ChatId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(x => x.LinkedDiscussionChat)
                .WithMany()
                .HasForeignKey(x => x.LinkedDiscussionChatId)
                .OnDelete(DeleteBehavior.SetNull);
        });

        modelBuilder.Entity<ChatInviteLink>(entity =>
        {
            entity.ToTable("ChatInviteLinks");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => x.ChatId);
            entity.HasIndex(x => x.Token).IsUnique();
            entity.Property(x => x.Token).IsRequired();

            entity.HasOne(x => x.CreatedBy)
                .WithMany()
                .HasForeignKey(x => x.CreatedById)
                .OnDelete(DeleteBehavior.Restrict);
        });

        modelBuilder.Entity<ChatJoinRequest>(entity =>
        {
            entity.ToTable("ChatJoinRequests");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => new { x.ChatId, x.Status });
            entity.HasIndex(x => new { x.InviteLinkId, x.UserId }).IsUnique();
            entity.HasIndex(x => x.UserId);

            entity.HasOne(x => x.InviteLink)
                .WithMany()
                .HasForeignKey(x => x.InviteLinkId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(x => x.User)
                .WithMany()
                .HasForeignKey(x => x.UserId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(x => x.ResolvedByUser)
                .WithMany()
                .HasForeignKey(x => x.ResolvedByUserId)
                .OnDelete(DeleteBehavior.SetNull);
        });

        modelBuilder.Entity<ChatTopic>(entity =>
        {
            entity.ToTable("ChatTopics");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => new { x.ChatId, x.IsPinned });
            entity.HasIndex(x => new { x.ChatId, x.LastActivityAt });
            entity.Property(x => x.Title).IsRequired();
            entity.Property(x => x.Icon).IsRequired();

            entity.HasOne(x => x.CreatedBy)
                .WithMany()
                .HasForeignKey(x => x.CreatedById)
                .OnDelete(DeleteBehavior.Restrict);
        });

        modelBuilder.Entity<ChatMember>(entity =>
        {
            entity.ToTable("ChatMembers");
            entity.HasKey(x => new { x.ChatId, x.UserId });
            entity.HasIndex(x => x.UserId);
            entity.HasIndex(x => new { x.UserId, x.IsPinned });
            entity.HasIndex(x => new { x.UserId, x.IsArchived });
            entity.HasIndex(x => new { x.UserId, x.FolderKey });

            entity.HasOne(x => x.User)
                .WithMany(x => x.ChatMemberships)
                .HasForeignKey(x => x.UserId)
                .OnDelete(DeleteBehavior.Cascade);
        });
    }

    private static void ConfigureMessages(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Message>(entity =>
        {
            entity.ToTable("Messages");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => new { x.ChatId, x.SentAt });
            entity.HasIndex(x => new { x.ChatId, x.SenderId, x.SentAt });
            entity.HasIndex(x => x.SenderId);
            entity.HasIndex(x => x.ReplyToMessageId);
            entity.HasIndex(x => x.TopicId);
            entity.HasIndex(x => new { x.ChatId, x.PinnedAt });
            entity.HasIndex(x => x.MediaAlbumId);
            entity.HasIndex(x => x.ExpiresAt);

            entity.Property(x => x.Text).IsRequired();

            entity.HasOne(x => x.Sender)
                .WithMany(x => x.Messages)
                .HasForeignKey(x => x.SenderId)
                .OnDelete(DeleteBehavior.Restrict);

            entity.HasOne(x => x.ReplyToMessage)
                .WithMany(x => x.Replies)
                .HasForeignKey(x => x.ReplyToMessageId)
                .OnDelete(DeleteBehavior.SetNull);

            entity.HasOne(x => x.PinnedByUser)
                .WithMany()
                .HasForeignKey(x => x.PinnedByUserId)
                .OnDelete(DeleteBehavior.SetNull);

            entity.HasOne(x => x.Topic)
                .WithMany()
                .HasForeignKey(x => x.TopicId)
                .OnDelete(DeleteBehavior.SetNull);

            entity.HasMany(x => x.Attachments)
                .WithOne(x => x.Message)
                .HasForeignKey(x => x.MessageId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasMany(x => x.Reactions)
                .WithOne(x => x.Message)
                .HasForeignKey(x => x.MessageId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasMany(x => x.Views)
                .WithOne(x => x.Message)
                .HasForeignKey(x => x.MessageId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        modelBuilder.Entity<MessageAttachment>(entity =>
        {
            entity.ToTable("MessageAttachments");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => x.MessageId);
            entity.HasIndex(x => new { x.MessageId, x.SortOrder });
            entity.Property(x => x.OriginalFileName).IsRequired();
            entity.Property(x => x.ContentType).IsRequired();
            entity.Property(x => x.StoragePath).IsRequired();
        });

        modelBuilder.Entity<MessageReaction>(entity =>
        {
            entity.ToTable("MessageReactions");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => x.MessageId);
            entity.HasIndex(x => new { x.MessageId, x.UserId }).IsUnique();
            entity.Property(x => x.Emoji).IsRequired();

            entity.HasOne(x => x.User)
                .WithMany(x => x.MessageReactions)
                .HasForeignKey(x => x.UserId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        modelBuilder.Entity<MessageView>(entity =>
        {
            entity.ToTable("MessageViews");
            entity.HasKey(x => new { x.MessageId, x.ViewerUserId });
            entity.HasIndex(x => x.ViewerUserId);

            entity.HasOne(x => x.ViewerUser)
                .WithMany()
                .HasForeignKey(x => x.ViewerUserId)
                .OnDelete(DeleteBehavior.Cascade);
        });
    }

    private static void ConfigureModeration(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ModerationLogEntry>(entity =>
        {
            entity.ToTable("ModerationLogEntries");
            entity.HasKey(x => x.Id);
            entity.Property(x => x.Id).ValueGeneratedOnAdd();
            entity.Property(x => x.Action).IsRequired();
            entity.HasIndex(x => new { x.ChatId, x.CreatedAt });
            entity.HasIndex(x => new { x.TargetType, x.TargetId });

            entity.HasOne(x => x.Chat)
                .WithMany()
                .HasForeignKey(x => x.ChatId)
                .OnDelete(DeleteBehavior.SetNull);

            entity.HasOne(x => x.ActorUser)
                .WithMany()
                .HasForeignKey(x => x.ActorUserId)
                .OnDelete(DeleteBehavior.Restrict);
        });
    }

    private static void ConfigureStories(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Story>(entity =>
        {
            entity.ToTable("Stories");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => new { x.AuthorUserId, x.CreatedAt });
            entity.HasIndex(x => x.ExpiresAt);
            entity.Property(x => x.Text).IsRequired();

            entity.HasOne(x => x.AuthorUser)
                .WithMany()
                .HasForeignKey(x => x.AuthorUserId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasMany(x => x.Attachments)
                .WithOne(x => x.Story)
                .HasForeignKey(x => x.StoryId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasMany(x => x.Views)
                .WithOne(x => x.Story)
                .HasForeignKey(x => x.StoryId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        modelBuilder.Entity<StoryAttachment>(entity =>
        {
            entity.ToTable("StoryAttachments");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => new { x.StoryId, x.SortOrder });
            entity.Property(x => x.OriginalFileName).IsRequired();
            entity.Property(x => x.ContentType).IsRequired();
            entity.Property(x => x.StoragePath).IsRequired();
        });

        modelBuilder.Entity<StoryView>(entity =>
        {
            entity.ToTable("StoryViews");
            entity.HasKey(x => new { x.StoryId, x.ViewerUserId });
            entity.HasIndex(x => x.ViewerUserId);

            entity.HasOne(x => x.ViewerUser)
                .WithMany()
                .HasForeignKey(x => x.ViewerUserId)
                .OnDelete(DeleteBehavior.Cascade);
        });
    }

    private static void ConfigurePushDevices(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<PushDevice>(entity =>
        {
            entity.ToTable("PushDevices");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => x.InstallationId).IsUnique();
            entity.HasIndex(x => x.UserId);
            entity.Property(x => x.InstallationId).IsRequired();
            entity.Property(x => x.DeviceToken).IsRequired();
            entity.Property(x => x.ClientPlatform).HasMaxLength(32);
            entity.Property(x => x.ClientVersionName).HasMaxLength(64);

            entity.HasOne(x => x.User)
                .WithMany(x => x.PushDevices)
                .HasForeignKey(x => x.UserId)
                .OnDelete(DeleteBehavior.Cascade);
        });
    }

    private static void ConfigureBots(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Bot>(entity =>
        {
            entity.ToTable("Bots");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => x.UserId).IsUnique();
            entity.HasIndex(x => x.OwnerUserId);
            entity.HasIndex(x => x.TokenHash).IsUnique();
            entity.Property(x => x.TokenHash).IsRequired();

            entity.HasOne(x => x.User)
                .WithOne(x => x.BotProfile)
                .HasForeignKey<Bot>(x => x.UserId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(x => x.OwnerUser)
                .WithMany(x => x.OwnedBots)
                .HasForeignKey(x => x.OwnerUserId)
                .OnDelete(DeleteBehavior.Restrict);
        });

        modelBuilder.Entity<BotCommand>(entity =>
        {
            entity.ToTable("BotCommands");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => new { x.BotId, x.Command }).IsUnique();
            entity.Property(x => x.Command).IsRequired();
            entity.Property(x => x.Description).IsRequired();

            entity.HasOne(x => x.Bot)
                .WithMany(x => x.Commands)
                .HasForeignKey(x => x.BotId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        modelBuilder.Entity<BotUpdate>(entity =>
        {
            entity.ToTable("BotUpdates");
            entity.HasKey(x => x.Id);
            entity.Property(x => x.Id).ValueGeneratedOnAdd();
            entity.HasIndex(x => new { x.BotId, x.Id });
            entity.HasIndex(x => new { x.BotId, x.MessageId }).IsUnique();

            entity.HasOne(x => x.Bot)
                .WithMany(x => x.Updates)
                .HasForeignKey(x => x.BotId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(x => x.Chat)
                .WithMany()
                .HasForeignKey(x => x.ChatId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(x => x.Message)
                .WithMany(x => x.BotUpdates)
                .HasForeignKey(x => x.MessageId)
                .OnDelete(DeleteBehavior.Cascade);
        });
    }

    private static void ConfigureAndroidUpdateCompatibility(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<AndroidUpdateCompatibilityHit>(entity =>
        {
            entity.ToTable("AndroidUpdateCompatibilityHits");
            entity.HasKey(x => x.Id);
            entity.Property(x => x.Id).ValueGeneratedOnAdd();
            entity.HasIndex(x => x.HitAt);
            entity.HasIndex(x => new { x.Route, x.HitAt });
            entity.HasIndex(x => new { x.ClientPlatform, x.ClientVersionCode, x.HitAt });
            entity.Property(x => x.Route).IsRequired();
            entity.Property(x => x.RequestPath).IsRequired();
        });
    }
}
