using ERPCore.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace ERPCore.Infra.Persistence.Configurations;

// Manufacturing / Production Lines (docs/30 Part C). All eleven tables live in this one
// file, following the StockConfiguration.cs precedent of grouping an aggregate's
// configurations together.
//
// Cascade shape (deliberate): the two aggregate roots cascade to their own children —
// template → stages/edges → inputs/outputs, run → stages/edges/events → inputs/outputs.
// Every *cross* reference is Restrict, because making them cascade would give EF two
// delete paths to the same table and the model validator rejects that. The consequence
// is an ordering rule for the service layer: when replacing a graph, delete edges before
// stages and inputs before outputs.

public sealed class ProductionTemplateConfiguration : IEntityTypeConfiguration<ProductionTemplate>
{
    public void Configure(EntityTypeBuilder<ProductionTemplate> builder)
    {
        builder.ToTable("production_templates");
        builder.HasKey(t => t.TemplateId);

        builder.Property(t => t.Code).IsRequired().HasMaxLength(30);
        builder.HasIndex(t => t.Code).IsUnique();

        builder.Property(t => t.Name).IsRequired().HasMaxLength(150);
        builder.Property(t => t.Description).HasMaxLength(500);

        builder.Property(t => t.Status).HasConversion<string>().HasMaxLength(20).IsRequired();

        // Same jsonb-as-string mapping as TemplateStage.FieldDefs and AuditLog.ChangeSet: a
        // typed/owned mapping would make AuditScribe emit audit rows for the nested entries.
        builder.Property(t => t.Annotations).HasColumnType("jsonb");

        builder.Property(t => t.CreatedAt).IsRequired();

        // PostgreSQL xmin system column as the optimistic concurrency token (ETag).
        builder.Property(t => t.RowVersion).IsRowVersion();

        builder.HasOne(t => t.Creator).WithMany().HasForeignKey(t => t.CreatedBy).OnDelete(DeleteBehavior.Restrict);

        builder.HasIndex(t => t.Status);
    }
}

public sealed class TemplateStageConfiguration : IEntityTypeConfiguration<TemplateStage>
{
    public void Configure(EntityTypeBuilder<TemplateStage> builder)
    {
        builder.ToTable("template_stages");
        builder.HasKey(s => s.StageId);

        builder.Property(s => s.Name).IsRequired().HasMaxLength(150);
        builder.Property(s => s.RoleLabel).HasMaxLength(60);
        builder.Property(s => s.PosX).HasPrecision(18, 4);
        builder.Property(s => s.PosY).HasPrecision(18, 4);
        builder.Property(s => s.FieldDefs).IsRequired().HasColumnType("jsonb");

        builder.HasOne(s => s.Template).WithMany(t => t.Stages)
            .HasForeignKey(s => s.TemplateId).OnDelete(DeleteBehavior.Cascade);
    }
}

public sealed class StageEdgeConfiguration : IEntityTypeConfiguration<StageEdge>
{
    public void Configure(EntityTypeBuilder<StageEdge> builder)
    {
        builder.ToTable("stage_edges");
        builder.HasKey(e => e.EdgeId);

        builder.HasOne(e => e.Template).WithMany(t => t.Edges)
            .HasForeignKey(e => e.TemplateId).OnDelete(DeleteBehavior.Cascade);

        builder.HasOne(e => e.ParentStage).WithMany()
            .HasForeignKey(e => e.ParentStageId).OnDelete(DeleteBehavior.Restrict);
        builder.HasOne(e => e.ChildStage).WithMany()
            .HasForeignKey(e => e.ChildStageId).OnDelete(DeleteBehavior.Restrict);

        // One arrow per ordered pair; self-loops and cycles are rejected by the validator.
        builder.HasIndex(e => new { e.ParentStageId, e.ChildStageId }).IsUnique();
    }
}

public sealed class StageInputConfiguration : IEntityTypeConfiguration<StageInput>
{
    public void Configure(EntityTypeBuilder<StageInput> builder)
    {
        builder.ToTable("stage_inputs");
        builder.HasKey(i => i.InputId);

        builder.Property(i => i.Source).HasConversion<string>().HasMaxLength(20).IsRequired();
        builder.Property(i => i.QtyUnit).HasConversion<string>().HasMaxLength(20).IsRequired();
        builder.Property(i => i.QtyPerBatch).HasPrecision(18, 4);

        builder.HasOne(i => i.Stage).WithMany(s => s.Inputs)
            .HasForeignKey(i => i.StageId).OnDelete(DeleteBehavior.Cascade);

        builder.HasOne(i => i.Item).WithMany().HasForeignKey(i => i.ItemId).OnDelete(DeleteBehavior.Restrict);
        builder.HasOne(i => i.FromOutput).WithMany()
            .HasForeignKey(i => i.FromOutputId).OnDelete(DeleteBehavior.Restrict);
    }
}

