using Db.Models;
using Microsoft.EntityFrameworkCore;

public class AdapterContext : DbContext
{
    public AdapterContext(DbContextOptions<AdapterContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Configure your entity mappings here
        base.OnModelCreating(modelBuilder);

        // primary keys
        modelBuilder.Entity<Tag>().HasKey(t => t.Name);
        modelBuilder.Entity<Video>().HasKey(v => v.Id);
        modelBuilder.Entity<VideoMetadata>().HasKey(vm => vm.Id);
        modelBuilder.Entity<Setting>().HasKey(s => s.Name);

        // Id of VideoMetadata is the same as Video Id
        modelBuilder.Entity<VideoMetadata>()
            .Property(vm => vm.Id)
            .ValueGeneratedNever(); // Id is set by the Video entity

        // Id of Video is set by the database
        modelBuilder.Entity<Video>()
            .Property(v => v.Id)
            .ValueGeneratedOnAdd(); // Id is generated by the database

        // Id of Setting is the same as SettingName enum value
        modelBuilder.Entity<Setting>()
            .Property(s => s.Name)
            .HasConversion<string>(); // Store enum as string in the database

        // relationships

        // Video can have one Metadata (can be null)
        modelBuilder.Entity<Video>()
            .HasOne(v => v.Metadata)
            .WithOne(vm => vm.Video)
            .HasForeignKey<VideoMetadata>(vm => vm.Id);

        // Video can have many Tags
        modelBuilder.Entity<Video>()
            .HasMany(v => v.Tags)
            .WithMany(t => t.Videos)
            .UsingEntity(j => j.ToTable("VideoTags")); // Join table for many-to-many relationship

        // Setting has a unique Name
        modelBuilder.Entity<Setting>()
            .HasIndex(s => s.Name)
            .IsUnique();

        // Setting name enum is stored as string
        modelBuilder.Entity<Setting>()
            .Property(s => s.Name)
            .HasConversion<string>();

        // Setting value is stored as string
        modelBuilder.Entity<Setting>()
            .Property(s => s.Value)
            .IsRequired();

        // Video relative path is required
        modelBuilder.Entity<Video>()
            .Property(v => v.RelativePath)
            .IsRequired();

        // VideoMetadata properties
        modelBuilder.Entity<VideoMetadata>()
            .Property(vm => vm.Title)
            .IsRequired();

        modelBuilder.Entity<VideoMetadata>()
            .Property(vm => vm.Duration)
            .IsRequired();

        // VideoMetadata duration is defaulted to zero if not set
        modelBuilder.Entity<VideoMetadata>()
            .Property(vm => vm.Duration)
            .HasDefaultValue(TimeSpan.Zero);

        // VideoMetadata width and height are required
        modelBuilder.Entity<VideoMetadata>()
            .Property(vm => vm.Width)
            .IsRequired();

        modelBuilder.Entity<VideoMetadata>()
            .Property(vm => vm.Height)
            .IsRequired();

        // Tag name is required
        modelBuilder.Entity<Tag>()
            .Property(t => t.Name)
            .IsRequired();

        // Tag name is unique
        modelBuilder.Entity<Tag>()
            .HasIndex(t => t.Name)
            .IsUnique();

    }

    // Tags a video can have (e.g. "funny", "cat", "dog")
    public DbSet<Tag> Tags { get; set; }

    // Videos with tags, relative paths, and other metadata
    public DbSet<Video> Videos { get; set; }

    // Video metadata, such as title, description, and duration
    public DbSet<VideoMetadata> VideoMetadatas { get; set; }
    // Settings for the adapter, such as video path
    public DbSet<Setting> Settings { get; set; }

    public async Task<T?> GetSettingAsync<T>(SettingName settingName)
    {
        var setting = await Settings.FindAsync(settingName);
        return setting != null ? (T?)Convert.ChangeType(setting.Value, typeof(T)) : default;
    }

    public async Task SetSettingAsync<T>(SettingName settingName, T value)
    {
        var setting = await Settings.FindAsync(settingName);
        if (setting == null)
        {
            setting = new Setting { Name = settingName, Value = value?.ToString() ?? string.Empty };
            Settings.Add(setting);
        }
        else
        {
            setting.Value = value?.ToString() ?? string.Empty;
            Settings.Update(setting);
        }
        await SaveChangesAsync();
    }
}