using Extrudex.Domain.Base;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Extrudex.Infrastructure.Data.Configurations;

/// <summary>
/// Base configuration for all entities. Sets up common conventions:
/// - Table names in snake_case
/// - GUID primary keys stored as PostgreSQL UUID
/// - Automatic timestamp columns in snake_case
/// </summary>
/// <typeparam name="TEntity">The entity type to configure.</typeparam>
public abstract class BaseEntityConfiguration<TEntity> : IEntityTypeConfiguration<TEntity>
    where TEntity : BaseEntity
{
    public virtual void Configure(EntityTypeBuilder<TEntity> builder)
    {
        // Table name in snake_case
        builder.ToTable(ToSnakeCase(typeof(TEntity).Name));

        // Primary key stored as UUID
        builder.HasKey(e => e.Id);
        builder.Property(e => e.Id)
            .HasColumnName("id")
            .ValueGeneratedNever();

        // If the entity is auditable, configure the timestamp columns
        if (typeof(AuditableEntity).IsAssignableFrom(typeof(TEntity)))
        {
            ConfigureAuditColumns(builder);
        }
    }

    /// <summary>
    /// Configures audit timestamp columns (created_at, updated_at) for auditable entities.
    /// Uses string-based property names since the generic type constraint is BaseEntity
    /// and cannot be cast to AuditableEntity at compile time.
    /// </summary>
    private static void ConfigureAuditColumns(EntityTypeBuilder<TEntity> builder)
    {
        builder.Property<DateTime>("CreatedAt")
            .HasColumnName("created_at")
            .HasDefaultValueSql("now() at time zone 'utc'");

        builder.Property<DateTime>("UpdatedAt")
            .HasColumnName("updated_at")
            .HasDefaultValueSql("now() at time zone 'utc'");
    }

    /// <summary>
    /// Converts PascalCase or camelCase entity name to plural snake_case table name.
    /// e.g. MaterialBase → material_bases, AmsSlot → ams_slots
    /// </summary>
    protected static string ToSnakeCase(string name)
    {
        var snake = string.Concat(
                name.Select((ch, i) =>
                    i > 0 && char.IsUpper(ch) && (char.IsLower(name[i - 1]) || (i + 1 < name.Length && char.IsLower(name[i + 1])))
                        ? "_" + ch
                        : ch.ToString()))
            .ToLowerInvariant();

        // Pluralize: add 's' (handles most cases; irregular plurals handled explicitly if needed)
        // Special cases: already_plural stays, 'y' → 'ies', 's'/'x'/'ch'/'sh' → 'es'
        if (snake.EndsWith("s"))
            return snake; // Already plural or ambiguous — leave as-is
        if (snake.EndsWith("y") && !snake.EndsWith("ay") && !snake.EndsWith("ey") && !snake.EndsWith("oy") && !snake.EndsWith("uy"))
            return snake[..^1] + "ies";
        if (snake.EndsWith("x") || snake.EndsWith("ch") || snake.EndsWith("sh"))
            return snake + "es";
        return snake + "s";
    }
}