public sealed class StageOutputConfiguration : IEntityTypeConfiguration<StageOutput>
{
    public void Configure(EntityTypeBuilder<StageOutput> builder)
    {
        builder.ToTable("stage_outputs");
        builder.HasKey(o => o.OutputId);

        builder.Property(o => o.Name).IsRequired().HasMaxLength(150);
        builder.Property(o => o.QtyPerBatch).HasPrecision(18, 4);

        builder.HasOne(o => o.Stage).WithMany(s => s.Outputs)
            .HasForeignKey(o => o.StageId).OnDelete(DeleteBehavior.Cascade);

        builder.HasOne(o => o.Item).WithMany().HasForeignKey(o => o.ItemId).OnDelete(DeleteBehavior.Restrict);
        builder.HasOne(o => o.Uom).WithMany().HasForeignKey(o => o.UomId).OnDelete(DeleteBehavior.Restrict);
    }
}

public sealed class ProductionRunConfiguration : IEntityTypeConfiguration<ProductionRun>
{
    public void Configure(EntityTypeBuilder<ProductionRun> builder)
    {
        builder.ToTable("production_runs");
        builder.HasKey(r => r.RunId);

        builder.Property(r => r.DocNo).IsRequired().HasMaxLength(30);
        builder.HasIndex(r => r.DocNo).IsUnique();

        builder.Property(r => r.TargetQty).HasPrecision(18, 4);
        builder.Property(r => r.ScaleFactor).HasPrecision(18, 6);
        builder.Property(r => r.Status).HasConversion<string>().HasMaxLength(20).IsRequired();
        builder.Property(r => r.CreatedAt).IsRequired();
        builder.Property(r => r.RowVersion).IsRowVersion();

        builder.HasOne(r => r.Template).WithMany(t => t.Runs)
            .HasForeignKey(r => r.TemplateId).OnDelete(DeleteBehavior.Restrict);
        builder.HasOne(r => r.Warehouse).WithMany().HasForeignKey(r => r.WarehouseId).OnDelete(DeleteBehavior.Restrict);
        builder.HasOne(r => r.OutputBin).WithMany().HasForeignKey(r => r.OutputBinId).OnDelete(DeleteBehavior.Restrict);
        builder.HasOne(r => r.CancelReason).WithMany().HasForeignKey(r => r.CancelReasonCodeId).OnDelete(DeleteBehavior.Restrict);
        builder.HasOne(r => r.Creator).WithMany().HasForeignKey(r => r.CreatedBy).OnDelete(DeleteBehavior.Restrict);

        // Run board filters (docs/30 §D.2) and the template edit-lock count (FR-MFG-06).
        builder.HasIndex(r => r.Status);
        builder.HasIndex(r => new { r.TemplateId, r.Status });
    }
}

public sealed class RunStageConfiguration : IEntityTypeConfiguration<RunStage>
{
    public void Configure(EntityTypeBuilder<RunStage> builder)
    {
        builder.ToTable("run_stages");
        builder.HasKey(s => s.RunStageId);

        builder.Property(s => s.Name).IsRequired().HasMaxLength(150);
        builder.Property(s => s.RoleLabel).HasMaxLength(60);
        builder.Property(s => s.PosX).HasPrecision(18, 4);
        builder.Property(s => s.PosY).HasPrecision(18, 4);
        builder.Property(s => s.Status).HasConversion<string>().HasMaxLength(20).IsRequired();
        builder.Property(s => s.FieldDefs).IsRequired().HasColumnType("jsonb");
        builder.Property(s => s.FieldValues).HasColumnType("jsonb");
        builder.Property(s => s.RowVersion).IsRowVersion();

        builder.HasOne(s => s.Run).WithMany(r => r.Stages)
            .HasForeignKey(s => s.RunId).OnDelete(DeleteBehavior.Cascade);

        // SetNull, not Restrict: a template edit may delete a stage that completed or
        // cancelled runs still point at. Everything needed to render such a run is copied
        // onto this row, so losing the provenance link is the intended trade (FR-MFG-06).
        builder.HasOne(s => s.TemplateStage).WithMany()
            .HasForeignKey(s => s.TemplateStageId).OnDelete(DeleteBehavior.SetNull);

        builder.HasIndex(s => new { s.RunId, s.Status });
    }
}

public sealed class RunEdgeConfiguration : IEntityTypeConfiguration<RunEdge>
{
    public void Configure(EntityTypeBuilder<RunEdge> builder)
    {
        builder.ToTable("run_edges");
        builder.HasKey(e => e.RunEdgeId);

        builder.HasOne(e => e.Run).WithMany(r => r.Edges)
            .HasForeignKey(e => e.RunId).OnDelete(DeleteBehavior.Cascade);

        builder.HasOne(e => e.ParentRunStage).WithMany()
            .HasForeignKey(e => e.ParentRunStageId).OnDelete(DeleteBehavior.Restrict);
        builder.HasOne(e => e.ChildRunStage).WithMany()
            .HasForeignKey(e => e.ChildRunStageId).OnDelete(DeleteBehavior.Restrict);

        builder.HasIndex(e => new { e.ParentRunStageId, e.ChildRunStageId }).IsUnique();
    }
}

