using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Transport;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Repositories;
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Services;
using ZB.MOM.WW.ScadaBridge.DeploymentManager;
using ZB.MOM.WW.ScadaBridge.TemplateEngine;
using ZB.MOM.WW.ScadaBridge.Transport;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Templates;

namespace ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests.Import;

/// <summary>
/// #05-T5: a template's <see cref="TemplateNativeAlarmSource"/> collection must
/// survive an export→import round-trip (Add + Overwrite sync), diff in Preview,
/// and — crucially — carry across environments so an instance-level override of a
/// native alarm source no longer dangles in the target. Before this fix the
/// importer amputated the template's native sources entirely, so an imported
/// instance override referenced a source that did not exist. Same in-memory host
/// pattern as <see cref="TemplateScriptFidelityTests"/> / <see cref="BundleImporterApplyTests"/>.
/// </summary>
public sealed class NativeAlarmSourceImportTests : IDisposable
{
    private readonly ServiceProvider _provider;

    public NativeAlarmSourceImportTests()
    {
        var services = new ServiceCollection();
        services.AddSingleton<IConfiguration>(
            new ConfigurationBuilder().AddInMemoryCollection().Build());

        var dbName = $"NativeAlarmSourceImportTests_{Guid.NewGuid()}";
        services.AddDbContext<ScadaBridgeDbContext>(opts => opts
            .UseInMemoryDatabase(dbName)
            .ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning)));

        services.AddSingleton<IDataProtectionProvider>(new EphemeralDataProtectionProvider());
        services.AddScoped(sp => new ScadaBridgeDbContext(
            sp.GetRequiredService<DbContextOptions<ScadaBridgeDbContext>>(),
            sp.GetRequiredService<IDataProtectionProvider>()));

        services.AddSingleton<ITemplateGraphWatermark, TemplateGraphWatermark>();
        services.AddScoped<ITemplateEngineRepository, TemplateEngineRepository>();
        services.AddScoped<IExternalSystemRepository, ExternalSystemRepository>();
        services.AddScoped<INotificationRepository, NotificationRepository>();
        services.AddScoped<IInboundApiRepository, InboundApiRepository>();
        services.AddScoped<ISiteRepository, SiteRepository>();
        services.AddScoped<IDeploymentManagerRepository, DeploymentManagerRepository>();
        services.AddScoped<ISharedSchemaRepository, SharedSchemaRepository>();
        services.AddScoped<IAuditCorrelationContext, AuditCorrelationContext>();
        services.AddScoped<IAuditService, AuditService>();
        services.AddTransport();
        services.AddTemplateEngine();
        services.AddDeploymentManager();

        _provider = services.BuildServiceProvider();
    }

    public void Dispose() => _provider.Dispose();

    private async Task<Guid> ExportAllTemplatesAndLoadAsync()
    {
        Stream bundleStream;
        await using (var scope = _provider.CreateAsyncScope())
        {
            var exporter = scope.ServiceProvider.GetRequiredService<IBundleExporter>();
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var templateIds = await ctx.Templates.Select(t => t.Id).ToListAsync();
            var selection = new ExportSelection(
                TemplateIds: templateIds,
                SharedScriptIds: Array.Empty<int>(),
                ExternalSystemIds: Array.Empty<int>(),
                DatabaseConnectionIds: Array.Empty<int>(),
                NotificationListIds: Array.Empty<int>(),
                SmtpConfigurationIds: Array.Empty<int>(),
                ApiMethodIds: Array.Empty<int>(),
                IncludeDependencies: false);
            bundleStream = await exporter.ExportAsync(selection, user: "alice", sourceEnvironment: "dev",
                passphrase: null, cancellationToken: CancellationToken.None);
        }

        using var ms = new MemoryStream();
        await bundleStream.CopyToAsync(ms);
        ms.Position = 0;

        await using var loadScope = _provider.CreateAsyncScope();
        var importer = loadScope.ServiceProvider.GetRequiredService<IBundleImporter>();
        var session = await importer.LoadAsync(ms, passphrase: null);
        return session.SessionId;
    }

    private static TemplateNativeAlarmSource MakeSource(
        string name = "Boiler",
        string connection = "OpcUaPrimary",
        string sourceRef = "ns=3;s=Boiler.Alarm",
        string? filter = "severity>500",
        bool locked = false,
        bool inherited = false,
        bool lockedInDerived = false) => new(name)
        {
            Description = "boiler native alarms",
            ConnectionName = connection,
            SourceReference = sourceRef,
            ConditionFilter = filter,
            IsLocked = locked,
            IsInherited = inherited,
            LockedInDerived = lockedInDerived,
        };

    [Fact]
    public async Task Import_Add_CarriesTemplateNativeAlarmSources()
    {
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var t = new Template("Pump") { Description = "tpl" };
            // Set every flag non-default so BuildTemplate's fidelity is exercised.
            t.NativeAlarmSources.Add(MakeSource(locked: true, inherited: true, lockedInDerived: true));
            ctx.Templates.Add(t);
            await ctx.SaveChangesAsync();
        }
        var sessionId = await ExportAllTemplatesAndLoadAsync();

        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            ctx.Templates.RemoveRange(ctx.Templates);
            await ctx.SaveChangesAsync();
        }

        await using (var scope = _provider.CreateAsyncScope())
        {
            var importer = scope.ServiceProvider.GetRequiredService<IBundleImporter>();
            await importer.ApplyAsync(sessionId,
                new List<ImportResolution> { new("Template", "Pump", ResolutionAction.Add, null) },
                user: "bob");
        }

        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var src = await ctx.Templates
                .Where(t => t.Name == "Pump")
                .SelectMany(t => t.NativeAlarmSources)
                .SingleAsync();
            Assert.Equal("Boiler", src.Name);
            Assert.Equal("boiler native alarms", src.Description);
            Assert.Equal("OpcUaPrimary", src.ConnectionName);
            Assert.Equal("ns=3;s=Boiler.Alarm", src.SourceReference);
            Assert.Equal("severity>500", src.ConditionFilter);
            Assert.True(src.IsLocked);
            Assert.True(src.IsInherited);
            Assert.True(src.LockedInDerived);
        }
    }

    [Fact]
    public async Task Import_Overwrite_NoChange_EmitsNoNativeSourceAudit()
    {
        // An idempotent Overwrite (bundle == target) must produce ZERO
        // TemplateNativeAlarmSource* audit rows — the "no audit noise" invariant.
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var t = new Template("Pump") { Description = "same" };
            t.NativeAlarmSources.Add(MakeSource("Boiler", locked: true, lockedInDerived: true));
            ctx.Templates.Add(t);
            await ctx.SaveChangesAsync();
        }
        var sessionId = await ExportAllTemplatesAndLoadAsync();

        await using (var scope = _provider.CreateAsyncScope())
        {
            var importer = scope.ServiceProvider.GetRequiredService<IBundleImporter>();
            await importer.ApplyAsync(sessionId,
                new List<ImportResolution> { new("Template", "Pump", ResolutionAction.Overwrite, null) },
                user: "bob");
        }

        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            Assert.False(await ctx.AuditLogEntries.AnyAsync(a =>
                a.Action.StartsWith("TemplateNativeAlarmSource")));
        }
    }

    [Fact]
    public async Task Import_Overwrite_SyncsNativeAlarmSources()
    {
        // Source has Keep (changed ConditionFilter later) + Add ("Chiller"); target
        // will hold Keep (different filter) + Drop ("Legacy"). Import Overwrite must
        // update Keep, add Chiller, delete Legacy — with one audit row each.
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var t = new Template("Pump") { Description = "same" };
            t.NativeAlarmSources.Add(MakeSource("Keep", filter: "severity>500"));
            t.NativeAlarmSources.Add(MakeSource("Chiller", connection: "OpcUaChiller", sourceRef: "ns=4;s=Chiller"));
            ctx.Templates.Add(t);
            await ctx.SaveChangesAsync();
        }
        var sessionId = await ExportAllTemplatesAndLoadAsync();

        // Mutate target: change Keep's filter, drop Chiller, add Legacy.
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var t = await ctx.Templates.Include(x => x.NativeAlarmSources).SingleAsync(x => x.Name == "Pump");
            t.NativeAlarmSources.Single(s => s.Name == "Keep").ConditionFilter = "severity>900";
            t.NativeAlarmSources.Remove(t.NativeAlarmSources.Single(s => s.Name == "Chiller"));
            t.NativeAlarmSources.Add(MakeSource("Legacy", connection: "OpcUaLegacy", sourceRef: "ns=9;s=Legacy"));
            await ctx.SaveChangesAsync();
        }

        await using (var scope = _provider.CreateAsyncScope())
        {
            var importer = scope.ServiceProvider.GetRequiredService<IBundleImporter>();
            await importer.ApplyAsync(sessionId,
                new List<ImportResolution> { new("Template", "Pump", ResolutionAction.Overwrite, null) },
                user: "bob");
        }

        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var sources = await ctx.Templates
                .Where(t => t.Name == "Pump")
                .SelectMany(t => t.NativeAlarmSources)
                .ToListAsync();
            Assert.Equal(2, sources.Count);
            Assert.Equal("severity>500", sources.Single(s => s.Name == "Keep").ConditionFilter); // updated back
            Assert.Contains(sources, s => s.Name == "Chiller"); // added
            Assert.DoesNotContain(sources, s => s.Name == "Legacy"); // deleted

            Assert.True(await ctx.AuditLogEntries.AnyAsync(a =>
                a.Action == "TemplateNativeAlarmSourceUpdated" && a.EntityName == "Pump.Keep"));
            Assert.True(await ctx.AuditLogEntries.AnyAsync(a =>
                a.Action == "TemplateNativeAlarmSourceAdded" && a.EntityName == "Pump.Chiller"));
            Assert.True(await ctx.AuditLogEntries.AnyAsync(a =>
                a.Action == "TemplateNativeAlarmSourceDeleted" && a.EntityName == "Pump.Legacy"));
        }
    }

    [Fact]
    public async Task Preview_DiffsNativeAlarmSources()
    {
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var t = new Template("Pump") { Description = "tpl" };
            t.NativeAlarmSources.Add(MakeSource("Boiler", filter: "severity>500"));
            ctx.Templates.Add(t);
            await ctx.SaveChangesAsync();
        }
        var sessionId = await ExportAllTemplatesAndLoadAsync();

        // Mutate the target source so the diff has something to report.
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var t = await ctx.Templates.Include(x => x.NativeAlarmSources).SingleAsync(x => x.Name == "Pump");
            t.NativeAlarmSources.Single().ConditionFilter = "severity>900";
            await ctx.SaveChangesAsync();
        }

        await using (var scope = _provider.CreateAsyncScope())
        {
            var importer = scope.ServiceProvider.GetRequiredService<IBundleImporter>();
            var preview = await importer.PreviewAsync(sessionId);
            var pump = Assert.Single(preview.Items, i => i.EntityType == "Template" && i.Name == "Pump");
            Assert.Equal(ConflictKind.Modified, pump.Kind);
            Assert.NotNull(pump.FieldDiffJson);
            Assert.Contains("NativeAlarmSources", pump.FieldDiffJson!, StringComparison.Ordinal);
        }
    }

    [Fact]
    public async Task Import_InstanceOverride_NoLongerDangles()
    {
        // Target holds a template + site + instance whose override repoints the
        // "Boiler" native source. We export the template WITH the source, delete
        // the target's copy of the source (so the override dangles), then import
        // Overwrite — which re-adds the source — and flatten: the resolved config
        // must now carry "Boiler" with the instance override applied.
        int instanceId;
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var t = new Template("Pump") { Description = "tpl" };
            t.NativeAlarmSources.Add(MakeSource("Boiler", connection: "OpcUaPrimary", locked: false));
            ctx.Templates.Add(t);

            var site = new Site("Plant 1", "plant-1")
            {
                NodeAAddress = "akka://s@10.0.0.1:2552",
                NodeBAddress = "akka://s@10.0.0.2:2552",
            };
            ctx.Sites.Add(site);
            await ctx.SaveChangesAsync();

            ctx.DataConnections.Add(new DataConnection("OpcUaPrimary", "OpcUa", site.Id)
            {
                PrimaryConfiguration = "{}",
            });
            ctx.DataConnections.Add(new DataConnection("OpcUaBackup", "OpcUa", site.Id)
            {
                PrimaryConfiguration = "{}",
            });

            var instance = new Instance("Pump-01")
            {
                TemplateId = t.Id,
                SiteId = site.Id,
                State = InstanceState.NotDeployed,
            };
            instance.NativeAlarmSourceOverrides.Add(new InstanceNativeAlarmSourceOverride("Boiler")
            {
                ConnectionNameOverride = "OpcUaBackup",
            });
            ctx.Instances.Add(instance);
            await ctx.SaveChangesAsync();
            instanceId = instance.Id;
        }

        var sessionId = await ExportAllTemplatesAndLoadAsync();

        // Delete the target template's native source → the override now dangles.
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var t = await ctx.Templates.Include(x => x.NativeAlarmSources).SingleAsync(x => x.Name == "Pump");
            t.NativeAlarmSources.Clear();
            await ctx.SaveChangesAsync();
        }

        // Pre-check: with the source gone, flatten resolves no "Boiler".
        await using (var scope = _provider.CreateAsyncScope())
        {
            var pipeline = scope.ServiceProvider.GetRequiredService<IFlatteningPipeline>();
            var pre = await pipeline.FlattenAndValidateAsync(instanceId, CancellationToken.None, validateScripts: false);
            Assert.True(pre.IsSuccess);
            Assert.DoesNotContain(pre.Value!.Configuration.NativeAlarmSources, s => s.CanonicalName == "Boiler");
        }

        // Import Overwrite re-adds the template source.
        await using (var scope = _provider.CreateAsyncScope())
        {
            var importer = scope.ServiceProvider.GetRequiredService<IBundleImporter>();
            await importer.ApplyAsync(sessionId,
                new List<ImportResolution> { new("Template", "Pump", ResolutionAction.Overwrite, null) },
                user: "bob");
        }

        // Now the override resolves against the re-imported source.
        await using (var scope = _provider.CreateAsyncScope())
        {
            var pipeline = scope.ServiceProvider.GetRequiredService<IFlatteningPipeline>();
            var post = await pipeline.FlattenAndValidateAsync(instanceId, CancellationToken.None, validateScripts: false);
            Assert.True(post.IsSuccess);
            var boiler = Assert.Single(post.Value!.Configuration.NativeAlarmSources, s => s.CanonicalName == "Boiler");
            Assert.Equal("OpcUaBackup", boiler.ConnectionName); // instance override applied
        }
    }
}
