using e_suite.Database.Audit.AuditEngine;
using e_suite.Database.Audit.Extensions;
using e_suite.Database.Audit.Tables.Audit;
using eSuite.Core.Clock;
using Microsoft.EntityFrameworkCore;

namespace e_suite.Database.Audit;

public abstract class AuditableEntityContext : DbContext, IAudit
{
    private readonly AuditEngineCore _auditEngine;
    private readonly IClock _clock;

    protected AuditableEntityContext(DbContextOptions options, IClock clock) : base(options)
    {
        _clock = clock;
        _auditEngine = new AuditEngineCore(this, clock);
    }

    public DbSet<AuditDetail> AuditDetails { get; set; } = null!;

    public DbSet<AuditEntry> AuditEntries { get; set; } = null!;

    public DbSet<AuditDrillHierarchy> AuditDrillHierarchies { get; set; } = null!;

    public virtual async Task<int> NoAuditSaveChangesAsync()
    {
        return await NoAuditSaveChangesAsync(default!);
    }

    public virtual async Task<int> NoAuditSaveChangesAsync(CancellationToken cancellationToken)
    {
        return await SaveChangesAsync(cancellationToken);
    }

    public virtual async Task<int> SaveChangesAsync(AuditUserDetails auditUserDetails)
    {
        return await SaveChangesAsync(auditUserDetails, default);
    }

    private new async Task<int> SaveChangesAsync(CancellationToken cancellationToken)
    {
        return await base.SaveChangesAsync(cancellationToken);
    }

    public virtual async Task<int> SaveChangesAsync(AuditUserDetails auditUserDetails, CancellationToken cancellationToken)
    {
        var auditEntries = _auditEngine.OnBeforeSaveChanges(this, auditUserDetails, cancellationToken);
        cancellationToken.ThrowIfCancellationRequested();
        var result = await base.SaveChangesAsync(cancellationToken);
        cancellationToken.ThrowIfCancellationRequested();
        _auditEngine.OnAfterSaveChanges(this, auditEntries, cancellationToken);
        cancellationToken.ThrowIfCancellationRequested();
        await base.SaveChangesAsync(cancellationToken);
        return result;
    }

    public virtual async Task<int> AddAdhocAuditEntry(AuditUserDetails auditUserDetails, AuditType auditType, Dictionary<string, Change> fields, CancellationToken cancellationToken)
    {
        var auditEntries = new List<AuditRecorder>();

        var auditEntry = new AuditRecorder(null, auditUserDetails, _clock)
        {
            AuditType = auditType
        };
        auditEntry.Fields.AddRange(fields);

        auditEntries.Add(auditEntry);

        _auditEngine.OnAfterSaveChanges(this, auditEntries, cancellationToken);
        return await base.SaveChangesAsync(cancellationToken);
    }
}
