using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Tiku.Application.Security;

namespace Tiku.Infrastructure.Persistence;

public sealed class TenantIsolationSaveChangesInterceptor(ITenantContext tenantContext) : SaveChangesInterceptor
{
    public override InterceptionResult<int> SavingChanges(
        DbContextEventData eventData,
        InterceptionResult<int> result)
    {
        Enforce(eventData.Context);
        return result;
    }

    public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult<int> result,
        CancellationToken cancellationToken = default)
    {
        Enforce(eventData.Context);
        return ValueTask.FromResult(result);
    }

    private void Enforce(DbContext? dbContext)
    {
        if (dbContext is null) return;

        foreach (var entry in dbContext.ChangeTracker.Entries()
                     .Where(entry => entry.State is EntityState.Added or EntityState.Modified or EntityState.Deleted))
        {
            var tenantProperty = entry.Metadata.FindProperty("TenantId");
            if (tenantProperty?.ClrType != typeof(Guid)) continue;

            var property = entry.Property("TenantId");
            var currentTenantId = (Guid)(property.CurrentValue ?? Guid.Empty);
            var originalTenantId = (Guid)(property.OriginalValue ?? Guid.Empty);
            if (entry.State == EntityState.Added)
            {
                if (!tenantContext.TenantId.HasValue && !tenantContext.IsSystem)
                    throw new TenantIsolationException(
                        entry.Metadata.ClrType,
                        "Cannot add tenant-owned data without a resolved tenant.");

                if (currentTenantId == Guid.Empty && tenantContext.TenantId.HasValue)
                {
                    property.CurrentValue = tenantContext.TenantId.Value;
                    currentTenantId = tenantContext.TenantId.Value;
                }

                if (!tenantContext.IsSystem && currentTenantId != tenantContext.TenantId)
                    throw new TenantIsolationException(
                        entry.Metadata.ClrType,
                        "Cannot add data for another tenant.");

                continue;
            }

            if (property.IsModified || currentTenantId != originalTenantId)
                throw new TenantIsolationException(
                    entry.Metadata.ClrType,
                    "Tenant ownership cannot be changed.");

            if (!tenantContext.IsSystem &&
                (!tenantContext.TenantId.HasValue || originalTenantId != tenantContext.TenantId.Value))
                throw new TenantIsolationException(
                    entry.Metadata.ClrType,
                    "Cannot modify or delete data owned by another tenant.");
        }
    }
}

public sealed class TenantIsolationException(Type entityType, string message)
    : InvalidOperationException($"Tenant isolation rejected {entityType.Name}: {message}");