using Microsoft.EntityFrameworkCore;
using ScadaLink.Commons.Entities.Audit;
using ScadaLink.ConfigurationDatabase;
using ScadaLink.ConfigurationDatabase.Configurations;

namespace ScadaLink.ConfigurationDatabase.Tests.Configurations;

/// <summary>
/// Schema-level tests for <see cref="AuditLogEntityTypeConfiguration"/> (#23 M1 Bundle B).
/// Verifies that <see cref="AuditEvent"/> maps to the AuditLog table with the
/// PK, property set, column types/lengths, and five named indexes specified in alog.md §4.
/// Inspects EF model metadata via the existing in-memory SQLite test context — no
/// database round-trips required.
/// </summary>
public class AuditLogEntityTypeConfigurationTests : IDisposable
{
    private readonly ScadaLinkDbContext _context;

    public AuditLogEntityTypeConfigurationTests()
    {
        _context = SqliteTestHelper.CreateInMemoryContext();
    }

    public void Dispose()
    {
        _context.Database.CloseConnection();
        _context.Dispose();
    }

    [Fact]
    public void Configure_MapsToAuditLogTable_WithCompositePrimaryKey()
    {
        // Composite PK {EventId, OccurredAtUtc} is required by the partitioned
        // AuditLog table — the clustered key must include the partition column
        // (OccurredAtUtc) so each row can be located in its partition (#23 Bundle C).
        var entity = _context.Model.FindEntityType(typeof(AuditEvent));

        Assert.NotNull(entity);
        Assert.Equal("AuditLog", entity!.GetTableName());

        var pk = entity.FindPrimaryKey();
        Assert.NotNull(pk);

        var pkPropertyNames = pk!.Properties.Select(p => p.Name).ToArray();
        Assert.Equal(new[] { nameof(AuditEvent.EventId), nameof(AuditEvent.OccurredAtUtc) }, pkPropertyNames);
    }

    [Fact]
    public void Configure_DeclaresUniqueIndex_OnEventIdAlone_ForIdempotencyLookups()
    {
        // EventId remains globally unique (the idempotency key for
        // InsertIfNotExistsAsync, per M1-T8) via a dedicated unique index that
        // is independent of the composite PK.
        var entity = _context.Model.FindEntityType(typeof(AuditEvent));
        Assert.NotNull(entity);

        var eventIdIndex = entity!.GetIndexes()
            .SingleOrDefault(i => i.GetDatabaseName() == "UX_AuditLog_EventId");

        Assert.NotNull(eventIdIndex);
        Assert.True(eventIdIndex!.IsUnique);

        var indexedProperty = Assert.Single(eventIdIndex.Properties);
        Assert.Equal(nameof(AuditEvent.EventId), indexedProperty.Name);
    }

    [Fact]
    public void Configure_HasExpectedPropertyCount()
    {
        var entity = _context.Model.FindEntityType(typeof(AuditEvent));
        Assert.NotNull(entity);

        var properties = entity!.GetProperties()
            .Where(p => !p.IsShadowProperty())
            .ToList();

        // AuditEvent record exposes 21 init-only properties (alog.md §4).
        Assert.Equal(21, properties.Count);
    }

    [Fact]
    public void Configure_ExpectedIndexes_WithCorrectNames()
    {
        var entity = _context.Model.FindEntityType(typeof(AuditEvent));
        Assert.NotNull(entity);

        var indexNames = entity!.GetIndexes()
            .Select(i => i.GetDatabaseName())
            .OrderBy(n => n, StringComparer.Ordinal)
            .ToList();

        // Five reconciliation/query indexes from alog.md §4, plus the EventId unique
        // index introduced alongside the composite PK (Bundle C).
        var expected = new[]
        {
            "IX_AuditLog_Channel_Status_Occurred",
            "IX_AuditLog_CorrelationId",
            "IX_AuditLog_OccurredAtUtc",
            "IX_AuditLog_Site_Occurred",
            "IX_AuditLog_Target_Occurred",
            "UX_AuditLog_EventId",
        };

        Assert.Equal(expected, indexNames);
    }

    [Theory]
    [InlineData(nameof(AuditEvent.Channel))]
    [InlineData(nameof(AuditEvent.Kind))]
    [InlineData(nameof(AuditEvent.Status))]
    [InlineData(nameof(AuditEvent.ForwardState))]
    public void Configure_EnumColumns_StoredAsVarchar32(string propertyName)
    {
        var entity = _context.Model.FindEntityType(typeof(AuditEvent));
        Assert.NotNull(entity);

        var property = entity!.FindProperty(propertyName);
        Assert.NotNull(property);

        // Enums are converted to strings (varchar(32) IsUnicode=false on SQL Server).
        Assert.Equal(typeof(string), property!.GetProviderClrType() ?? property.ClrType);
        Assert.Equal(32, property.GetMaxLength());
        Assert.False(property.IsUnicode() ?? true);
    }

    [Fact]
    public void Configure_FilteredIndexes_HaveExpectedFilters()
    {
        var entity = _context.Model.FindEntityType(typeof(AuditEvent));
        Assert.NotNull(entity);

        var correlationIdx = entity!.GetIndexes()
            .Single(i => i.GetDatabaseName() == "IX_AuditLog_CorrelationId");
        Assert.Equal("[CorrelationId] IS NOT NULL", correlationIdx.GetFilter());

        var targetIdx = entity.GetIndexes()
            .Single(i => i.GetDatabaseName() == "IX_AuditLog_Target_Occurred");
        Assert.Equal("[Target] IS NOT NULL", targetIdx.GetFilter());
    }
}
