using Microsoft.EntityFrameworkCore;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
using ZB.MOM.WW.OtOpcUa.Configuration.Queries;

namespace ZB.MOM.WW.OtOpcUa.Configuration.Tests;

/// <summary>
/// Verifies <see cref="ClusterAuditQuery.ForClusterAsync"/> — the cluster-scoped audit view used
/// by the AdminUI ClusterAudit page. The structured AuditWriterActor path stamps NodeId (not
/// ClusterId), so before the Task 2.2 fix those rows were invisible to a cluster filtered only on
/// ClusterId. These tests pin the OR-predicate that joins NodeId back to its cluster.
/// </summary>
[Trait("Category", "Unit")]
public sealed class ClusterAuditQueryTests : IDisposable
{
    private readonly OtOpcUaConfigDbContext _db;

    /// <summary>Initializes a new instance with a fresh in-memory config database.</summary>
    public ClusterAuditQueryTests()
    {
        var options = new DbContextOptionsBuilder<OtOpcUaConfigDbContext>()
            .UseInMemoryDatabase($"cluster-audit-{Guid.NewGuid():N}")
            .Options;
        _db = new OtOpcUaConfigDbContext(options);
    }

    /// <summary>Disposes the database context.</summary>
    public void Dispose() => _db.Dispose();

    private void SeedNode(string clusterId, string nodeId) =>
        _db.ClusterNodes.Add(new ClusterNode
        {
            NodeId = nodeId,
            ClusterId = clusterId,
            Host = $"{nodeId}.local",
            ApplicationUri = $"urn:{nodeId}",
            CreatedBy = "test",
        });

    private void SeedAudit(string eventType, string? clusterId, string? nodeId, DateTime ts) =>
        _db.ConfigAuditLogs.Add(new ConfigAuditLog
        {
            Principal = "tester",
            EventType = eventType,
            ClusterId = clusterId,
            NodeId = nodeId,
            Timestamp = ts,
        });

    /// <summary>Structured rows (ClusterId null, NodeId set) for a node in the cluster are now
    /// visible, alongside the SP-path rows that stamp ClusterId directly.</summary>
    [Fact]
    public async Task Surfaces_both_clusterId_rows_and_structured_nodeId_rows()
    {
        SeedNode("LINE3-OPCUA", "LINE3-OPCUA-A");
        SeedNode("LINE3-OPCUA", "LINE3-OPCUA-B");
        SeedNode("OTHER-CLUSTER", "OTHER-A");
        var t0 = new DateTime(2026, 6, 2, 10, 0, 0, DateTimeKind.Utc);

        // SP path: stamps ClusterId.
        SeedAudit("Published", clusterId: "LINE3-OPCUA", nodeId: null, ts: t0);
        // Structured AuditWriterActor path: stamps NodeId, ClusterId null — these were invisible.
        SeedAudit("DraftEdited", clusterId: null, nodeId: "LINE3-OPCUA-A", ts: t0.AddMinutes(1));
        SeedAudit("NodeApplied", clusterId: null, nodeId: "LINE3-OPCUA-B", ts: t0.AddMinutes(2));
        // Noise that must NOT appear: other cluster's structured row + an orphan NodeId.
        SeedAudit("Published", clusterId: null, nodeId: "OTHER-A", ts: t0.AddMinutes(3));
        SeedAudit("Published", clusterId: null, nodeId: "UNKNOWN-NODE", ts: t0.AddMinutes(4));
        await _db.SaveChangesAsync();

        var rows = await ClusterAuditQuery.ForClusterAsync(_db, "LINE3-OPCUA", pageSize: 200);

        rows.Select(r => r.EventType).ShouldBe(
            ["NodeApplied", "DraftEdited", "Published"], // newest first
            ignoreOrder: false);
    }

    /// <summary>An audit row stamped with another cluster's ClusterId never appears.</summary>
    [Fact]
    public async Task Does_not_surface_other_cluster_rows()
    {
        SeedNode("LINE3-OPCUA", "LINE3-OPCUA-A");
        var t0 = new DateTime(2026, 6, 2, 10, 0, 0, DateTimeKind.Utc);
        SeedAudit("Published", clusterId: "OTHER-CLUSTER", nodeId: null, ts: t0);
        await _db.SaveChangesAsync();

        var rows = await ClusterAuditQuery.ForClusterAsync(_db, "LINE3-OPCUA", pageSize: 200);

        rows.ShouldBeEmpty();
    }

    /// <summary>Respects the page-size cap, newest first.</summary>
    [Fact]
    public async Task Caps_to_page_size_newest_first()
    {
        SeedNode("LINE3-OPCUA", "LINE3-OPCUA-A");
        var t0 = new DateTime(2026, 6, 2, 10, 0, 0, DateTimeKind.Utc);
        for (var i = 0; i < 5; i++)
            SeedAudit("DraftEdited", clusterId: null, nodeId: "LINE3-OPCUA-A", ts: t0.AddMinutes(i));
        await _db.SaveChangesAsync();

        var rows = await ClusterAuditQuery.ForClusterAsync(_db, "LINE3-OPCUA", pageSize: 3);

        rows.Count.ShouldBe(3);
        rows.First().Timestamp.ShouldBe(t0.AddMinutes(4)); // newest
    }
}
