using Data.Data.Configurations;
using DeepDrftModels.Entities;
using DeepDrftModels.Enums;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace DeepDrftData.Data.Configurations;

public class ReleaseConfiguration : BaseEntityConfiguration<ReleaseEntity>
{
    public override void Configure(EntityTypeBuilder<ReleaseEntity> builder)
    {
        // Wires up Id PK + audit columns (CreatedAt, UpdatedAt, IsDeleted) and the IsDeleted index.
        base.Configure(builder);

        builder.ToTable("release");

        // Map the base audit columns to the snake_case naming the rest of the schema uses.
        builder.Property(e => e.Id).HasColumnName("id");
        builder.Property(e => e.CreatedAt).HasColumnName("created_at");
        builder.Property(e => e.UpdatedAt).HasColumnName("updated_at");
        builder.Property(e => e.IsDeleted).HasColumnName("is_deleted");

        // App-minted GUID-string public handle, configured exactly like TrackConfiguration's
        // entry_key: required, max 100, snake_case column. The unique index guarantees a release
        // resolves to one row by its public key.
        builder.Property(e => e.EntryKey)
            .IsRequired()
            .HasMaxLength(100)
            .HasColumnName("entry_key");

        builder.HasIndex(e => e.EntryKey)
            .IsUnique()
            .HasDatabaseName("IX_release_entry_key");

        builder.Property(e => e.Title)
            .IsRequired()
            .HasMaxLength(200)
            .HasColumnName("title");

        builder.Property(e => e.Artist)
            .IsRequired()
            .HasMaxLength(200)
            .HasColumnName("artist");

        builder.Property(e => e.Genre)
            .HasMaxLength(100)
            .HasColumnName("genre");

        // Plain-text prose blurb. Generous ceiling for a paragraph; nullable (no data migration).
        builder.Property(e => e.Description)
            .HasMaxLength(4000)
            .HasColumnName("description");

        builder.Property(e => e.ReleaseDate)
            .HasColumnName("release_date");

        builder.Property(e => e.ImagePath)
            .HasMaxLength(500)
            .HasColumnName("image_path");

        // ReleaseType is meaningful ONLY when Medium == Cut. It is the Cut medium's discriminator
        // data and lives on the base table by deliberate, named exception:
        //   A CutMetadata satellite (mirroring SessionMetadata/MixMetadata) was considered and
        //   rejected. ReleaseType is read on every card of the /cuts browse — the highest-traffic
        //   read in the system. Moving it to a satellite would put a join on that hot path. So it
        //   stays here. Future media MUST NOT copy this pattern: the default is a satellite metadata
        //   table; this is the one allowed exception, justified solely by the /cuts read volume.
        //
        // The "ReleaseType only for Cut" invariant is advisory — enforced at the service layer and
        // surfaced via the nullable ReleaseDto.ReleaseType (nulled for non-Cut at the converter).
        // It is NOT a DB check constraint by choice, not necessity: EF supports HasCheckConstraint,
        // but the invariant is advisory and we keep the schema free of it.
        builder.Property(e => e.ReleaseType)
            .IsRequired()
            .HasConversion<string>()   // Store as readable string, not int ordinal
            .HasMaxLength(20)
            .HasColumnName("release_type")
            .HasDefaultValue(ReleaseType.Single);

        builder.Property(e => e.Medium)
            .IsRequired()
            .HasConversion<string>()   // Store as readable string, not int ordinal
            .HasMaxLength(20)
            .HasColumnName("medium")
            .HasDefaultValue(ReleaseMedium.Cut);   // Existing rows migrate to Cut with no data migration.

        builder.Property(e => e.CreatedByUserId)
            .HasColumnName("created_by_user_id");

        // Names the is_deleted index explicitly. BaseEntityConfiguration.Configure already
        // calls HasIndex(e => e.IsDeleted); this adds HasDatabaseName so EF always uses
        // "IX_release_is_deleted" regardless of auto-naming conventions.
        builder.HasIndex(e => e.IsDeleted).HasDatabaseName("IX_release_is_deleted");

        // Unique constraint on the natural key (title + artist). Prevents duplicate release rows
        // from concurrent uploads of the same album. The FindOrCreateRelease path catches the
        // resulting UniqueViolation and re-queries for the winning row.
        // Partial filter excludes soft-deleted rows so re-uploading a deleted release does not
        // hit a uniqueness conflict when FindOrCreateRelease creates a fresh row.
        builder.HasIndex(e => new { e.Title, e.Artist })
            .IsUnique()
            .HasDatabaseName("IX_release_title_artist")
            .HasFilter("\"is_deleted\" = false");
    }
}