public sealed class RunStageInputConfiguration : IEntityTypeConfiguration<RunStageInput>
{
    public void Configure(EntityTypeBuilder<RunStageInput> builder)
    {
        builder.ToTable("run_stage_inputs");
        builder.HasKey(i => i.RunInputId);

        builder.Property(i => i.Source).HasConversion<string>().HasMaxLength(20).IsRequired();
        builder.Property(i => i.QtyUnit).HasConversion<string>().HasMaxLength(20).IsRequired();
        builder.Property(i => i.PlannedQty).HasPrecision(18, 4);
        builder.Property(i => i.ConsumedQty).HasPrecision(18, 4);
        builder.Property(i => i.ConsumedValue).HasPrecision(18, 4);
        builder.Property(i => i.DeliveredQty).HasPrecision(18, 4);
        builder.Property(i => i.ReturnedQty).HasPrecision(18, 4);
        builder.Property(i => i.ReturnedValue).HasPrecision(18, 4);

        builder.HasOne(i => i.RunStage).WithMany(s => s.Inputs)
            .HasForeignKey(i => i.RunStageId).OnDelete(DeleteBehavior.Cascade);

        builder.HasOne(i => i.Item).WithMany().HasForeignKey(i => i.ItemId).OnDelete(DeleteBehavior.Restrict);
        builder.HasOne(i => i.FromRunOutput).WithMany()
            .HasForeignKey(i => i.FromRunOutputId).OnDelete(DeleteBehavior.Restrict);

        // Transfers route by the source output, so this is the hot lookup.
        builder.HasIndex(i => i.FromRunOutputId);
    }
}

public sealed class RunStageOutputConfiguration : IEntityTypeConfiguration<RunStageOutput>
{
    public void Configure(EntityTypeBuilder<RunStageOutput> builder)
    {
        builder.ToTable("run_stage_outputs");
        builder.HasKey(o => o.RunOutputId);

        builder.Property(o => o.Name).IsRequired().HasMaxLength(150);
        builder.Property(o => o.PlannedQty).HasPrecision(18, 4);
        builder.Property(o => o.ProducedQty).HasPrecision(18, 4);
        builder.Property(o => o.ScrappedQty).HasPrecision(18, 4);
        builder.Property(o => o.TransferredQty).HasPrecision(18, 4);

        builder.HasOne(o => o.RunStage).WithMany(s => s.Outputs)
            .HasForeignKey(o => o.RunStageId).OnDelete(DeleteBehavior.Cascade);

        builder.HasOne(o => o.Item).WithMany().HasForeignKey(o => o.ItemId).OnDelete(DeleteBehavior.Restrict);
        builder.HasOne(o => o.Uom).WithMany().HasForeignKey(o => o.UomId).OnDelete(DeleteBehavior.Restrict);
        builder.HasOne(o => o.ScrapReason).WithMany().HasForeignKey(o => o.ScrapReasonCodeId).OnDelete(DeleteBehavior.Restrict);
    }
}

public sealed class RunStageEventConfiguration : IEntityTypeConfiguration<RunStageEvent>
{
    public void Configure(EntityTypeBuilder<RunStageEvent> builder)
    {
        // Append-only history: the app never updates or deletes these rows.
        builder.ToTable("run_stage_events");
        builder.HasKey(e => e.EventId);

        builder.Property(e => e.EventType).HasConversion<string>().HasMaxLength(20).IsRequired();
        builder.Property(e => e.Note).HasMaxLength(500);
        builder.Property(e => e.Payload).HasColumnType("jsonb");
        builder.Property(e => e.CreatedAt).IsRequired();

        builder.HasOne(e => e.Run).WithMany(r => r.Events)
            .HasForeignKey(e => e.RunId).OnDelete(DeleteBehavior.Cascade);

        // SetNull rather than Cascade: the run-level cascade above already removes these
        // rows, and a second cascade path (run → stage → event) is what EF rejects.
        builder.HasOne(e => e.RunStage).WithMany(s => s.Events)
            .HasForeignKey(e => e.RunStageId).OnDelete(DeleteBehavior.SetNull);

        builder.HasOne(e => e.User).WithMany().HasForeignKey(e => e.UserId).OnDelete(DeleteBehavior.Restrict);

        // The run-detail timeline reads the whole run's history in one ordered pass.
        builder.HasIndex(e => new { e.RunId, e.EventId });
    }
}
