using ERPCore.Domain.Entities;
using ERPCore.Infra.Auth;
using ERPCore.Infra.Persistence.Auditing;
using Microsoft.EntityFrameworkCore;

namespace ERPCore.Infra.Persistence;

/// <summary>
/// EF Core context for the ERP database. The 38 Phase 1 entities and their
/// <see cref="IEntityTypeConfiguration{TEntity}"/> configurations are added under
/// Domain/Entities and Infra/Persistence/Configurations as they are implemented.
/// The authoritative schema lives in docs/10-BACKEND-PHASE1.md — do not invent it here.
/// Every save writes an immutable audit trail (FR-X-02) via <see cref="AuditScribe"/>.
/// </summary>
public class ErpDbContext : DbContext
{
    private readonly ICurrentUser _currentUser;

    public ErpDbContext(DbContextOptions<ErpDbContext> options, ICurrentUser currentUser) : base(options)
    {
        _currentUser = currentUser;
    }

    // --- Master Data (docs/10 Part C.1) ---
    public DbSet<Category> Categories => Set<Category>();
    public DbSet<Uom> Uoms => Set<Uom>();
    public DbSet<UomConversion> UomConversions => Set<UomConversion>();
    public DbSet<Item> Items => Set<Item>();
    public DbSet<ItemReorder> ItemReorders => Set<ItemReorder>();
    public DbSet<Vendor> Vendors => Set<Vendor>();
    public DbSet<Warehouse> Warehouses => Set<Warehouse>();
    public DbSet<Bin> Bins => Set<Bin>();

    // --- Cross-cutting (docs/10 Part C.7) ---
    public DbSet<User> Users => Set<User>();
    public DbSet<NumberSequence> NumberSequences => Set<NumberSequence>();

    // --- Procurement (docs/10 Part C.2) ---
    public DbSet<Requisition> Requisitions => Set<Requisition>();
    public DbSet<RequisitionLine> RequisitionLines => Set<RequisitionLine>();
    public DbSet<Rfq> Rfqs => Set<Rfq>();
    public DbSet<RfqLine> RfqLines => Set<RfqLine>();
    public DbSet<VendorQuotation> VendorQuotations => Set<VendorQuotation>();
    public DbSet<VendorQuotationLine> VendorQuotationLines => Set<VendorQuotationLine>();
    public DbSet<PurchaseOrder> PurchaseOrders => Set<PurchaseOrder>();
    public DbSet<PoLine> PoLines => Set<PoLine>();

    // --- Goods Receipt (docs/10 Part C.3) ---
    public DbSet<Grn> Grns => Set<Grn>();
    public DbSet<GrnLine> GrnLines => Set<GrnLine>();

    // --- Batch / Serial (docs/10 Part C.4) ---
    public DbSet<Batch> Batches => Set<Batch>();
    public DbSet<Serial> Serials => Set<Serial>();

    // --- Stock core: FIFO layers + immutable ledger (docs/10 Part C.5) ---
    public DbSet<StockLayer> StockLayers => Set<StockLayer>();
    public DbSet<StockLedger> StockLedger => Set<StockLedger>();

    // --- Stock transactions (docs/10 Part C.6) ---
    public DbSet<StockTransfer> StockTransfers => Set<StockTransfer>();
    public DbSet<StockTransferLine> StockTransferLines => Set<StockTransferLine>();
    public DbSet<StockAdjustment> StockAdjustments => Set<StockAdjustment>();
    public DbSet<StockAdjustmentLine> StockAdjustmentLines => Set<StockAdjustmentLine>();
    public DbSet<StockCount> StockCounts => Set<StockCount>();
    public DbSet<StockCountLine> StockCountLines => Set<StockCountLine>();

    // --- Purchase returns (docs/10 Part C.2) ---
    public DbSet<PurchaseReturn> PurchaseReturns => Set<PurchaseReturn>();
    public DbSet<PurchaseReturnLine> PurchaseReturnLines => Set<PurchaseReturnLine>();

    // --- Reference data (docs/10 Part C.7) ---
    public DbSet<ReasonCode> ReasonCodes => Set<ReasonCode>();

    // --- Cross-cutting: audit trail + GL-ready journal (docs/10 Part C.7) ---
    public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
    public DbSet<JournalEntryStub> JournalEntryStubs => Set<JournalEntryStub>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // Pick up every IEntityTypeConfiguration in this assembly
        // (Infra/Persistence/Configurations/*).
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(ErpDbContext).Assembly);
    }

    // Audit trail (FR-X-02): capture mutations before save (accurate old→new), then
    // write the log rows once inserts have their generated keys. A second base save
    // persists the logs without re-auditing them.
    public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken ct = default)
    {
        var pending = AuditScribe.Capture(ChangeTracker);
        var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, ct);
        if (pending.Count > 0)
        {
            WriteAuditLogs(pending);
            await base.SaveChangesAsync(acceptAllChangesOnSuccess, ct);
        }
        return result;
    }

    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        var pending = AuditScribe.Capture(ChangeTracker);
        var result = base.SaveChanges(acceptAllChangesOnSuccess);
        if (pending.Count > 0)
        {
            WriteAuditLogs(pending);
            base.SaveChanges(acceptAllChangesOnSuccess);
        }
        return result;
    }

    private void WriteAuditLogs(IReadOnlyList<PendingAudit> pending)
    {
        var now = DateTime.UtcNow;
        var userId = _currentUser.AuditUserId;
        foreach (var p in pending)
            AuditLogs.Add(AuditScribe.ToLog(p, userId, now));
    }
}
